CMake

最小配置示例

CMakelists.txt

1
2
3
4
cmake_minimum_required(VERSION 3.0)  # cmake最低版本
project(sample CXX) # 项目名称
add_library(sample sample.cpp) # 添加库
add_executable(sample_exe sample_exe.cpp) # 添加可执行程序

相关函数

  • project:定义工程名称,并可指定工程支持的语言
    • project(projectname [CXX] [C] [Java][C CXX])
    • 项目对应的变量:
      • PROJECT_NAME:项目名称
      • PROJECT_SOURCE_DIR:项目源码根目录
      • PROJECT_VERSION:项目版本
      • PROJECT_BINARY_DIR:项目生成的临时二进制目录,用于存放配置/编译中间文件。
  • add_library:生成库文件
    • add_library(libname [SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL] source)
      • libname: 库名称
      • source:源文件,可以使用列表变量,也可以直接添加源文件名称,还可以使用函数 target_source 添加源文件
      • SHARED:声明该库仅被作为动态库生成
      • STATIC 声明该库仅被作为静态库生成
  • add_executable:生成可执行文件
    • add_executable(exename source)

生成项目

1
2
cmake -G "Unix Makefiles" ..
make
1
2
cmake.exe -S ./sample -B ./sample/binary -G "Visual Studio 16 2019" -A x64
cmake.exe --build ./sample/binary

命令行参数

  • -S 顶级CMakeLists.txt(包含project声明)所在路径。
  • -B 存放临时编译的二进制文件(.obj、.ilk等)和编译器对应的配置文件路径。
  • -G 编译器名称
  • -A 架构名称
  • -D 使用该变量以向cmake传入各种参数,包括选项及覆盖cmake提供的各种默认变量值。
  • --toolchain cmake toolchain文件路径。
  • --install-prefix 安装的二进制存放路径。
  • --trace / --trace-expand 调试时使用,用于打印已执行的cmake代码及行号。否则仅输出函数message中的内容。
  • --build 使用cmake直接调用编译器编译项目。
  • --config 选择需要编译的项目配置类型。
  • --install 安装已编译好的二进制文件至 CMAKE_INSTALL_PREFIX 中。

依赖管理

查找依赖

  • find_package:使用预先设置的配置文件来查找依赖项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    find_package(PACKAGE_NAME_CASE_SENSITIVE
    [version] [EXACT] [QUIET]
    [REQUIRED] [[COMPONENTS] [components...]]
    [OPTIONAL_COMPONENTS components...]
    [CONFIG|NO_MODULE]
    [NO_POLICY_SCOPE]
    [NAMES name1 [name2 ...]]
    [CONFIGS config1 [config2 ...]]
    [HINTS path1 [path2 ... ]]
    [PATHS path1 [path2 ... ]]
    [PATH_SUFFIXES suffix1 [suffix2 ...]]
    [NO_DEFAULT_PATH]
    [NO_PACKAGE_ROOT_PATH]
    [NO_CMAKE_PATH]
    [NO_CMAKE_ENVIRONMENT_PATH]
    [NO_SYSTEM_ENVIRONMENT_PATH]
    [NO_CMAKE_PACKAGE_REGISTRY]
    [NO_CMAKE_BUILDS_PATH] # Deprecated; does nothing.
    [NO_CMAKE_SYSTEM_PATH]
    [NO_CMAKE_SYSTEM_PACKAGE_REGISTRY]
    [CMAKE_FIND_ROOT_PATH_BOTH |
    ONLY_CMAKE_FIND_ROOT_PATH |
    NO_CMAKE_FIND_ROOT_PATH)

    常用参数:

    • PACKAGE_NAME_CASE_SENSITIVE 查找的库的名称,大小写敏感,与之对应的是调用了包含此名称的配置文件。
    • version 依赖的版本号。如果依赖的配置同时提供了版本文件,则会使用该值对比配置中的版本而确定是否可以使用。
    • EXACT 版本号必须严格对应配置中的版本号。
    • QUIET 关闭了查找信息(不包含查找失败/错误信息)的输出。
    • REQUIRED 如果库没找到就报错。
    • CONFIG 该关键字声明了需要使用 依赖项通过自己的cmake代码 使用cmake 自动生成的 配置文件,入口配置文件名称一般为 -config.cmake 或 Config.cmake 。
  • find_library:原始的cmake查找依赖方式,直接查找依赖项库文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    find_library (
    <LIBRARY_NAME>
    name | NAMES name1 [name2 ...] [NAMES_PER_DIR]
    [HINTS [path | ENV var]... ]
    [PATHS [path | ENV var]... ]
    [PATH_SUFFIXES suffix1 [suffix2 ...]]
    [DOC "cache documentation string"]
    [NO_CACHE]
    [REQUIRED]
    [NO_DEFAULT_PATH]
    [NO_PACKAGE_ROOT_PATH]
    [NO_CMAKE_PATH]
    [NO_CMAKE_ENVIRONMENT_PATH]
    [NO_SYSTEM_ENVIRONMENT_PATH]
    [NO_CMAKE_SYSTEM_PATH]
    [CMAKE_FIND_ROOT_PATH_BOTH |
    ONLY_CMAKE_FIND_ROOT_PATH |
    NO_CMAKE_FIND_ROOT_PATH]
    )

    • LIBRARY_NAME:由于直接查找库文件而不是查找配置文件,此名称仅作为结果中宏的前缀使用。
    • NAMES:此项声明了库文件的名称。值得注意的是,在UNIX-style系统中,自动添加“lib”作为库名称的前缀。
    • NAMES_PER_DIR:一个名称遍历查找一次,再用另一个名称遍历查找一次。而不是根据路径使用多个名称遍历。
    • 查找完成后:
      • 如果查找到,则会设置 LIBRARY_NAME 为查找到的库文件的名称(包含全路径)。
      • 如果没有查找到,则会将 LIBRARY_NAME 设置为 -NOTFOUND 。

      所以这里和 find_package 又有不同,我们应当使用以下代码判断是否查找到:

      1
      2
      3
      if (PACKAGE_NAME MATCHES "-NOTFOUND")
      message(FATAL_ERROR "${PACKAGE_NAME} not found!")
      endif()

  • find_path:这个函数一般是查找头文件或其他的 非库文件 且 非可执行程序。其函数原型为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    find_path (
    <FILE_NAME>
    name | NAMES name1 [name2 ...]
    [HINTS [path | ENV var]... ]
    [PATHS [path | ENV var]... ]
    [PATH_SUFFIXES suffix1 [suffix2 ...]]
    [DOC "cache documentation string"]
    [NO_CACHE]
    [REQUIRED]
    [NO_DEFAULT_PATH]
    [NO_PACKAGE_ROOT_PATH]
    [NO_CMAKE_PATH]
    [NO_CMAKE_ENVIRONMENT_PATH]
    [NO_SYSTEM_ENVIRONMENT_PATH]
    [NO_CMAKE_SYSTEM_PATH]
    [CMAKE_FIND_ROOT_PATH_BOTH |
    ONLY_CMAKE_FIND_ROOT_PATH |
    NO_CMAKE_FIND_ROOT_PATH]
    )
    一般情况下,由于需要cmake表达式来让cmake判断使用哪个配置的库,我们通常这么写:

    1
    2
    3
    4
    5
    6
    7
    find_path(<PACKAGE_NAME>_INCLUDE_DIR NAMES header.h PATH_SUFFIXES include/...)

    find_library(<PACKAGE_NAME>_LIBRARY_RELEASE NAMES name1 name2)
    find_library(<PACKAGE_NAME>_LIBRARY_DEBUG NAMES name1d name2d)
    select_library_configurations(<PACKAGE_NAME>)
    ...
    target_*(target_name ${<PACKAGE_NAME>})
  • find_program:这个函数专门用于查找可执行程序

使用依赖

经过了上面的狂轰乱炸,我们终于可以使用依赖项了。我们可以将查找到的依赖项用于多个函数中,例如添加头文件路径,添加链接库,添加编译选项等。

对于不同的查找方式,配置文件或cmake提供了不同的使用方式:

  • 宏 例如 _INCLUDE_DIRS 和 _LIBRARIES 这种方式。

    对于头文件来讲,直接加到include_directories中就好了。而对于库来讲,则复杂点:

    由于不能混合使用debug库及release库,cmake必须明确知道在不同配置下使用哪个库。所以宏中一般使用到了cmake表达式来处理这种情况,比如: \(<\):library.lib> \(<\){NOT:$>:libraryd.lib> 所以我们在写配置时,尽量将debug和release库均查找后使用 select_library_configurations 来生成表达式以便不同配置下使用。

  • target target 就简单的多了,因为它是一个object,cmake函数可以轻松提取 target 包含的需要使用的属性来使用。 当然,target 包含非namespace与namespace两种形式,不过使用上没区别。 ## 内部依赖

  • add_dependencies:

    1
    add_dependencies(<target> [<target-dependency>]...)
    向前者添加依赖项(后者),可以添加多个。在编译或某些配置时,优先处理后者。

编译相关函数

关键字

  • target target在cmake中是一个很重要的概念,可以理解为一个实例化对象, 一般是由add_executable(),add_library() 或 add_custom_target() 命令之一创建,它包含了例如以下内容:

    • 相关的源文件列表
    • 相关的编译选项
    • 相关的依赖库
    • 相关的头文件路径列表
    • 相关的库文件路径列表
    • 相关的其他属性
  • PUBLC PRIVATE INTERFACE 例如 target_link_libraries(A [PUBLIC/PRIVATE/INTERFACE] B)
    • PUBLIC 依赖项B仅链接到目标A,若有C链接了目标A,C不链接依赖项B。
    • PRIVATE 依赖项B并不链接到目标A,若有C链接了目标A,C会链接依赖项B。
    • INTERFACE 依赖项B链接到目标A,若有C链接了目标A,C也会链接依赖项B。 > CMake 中的 PUBLIC,PRIVATE,INTERFACE

相关函数

  • target_compile_options:向目标添加编译选项

    1
    2
    3
    target_compile_options(<target> [BEFORE]
    <INTERFACE|PUBLIC|PRIVATE> [items1...]
    [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
    例如:
    1
    target_compile_options(sample PUBLIC /arch=avx2 /Wall)

  • target_compile_definitions:向目标添加预设宏声明及定义

    1
    2
    3
    target_compile_definitions(<target>
    <INTERFACE|PUBLIC|PRIVATE> [items1...]
    [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
    例如:
    1
    target_compile_definitions(sample PRIVATE BUILD_DLL PUBLIC "-DPI=3.14159")
  • target_include_directories:声明了编译目标时查找使用头文件的路径。

    1
    2
    3
    target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
    <INTERFACE|PUBLIC|PRIVATE> [items1...]
    [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

    例如:
    1
    target_include_directories(sample PUBLIC public/include/sample PRIVATE sample)
  • target_link_libraries:声明了链接时需要参与的依赖库名称或target,其名称可包含完整路径。

    1
    2
    3
    target_link_libraries(<target>
    <INTERFACE|PUBLIC|PRIVATE> [items1...]
    [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
    例如:
    1
    target_link_libraries(sample PUBLIC CURL::curl glib m)
    注意:由于一个target中包含多个属性,一般情况下包含了头文件路径。所以使用target作为参数传入此函数时,无需调用 target_include_directories 再次声明添加头文件路径。

  • target_link_libraries:声明了链接时查找依赖库的路径。

    1
    2
    3
    target_link_libraries(<target> [BEFORE]
    <INTERFACE|PUBLIC|PRIVATE> [items1...]
    [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
    例如:
    1
    target_link_libraries(sample PUBLIC third_party/libs/x86/rel)

target族函数注意事项

当需要使用cmake export关键字导出声明的target并附带其中的 PUBLIC 属性时,我们必须 将PUBLIC / PRIVATE / INTERFACE 关键字向这类 target 族函数补齐。且如果一个 target 族函数声明了这三个关键字其中之一,该 target 所属的其他 target 族函数均应当声明关键字。且对于包含路径的值,我们需要声明此值的使用范围:

编译/链接时使用。必须使用绝对路径。 导出以向下游提供。必须使用相对路径。 所以,我们通常情况下使用以下方式:

1
target_include_directories(sample PRIVATE $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/header/include> PUBLIC $<INSTALL_INTERFACE:include>)

Reference

CMake教程系列


CMake
http://chenxindaaa.com/Programming/C-C/CMake/CMake/
Author
chenxindaaa
Posted on
December 1, 2022
Licensed under