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/