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 编辑

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
        • ... 中间档 ...
    • src/
      • app/
      • calc/