CMake 入門/輸出位置與安裝

指定輸出目錄 编辑

在前面的例子裡我們並沒有特別指定輸出檔的位置,因此檔案會分佈在 binary tree 當中,而 binary tree 的架構則和 source tree 相同。

這裡先介紹 CMake 對於檔案的分類

Windows Unix
RUNTIME .exe、.dll 可執行檔
LIBRARY .so
ARCHIVE .a、.lib
包含 dll 的連結介面
.a

對應三者輸出位置的全域變數為 CMAKE_RUNTIME_OUTPUT_DIRECTORY、CMAKE_LIBRARY_OUTPUT_DIRECTORY 和 CMAKE_ARCHIVE_OUTPUT_DIRECTORY。通常我們可能會這樣指定:

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

上面這種寫法會將整個 binary tree 下所建置的執行檔以及靜態、共享程式庫搜集到建置目錄的 bin 和 lib 下。

同樣的,上面這些變數都有對應的 target 屬性,我們也可以直接指定屬性蓋過全域設定。相對應的屬性有

  • RUNTIME_OUTPUT_DIRECTORY
  • LIBRARY_OUTPUT_DIRECTORY
  • ARCHIVE_OUTPUT_DIRECTORY

這些屬性都可以利用前面介紹過的 set_target_properties() 指令設定,作用同上述的全域變數,這裡就不在詳述。

安裝 编辑

CMake 的安裝可以分為兩種,第一種是編譯完成後透過 make install 安裝,第二種是將建置好的檔案打包給用戶安裝,這裡只介紹第一種。

安裝 Target 编辑

指令 install() 用來描述安裝路徑以及一些瑣碎的設定,每個 Target 可以安裝的檔案項目有五類:

  • ARCHIVE
  • LIBRARY
  • RUNTIME
  • FRAMEWORK
  • BUNDLE

其中 ARCHIVE、LIBRARY、RUNTIME 如上所述,FRAMEWORK 和 BUNDLE 屬於 Mac OS X 專有,這裡不另介紹。另外 install() 指令也可以安裝下列 target 屬性所指定的檔案:

  • PRIVATE_HEADER
  • PUBLIC_HEADER
  • RESOURCE


每一個可安裝的檔案項目都可以分別設定 DESTINATION、PERMISSIONS、CONFIGURATIONS、COMPONENT 等選項,完整的格式看起來非常繁瑣:

install(TARGETS targets... [EXPORT <export-name>]
          [[ARCHIVE|LIBRARY|RUNTIME|FRAMEWORK|BUNDLE|
            PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
                [DESTINATION <dir>]
                [PERMISSIONS permissions...]
                [CONFIGURATIONS [Debug|Release|...]]
                [COMPONENT <component>]
                [OPTIONAL] [NAMELINK_ONLY|NAMELINK_SKIP]
          ] [...])

實際撰寫看起來像這樣:

install(TARGETS targets...
    ARCHIVE
        DESTINATION     <dir>
        PERMISSIONS     permissions...
        CONFIGURATIONS  [Debug|Release|...]
        COMPONENT       <component>
    RUNTIME
        DESTINATION     <dir>
        PERMISSIONS     permissions...
        CONFIGURATIONS  [Debug|Release|...]
        COMPONENT       <component>
    PUBLIC_HEADER
        DESTINATION     <dir>
        PERMISSIONS     permissions...
        CONFIGURATIONS  [Debug|Release|...]
        COMPONENT       <component>
   
    )

最常用的參數是 DESTINATION,用以指定安裝的目的地。假如我們有 app、calc-s、calc 三個 target,install() 指令通常寫起來像這樣:

install(TARGETS app calc calc-s
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib/static
        PUBLIC_HEADER DESTINATION include
        )

有了install 指令 CMake 會幫我們在 makefile 當中產生 install 的 rule,當我們執行完 make install 之後,這些 target 所生成的執行檔或 dll會跑到安裝路徑下的 bin,靜態程式庫會跑到 lib/static,而so會跑到 lib 資料夾下。如果有指定 target 的 PUBLIC_HEADER 屬性,指定的 header 也會自己安裝到 include 資料夾下。

可是到底安裝到哪裡去了呢?安裝路徑由全域變數 CMAKE_INSTALL_PREFIX 控制。在 Windows 上預設是

C:\Program Files\

在 Unix-like 系統預設是

/usr/local/

我們可以在 CMakeLists 當中直接用 set() 設定 CMAKE_INSTALL_PREFIX 值,不過留到執行 cmake 再由命令列指定會比較有彈性。

cmake -DCMAKE_INSTALL_PREFIX="d:/my_precious" .

除了 CMAKE_INSTALL_PREFIX 之外,CMake 所產生出來的 Makefile 也支援 DESTDIR。有下面兩種使用方式:

$ make install DESTDIR="/some/absolute/path"
或
$ export DESTDIR="/some/absolute/path"
$ make install

最後安裝的位置應該是

  • DESTDIR/
    • CMAKE_INSTALL_PREFIX/
      • install 指定的 DESTINATION

除了 DESTINATION 外,另一個常用的選項是 PERMISSIONS,用以指定檔案存取權限,可用的選項有 OWNER_READ、OWNER_WRITE、OWNER_EXECUTE、GROUP_READ、GROUP_WRITE、 GROUP_EXECUTE、WORLD_READ、WORLD_WRITE、WORLD_EXECUTE、SETUID、SETGID。在不支援這些權限設定的平台上會自動忽略。

安裝其他文件 编辑

相較於 target 安裝,其他文件相對簡單了一點點。

install(FILES files...
    DESTINATION <dir>
    [PERMISSIONS permissions...]
    [CONFIGURATIONS [Debug|Release|...]]
    [COMPONENT <component>]
    [RENAME <name>] [OPTIONAL]
    )

來源檔案是以目前工作目錄相對的路徑來表示。

另外還可以安裝整個目錄

install(DIRECTORY dirs...
    DESTINATION <dir>
    [FILE_PERMISSIONS permissions...]
    [DIRECTORY_PERMISSIONS permissions...]
    [USE_SOURCE_PERMISSIONS] [OPTIONAL]
    [CONFIGURATIONS [Debug|Release|...]]
    [COMPONENT <component>] [FILES_MATCHING]
    [[PATTERN <pattern> | REGEX <regex>]
    [EXCLUDE] [PERMISSIONS permissions...]] [...]
    )

這個功能會將來源目錄下的內容以樹狀方式複製至 DESTINATION 指定的目錄,請注意下面兩者來源目錄最後斜線的差別:

# 会将目录复制成为 dst/src/{subdirs and files...}
install(DIRECTORY   myproj/src
        DESTINATION dst)

# 会将目录复制成为 dst/{subdirs and files...}
install(DIRECTORY   myproj/src/
        DESTINATION dst)

對 Linux 使用者而言這樣的行為應該很熟悉。


參數 FILES_MATCHING 用於指定操作檔案的條件,可以使用 PATTERN 或 REGEX 兩種比對方式,要注意 PATTERN 會比對全路徑而不只是檔名。

install(DIRECTORY src/ DESTINATION include
        FILES_MATCHING PATTERN "*.h")

以上會把 src/ 底下所有副檔名為 .h 的檔案複製到 include 資料夾下,並且會保留原本目錄樹的架構。

在比對條件後面接 EXCLUDE 可用來排除符合條件的檔案或目錄,使其被 install 忽略。

install(DIRECTORY myapp/ mylib DESTINATION myproj
        PATTERN ".git" EXCLUDE
        )
  • myapp 的內容會被複製到 myproj 下
  • mylib 的內容會被複製到 myproj/mylib 下
  • 所有完整路徑中包含「.git」的檔案或目錄都不會被複製,例如 myapp/.git/branches 等等。

範例 编辑

結合前面所述,這裡以一個具體而微的範例當成結尾。

檔案樹 编辑

  • lib2/
    • src/
      • doc/
        • doc.txt
      • CMakeLists.txt
      • calc.c
      • calc.h
      • readme.txt

檔案內容

CMakeLists.txt

cmake_minimum_required(VERSION 2.6)
 
project(calc)
include_directories(${CMAKE_SOURCE_DIR})

# build

add_library(calc SHARED calc.c)
set_target_properties(calc
    PROPERTIES
    PUBLIC_HEADER "calc.h"
    )
   
add_library(calc-static STATIC calc.c)
set_target_properties(calc-static
    PROPERTIES
    OUTPUT_NAME   "calc"
    PREFIX        "lib"
    PUBLIC_HEADER "calc.h"
    )


# install
 
install(TARGETS calc calc-static
    RUNTIME DESTINATION calc/bin
    LIBRARY DESTINATION calc/lib
    ARCHIVE DESTINATION calc/lib
    PUBLIC_HEADER DESTINATION calc/include
    )

install(DIRECTORY ../src/ DESTINATION calc
    FILES_MATCHING PATTERN "*.txt"
    PATTERN "CMakeLists.txt" EXCLUDE
    )


calc.h

#ifndef CALC_H_
#define CALC_H_

#if defined(_WIN32) && defined(calc_EXPORTS)
#  if defined(LIBCALC_INTERNAL)
#    define LIBCALC_API __declspec (dllexport)
#  else
#    define LIBCALC_API __declspec (dllimport)
#  endif
#endif

#if !defined(LIBCALC_API)
#  define LIBCALC_API
#endif

LIBCALC_API int Cube(int x);

LIBCALC_API int Square(int x);

#endif


calc.c

#define LIBCALC_INTERNAL
#include <calc.h>

LIBCALC_API int Cube(int x)
{
    return x * x * x;
}


LIBCALC_API int Square(int x)
{
    return x * x;
}


剩下 readme.txt 和 doc/doc.txt 的內容就交給讀者自行發揮。

建置 编辑

假設現在的工作目錄在lib2/,執行以下命令:

$ mkdir build
$ cd build
$ cmake -DCMAKE_INSTALL_PREFIX=讀者自行指定 ../src
$ make install

在 Linux 上的安裝結果:

  • ${CMAKE_INSTALL_PREFIX}/
    • calc/
      • bin/
      • doc/
        • doc.txt
      • include/
        • calc.h
      • lib/
        • libcalc.so
        • libcalc.a
      • readme.txt


在 Windows 上得到的安裝結果:

  • ${CMAKE_INSTALL_PREFIX}/
    • calc/
      • bin/
        • libcalc.dll
      • doc/
        • doc.txt
      • include/
        • calc.h
      • lib/
        • libcalc.dll.a
        • libcalc.a
      • readme.txt