CMake 入門/建置與連結程式庫
建置程式庫
[編輯]在前面的例子裏我們直接將所有的 source 編譯、連結成執行檔,但有時候我們需要建立程式庫,之後再和執行檔連結。要在 CMakeLists 當中加入一個程式庫 target,用的是 add_library() 指令,和前面介紹的 add_executable() 指令很像,但有變化更為複雜,後續的篇幅會陸續介紹,這裏就先看最直接的用法。
指令 target_link_libraries() 用來指定某個 target 要和哪些程式庫連結,當連結的程式庫是另一個 target 時,CMake 會自動建立相依關係以確保建置順序正確。
下面這個例子 CMake 會替 MyProject 產生 myexec 和 mylib 兩個 target,並且在建立 myexec 時和 mylib 連結。
project(MyProject) add_library(mylib ${mylib_sources}) add_executable(myexec ${myexec_sources}) target_link_libraries(myexec mylib)
建置程式庫的選擇
[編輯]指令 add_library() 完整規格如下:
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
參數 name 為欲生成的程式庫名稱,cmake 會依照編譯器慣例自動加上前綴和副檔名,例如名稱為 calc 的程式庫最後生成的檔案可能是 libcalc.a 或 libcalc.lib,因此不必在 add_library() 的名稱中加上 lib* 前綴。
後面的 [STATIC | SHARED | MODULE] 為欲建置的程式庫類型:
- STATIC:靜態程式庫。
- SHARED:共享程式庫,例如 Unix like 系統上的 so 檔或 Windows 上的 dll 檔。在建置 dll 時也會連帶生成連結用的 .a 檔,例如建置 libcalc.dll 時也會生成 libcalc.dll.a。
- MODULE:動態連結程式庫,不在編譯期進行連結,而是等到執行期才透過 dlopen() 或 LoadLibrary() 方式調用。一般情況下 MODULE 輸出的檔案類型也是 so 或 dll,但建立 dll 時不會順便產生 .a 檔。
如果沒有指定 target 之 STATIC、SHARED、MODULE 類型,則由全域變數 BUILD_SHARED_LIBS 來決定要建構何者,BUILD_SHARED_LIBS 預設為 OFF,亦即建立靜態程式庫。雖然可以直接在 CMakeLists 當中用 set() 指令指派 BUILD_SHARED_LIBS 的值,不過等到執行 CMake 再用 -D 指定通常會比較有彈性。
$ cmake -DBUILD_SHARED_LIBS=ON
這裏的 EXCLUDE_FROM_ALL 功能和 add_executable 一樣,都是使 make all 時不要主動建置此 target,除非手動指定或者是其他相依的 target 提出需求。
連結程式庫的選擇
[編輯]指令 target_link_libraries()用於指定 target 所需要連結的項目,並且可以依不同組態選擇不同的連結項目。
target_link_libraries(<target> [item1 [item2 [...]]] [[debug|optimized|general] <item>] ...)
連結項目可以是另一個 target,或者是編譯器能識別的程式庫表示法,例如 gcc 會依照 -l 所指定的名稱,尋找預設路徑下由 lib 開頭、附檔名為.a的檔案作為連結項目。
target_link_libraries(myapp debug -labc optimized -lxyz )
myapp 在 debug build 時會連結 libabc.a,在 release build 時會連結 libxyz.a,這裏假設上述兩者都放在編譯器預設的查找路徑之下。
稍微複雜一點的例子
[編輯]接下來看一個稍微複雜的例子,我們分別建立兩個子專案,分別是名為 app 的執行檔和名為 calc 的程式庫,原始碼分散在各自的資料夾。
- lib1/
- src/
- app/
- CMakeLists.txt
- main.c
- calc/
- CMakeLists.txt
- calc.c
- calc.h
- CMakeLists.txt
- app/
- src/
專案 app
[編輯]lib1/src/app/main.c
#include <stdio.h>
#include <calc/calc.h>
int main()
{
printf("Square of 2 is %d \n", Square(2));
printf("Square of 5 is %d \n\n", Square(5));
printf("Cube of 2 is %d \n", Cube(2));
printf("Cube of 5 is %d \n\n", Cube(5));
return 0;
}
lib1/src/app/CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
include_directories(${CMAKE_SOURCE_DIR})
project(app)
add_executable(app main.c)
target_link_libraries(app calc)
注意這裏使用 include_directories() 指令添加引入檔的查找目錄,CMAKE_SOURCE_DIR 即 CMake 進入 source tree 的起始目錄,也就是本例的 lib1/src。
以 GCC 編譯的話,執行 make VERBOSE=1 應該會看到編譯時加入類似 -I"lib1/src" 的項目。
專案 calc
[編輯]lib1/src/calc/calc.h
#ifndef CALC_H_
#define CALC_H_
int Cube(int x);
int Square(int x);
#endif
lib1/src/calc/calc.c
int Cube(int x)
{
return x * x * x;
}
int Square(int x)
{
return x * x;
}
lib1/src/calc/CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
project(calc)
add_library(calc calc.c)
最上層的 CMakeLists
[編輯]lib1/src/CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
add_subdirectory(calc)
add_subdirectory(app)
上面的 app 和 calc 都是完整的 CMake 專案,可以獨立建置。這裏在最上層加了一個 CMakeLists,內容只有兩條 add_subdirectory() 指令,這個指令的功能在於告訴 CMake 到子目錄下執行子目錄的 CMakeLists。
建置此範例
[編輯]設當前工作目錄在 lib1/,執行下列命令:
$ mkdir -p build $ cd build $ cmake ../src $ make
結果會在 build 目錄下比照 src 架構生成了 app 和 calc 兩個目錄,存放 app 和 libcalc.a 兩個目的檔。
- lib1/
- build/
- app/
- app (執行檔)
- ... 中間檔 ...
- calc/
- libcalc.a
- ... 中間檔 ...
- app/
- src/
- app/
- calc/
- build/