CMake 的一个简单案例释义

案例

# CMakeLists.txt
cmake_minimum_required(VERSION 3.25)
project(main)

set(CMAKE_MAKE_PROGRAM "/usr/bin/make")
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED True)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/target/libs)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/target/libs)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/target)
set(BUILD_SHARED_LIBS ON)

set(SRC_LIST main.c)

add_library(sorting "libs/sorting.c")
add_library(utils "libs/utils.c")
add_library("string" "libs/string.c")

add_executable(${PROJECT_NAME} ${SRC_LIST})

target_link_libraries("string" PUBLIC sorting utils)
target_link_libraries(${PROJECT_NAME} PUBLIC sorting utils "string")
  1. cmake_minimum_required(VERSION 3.25)。表示要求 CMake 程序的版本最低为 3.25,并设置 CMAKE_MINIMUM_REQUIRED_VERSION 的值为 3.25。参见 cmake-minimum-required

    [!note] 注意
    CMake 文档建议在项目最顶级的 CMakeLists.txt 文件 (因为这个文件是可以在子目录中再次配置的) 的最前面使用这个命令。并且建议要比 project 命令 (见下) 要更早使用,且不要在某个 function() 中调用此命令。

  2. project(main)。表示当前项目的名字。这个命令会设置当前 CMakeLists.txt 文件所在范围内的 PROJECT_NAME 变量。如果在顶级 CMakeLists.txt 文件中使用,还会同时设置 CMAKE_PROJECT_NAME 变量。其他一些用法比如设置项目描述信息、版本号等,参阅 project

  3. set(CMAKE_MAKE_PROGRAM "/usr/bin/make")。首先 set 命令用于手动设定一个变量的值,当需要将某个变量的值设置为空字符串时,可以使用 set(<variable> "") 这样的写法。参见 set

    如果想要彻底清除一个变量,则需要使用 unset(<variable>) 命令,参见 unset

    其次,CMAKE_MAKE_PROGRAM 表示需要在后续构建过程中使用的二进制程序名 (如果已经在 PATH 环境变量中存在,就只需要二进制程序名,比如 make) 或者完整路径,比如 /usr/bin/make。参见 CMAKE_MAKE_PROGRAM

  4. set(CMAKE_C_STANDARD 11)。表示将构建时使用的 C 语言标准设置为 C11。这个变量的值是添加构建目标 (target) 时对应 C_STANDARD 属性的默认值。参见 CMAKE_C_STANDARD

  5. set(CMAKE_C_STANDARD_REQUIRED True)。表示添加构建目标时需要设定 C_STANDARD 属性,它常常与上一条的变量配合使用。参见 CMAKE_C_STANDARD_REQUIRED

  6. set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/target/libs)CMAKE_ARCHIVE_OUTPUT_DIRECTORY 变量用于设定库构建目标的输出内容存放的位置,这里使用 ${CMAKE_BINARY_DIR}/target/libs 表示在项目根目录创建一个 target/libs 的目录结构,用于存放相关构建产物。关于它与后面两项的区别,参见后文辨析 CMAKE_LIBRARY_OUTPUT_DIRECTORYCMAKE_RUNTIME_OUTPUT_DIRECTORYCMAKE_ARCHIVE_OUTPUT_DIRECTORY 的部分。

  7. set(BUILD_SHARED_LIBS ON)。用于修改 add_library() 命令的默认行为。默认情况下此命令会构建静态库,但是启用了这个变量后,默认会构建共享库。

  8. set(SRC_LIST main.c)。使用一个变量来代表源文件列表。这对于有多源文件复用或源文件很多的情况帮助较大。

  9. add_library(sorting "libs/sorting.c") 等。定义一个名为 sorting 的链接库。由于此前通过 BUILD_SHARED_LIBS 开启了默认共享库构建,所以产物都是动态链接库。比如在 Windows 上,产物的名字是 cygsorting.dll

  10. add_executable(${PROJECT_NAME} ${SRC_LIST})。这行命令的目的是定义程序的可执行文件生成目标。我们用项目名字 (早前使用 project() 命令定义的名字) 为这个文件取名,然后通过变量 SRC_LIST 表名需要使用的源文件清单。注意使用变量时需要使用 ${} 标记,这是与定义时不同的。

  11. target_link_libraries("string" PUBLIC sorting utils)。为某个构建目标指定依赖项。比如我们的 string 库依赖 sortingutils,就需要这样定义。参见 target_link_libraries

辨析

CMAKE_SOURCE_DIRCMAKE_BINARY_DIR

默认情况下,这两个值是相同的,都是当前构建/树的顶级所在目录。如果没有使用 -S-B 选项指定,也就是当采用了 in-source 模式,这两个值都是一样的。如果希望获取当前所在一级的构建树目录,应该使用 CMAKE_CURRENT_BINARY_DIR 变量;若是当前所在一级的源码树目录,则应该使用 CMAKE_CURRENT_SOURCE_DIR 变量。

参考:

  1. CMAKE_BINARY_DIR
  2. CMAKE_SOURCE_DIR
  3. Building with CMake

CMAKE_LIBRARY_OUTPUT_DIRECTORYCMAKE_RUNTIME_OUTPUT_DIRECTORYCMAKE_ARCHIVE_OUTPUT_DIRECTORY

这三个变量都是用来设置顶级 (全局) 构建产物 (buildsystem artifact) 的输出或存放位置的。

最容易判断的就是 CMAKE_RUNTIME_OUTPUT_DIRECTORY,因为它一定是指构建产物中的 可执行文件 类型 (executable)。所谓可执行文件类型,具体分为两类:

  • 常见的 .exe 可执行文件。对应的产物由 add_executable() 命令来定义和产生;
  • 在 Windows 等 DLL 平台上,通过带有 SHARED 参数的 add_library() 命令构建的所有共享库的可执行 .dll 产物[1]

其次 CMAKE_LIBRARY_OUTPUT_DIRECTORY定义了 共享库 类型 (shared library) 构建产物的输出位置。共享库类型的产物可以通过以下方式定义:

  • 通过带 MODULE 参数的 add_library() 命令定义的所有可加载模块产物,如 .so.lib
  • 在 Linux、Unix、macOS 等非 DLL 平台上,通过带有 SHARED 参数的 add_library() 命令构建的所有共享库的可执行 .so.dylib 产物。

最后 CMAKE_LIBRARY_OUTPUT_DIRECTORY定义了 归档 类型 (archive) 构建产物的输出位置。归档类型的产物可以通过以下方式定义:

  • 通过带 STATIC 参数 (默认) 的 add_library() 命令定义的所有静态链接库 (static library) 产物,如 .a.lib
  • 在 DLL 平台: 使用了 SHARED 选项的 add_library() 命令定义的那些可供链接器导入的共享库目标 (如 .lib)。注意,只有当对应的库导出了至少一个非托管的符号 (unmanaged symbol) 时,才会生成对应的构建产物。
  • 在 DLL 平台: 定义了 ENABLE_EXPORTS 属性的 add_executable() 命令定义的那些可供链接器导入的文件 (比如 .lib)。
  • 在 AIX 平台[2]: 定义了 ENABLE_EXPORTS 属性的 add_executable() 命令定义的那些可供链接器导入的文件 (比如 .imp)。
  • 在 macOS 平台,同时使用 SHARED 选项和 ENABLE_EXPORTS 属性的 add_library() 命令定义的那些可供链接器导入的文件 (比如 .tbd)。

参考:

  1. Runtime Output Artifacts
  2. Library Output Artifacts
  3. Archive Output Artifacts

  1. 注意,这里的可执行 .dll 是指 DLL 文件中包含了可以执行的全部必要程序逻辑,只是没有经过链接器 (linker) 的处理,不能直接找到入口函数的可执行库文件。也就是说,只要能找到入口函数,这些 .dll 文件也是可以执行的。 ↩︎

  2. AIX (Advanced Interactive eXecutive) 是 IBM 公司开发的一套基于 Unix 的操作系统,参见 IBM AIX↩︎

posted @ 2023-11-17 18:45  那阵东风  阅读(45)  评论(0编辑  收藏  举报