CMake Turorial 个人笔记
本文是我学习 cmake 官方教程中记录的一些笔记,cmake目前的官方教程还是不错的,给出了12个Step,每个Step有若干Exercise做练习,这些内容都是可以运行的,在官方的GitHub仓库给出了这些Step的源代码,位置在 Help/guide/tutorial,或者你也可以使用下面我给出的百度网盘链接下载。
官方教程链接 CMake Tutorial — CMake 4.1.0 Documentation
官方 Github 仓库 Kitware/CMake: Mirror of CMake upstream repository
Turorial 源代码 https://pan.baidu.com/s/172iKEf52NNbd_pneBgvUrA?pwd=9i3n 提取码: 9i3n
一个典型的 cmake 项目配置过程
~/package $ mkdir build
~/package $ cd build
~/package/build $ cmake ..
~/package/build $ cmake --build .
cmake 工作流程大致如下,第一步 cmake .. 会根据 .. 目录下的 CMakeLists.txt 配置文件生成构建系统配置文件,常见构建系统有 Unix MakeFiles Ninja Visual Studio 17 2022 等,你可以通过 cmake -G 来查看并指定构建系统。第二步使用生成的构建系统的配置文件进行构建,例如 Unix Makefiles 会使用 make 命令,Ninja 使用 ninja 等,而 cmake --build . 是一种通用的命令,cmake会自行根据 . 目录自动构建。
# 使用 Visual Studio 2022 生成器
cmake -G "Visual Studio 17 2022" ..
# 使用 Ninja 生成器
cmake -G "Ninja" ..
# 使用 MinGW Makefiles 生成器
cmake -G "MinGW Makefiles" ..
一些资料中把第一步叫配置 (Configuration),第二步叫构建 (Build)
Step1: A Basic Starting Point
Exercise1: Building a Basic Project
- 添加最低版本要求
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])
<min>:指定最低版本号(如 3.10),低于此版本会报错并停止配置。<policy_max>(可选):指定 CMake 的策略兼容性最大版本,通常不用。FATAL_ERROR(可选):如果版本不满足,立即终止并报错。
例如
cmake_minimum_required(VERSION 3.10...4.0.1 FATAL_ERROR)
- 添加项目名称和版本号
project(Tutorial VERSION 1.0)
调用 project 的 cmakelist.txt 是顶层配置文件,其路径是 ${PROJECT_SOURCE_DIR}
- 添加可执行目标 (executable target)
add_executable(Tutorial tutorial.cxx)
Exercise2: Specifying the C++ Standard
- 设置C++标准并启用,需要在
add_executable()前设置
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
这种设置是全局的,在 Step3 我们会使用一种更精确可控的方法添加C++标准
Exercise3: Configured Header File
这部分我们定义了一个 .h.in 文件,其中所有形如的 @VAR@ 的内容都会被 cmake 使用 configure_file 替换,并输出 .h 文件
- 替换文件中的 cmake 变量
使用 configure_file 将 TutorialConfig.h.in 中的cmake变量替换并生成新文件 TutorialConfig.h
# CMakeLists.txt
configure_file(TutorialConfig.h.in TutorialConfig.h)
// TutorialConfig.h.in
// cmake 变量 Tutorial_VERSION_MAJOR 是主版本号,第二个是次版本号
// 在执行 project(<name> VERSION 1.0) 时就已经设置了
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
// TutorialConfig.h
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
- 添加 include 目录
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
这里使用了cmake内置变量 ${PROJECT_BINARY_DIR} 代表 build 构建目录
Step2: Adding a Library
Exercise1: Creating a Library
- 添加一个库目标 (library target)
## add_library(<target> <source file>)
add_library(MathFunctions MathFunctions.cxx)
- 添加子目录
## add_subdirectory(dir)
add_subdirectory(MathFunctions)
- 将库目标 (library target) 链接到可执行目标 (executable target)
target_link_libraries(Tutorial PUBLIC MathFunctions)
链接的可见性
public private interface 分别对于以下目标可见
- PUBLIC 目标 + 依赖目标的目标
- PRIVATE 目标
- INTERFACE 依赖目标的目标
什么是可见
-
编译选项的可见性:某些编译选项(如
-D宏定义、-I头文件路径)在目标编译时是否使用 (例如target_compile_options或target_compile_definitions等可以设置) -
头文件路径的可见性:目标能否引入这些路径下的头文件,即在代码中 include (
target_include_directories可以设置) -
链接库的可见性:目标在链接时是否使用该库 (
target_link_libraries可以设置)
- 添加include路径
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
内置变量
${PROJECT_BINARY_DIR}:项目的构建目录,也就是运行cmake ..时所在的目录,例如build目录${PROJECT_SOURCE_DIR}: 项目顶层CMakeLists.txt文件的目录
在这里添加 ${PROJECT_BINARY_DIR} 目的是引入被替换的 include.in.h 文件
添加 ${PROJECT_SOURCE_DIR} 目的引入 MathFunctions 库中的头文件
Exercise2: Adding an Option
例如,C/C++ 可以通过命令行传递参数,cmake 也可以在cmake配置时传递参数,这些参数变量会被缓存在本地,无需在每次构建时重新配置cmake并设置该值
cmake .. -DUSE_MYMATH=OFF ## DUSE_MYMATH 是自定义选项
cmake --build .
- 设置选项 (option)
option 创建一个布尔缓存变量
option(USE_MYMATH "Use tutorial provided math implementation" ON)
- 传递编译定义
编译器会接收到一个编译参数 -DUSE_MYMATH ,即添加一个 USE_MYMATH 的宏定义,效果相当于 #define USE_MYMATH
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
还可以添加带值的编译定义,效果相当于 #define VALUE 1
target_compile_definitions(MyTarget PUBLIC "VALUE=1")
这样定义后,MathFunctions 所属的源文件就可以使用这个宏定义了,例如
#ifdef USE_MYMATH
return detail::mysqrt(x);
#else
return std::sqrt(x);
#endif
- cmake 中的 if 语句
if (USE_MYMATH)
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
endif(USE_MYMATH) ## endif 括号的内容仅作注释,无实际效果
- 利用编译选项
USE_MYMATH
例如,我们可以在 cmakelist.txt 中创建一个 if 代码块,在代码块中编译对应的项目,实现一个库的可选编译
add_library(MathFunctions MathFunctions.cxx)
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)
# 添加编译器宏定义 等价于给编译器传递-D选项
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
# 仅在 USE_MYMATH=ON 时链接 SqrtLibrary
add_library(SqrtLibrary STATIC
mysqrt.cxx
)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif(USE_MYMATH) # 括号内容无所谓
上面代码实现的功能是,当使用 USE_MYMATH 为 ON 时,加入我们自己实现的链接库,否则就不使用自定义的链接库
相应的 target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH") 语句给编译器传递了信息,在源代码中要使用这个信息同时适配两种情况
#ifdef USE_MYMATH
return detail::mysqrt(x);
#else
return std::sqrt(x);
#endif
- 查看 cmake 选项
cmake -L
CMAKE_BUILD_TYPE:STRING=Debug
CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE
CMAKE_INSTALL_PREFIX:PATH=C:/Program Files (x86)/Tutorial
USE_MYMATH:BOOL=OFF
Step3: Adding Usage Requirements for a Library
Exercise 1 - Adding Usage Requirements for a Library
我们可以给一个库目标添加使用需求,这些需求可以被传递给其他目标
例如头文件搜索目录就是一个使用需求,对于链接到这个库目标的可执行目标,不需要手动引入库目录作为include文件搜索路径,而是由库目标本身定义的使用需求 (usage requirements) 指出 include 路径,并传递给可执行目标
- 对库目标的使用需求进行修改,使其符合 Modern CMake
库目标对 current source directory 是不需要的,也就是使用 INTERFACE 可见性
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
让我们这样声明的动机是,一个库中会有接口头文件,库的源代码文件不会使用这些头文件,而任何链接到这个库的目标都需要使用这些头文件。cmake 官方教程中把 INTERFACE 比喻为消费者 (consumers) 需要但生产者 (producer) 不需要
使用这种技术,可以使得可执行目标使用一个库时,唯一要做的就是 target_link_libraries() 而不需要添加头文件搜索路径,在大型项目中很有用
Exercise 2 - Setting the C++ Standard with Interface Libraries
- 使用接口库而不是 set() 变量来设置我们的 C++ 标准
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
| 类型 | 生成文件 | 作用 |
|---|---|---|
STATIC | 静态库(.lib) | 嵌入到目标中,独立于运行时。 |
SHARED | 动态库(.dll) | 在运行时动态加载,多个目标共享。 |
INTERFACE | 无文件 | 仅传递编译选项、包含路径等。 |
OBJECT | 目标文件(.o) | 生成目标文件供其他目标使用,不生成库文件。 |
ALIAS | 无文件 | 为现有目标创建别名,不能添加源文件或属性。 |
target_compile_features 的主要作用是为指定的目标启用某些编译器特性,例如 C++ 标准(如 cxx_std_11)或其他语言功能。CMake 会根据这些特性自动设置适当的编译器标志(如 -std=c++11 或 /std:c++11)
PRIVATE: 仅对目标自身生效。PUBLIC: 对目标自身和依赖它的目标生效。INTERFACE: 仅对依赖该目标的目标生效。
采用上述方法设置 C++ 标准的好处是,相比起全局设置的 set(),这种方法可以精确控制每个目标的 C++ 标准特性,更加灵活。
Step4: Adding Generator Expressions (生成器表达式)
Exercise 1: Adding Compiler Warning Flags with Generator Expressions
生成器表达式是一段模式字符串,cmake会在运行时动态替换这段字符串
- 使用生成器表达式确定编译器
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
COMPILE_LANG_AND_ID 是 CMake 中的一个生成器表达式,用于检查当前编译器的语言和编译器 ID,如果当前编译器的语言是 lang,并且编译器的 ID 是 id1、id2 等之一,则返回 1(真),否则为 0
$<COMPILE_LANG_AND_ID:lang,id1,id2,...>
- 使用
$<condition:value>添加编译选项
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>"
"$<${msvc_cxx}:-W3>"
)
condition:一个布尔条件。如果条件为真,则返回value;否则返回空字符串。value:当条件为真时使用的值。
- 设置仅在构建时提示警告信息,而不是安装时
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)
BUILD_INTERFACE 是 CMake 的一个生成器表达式,用于区分构建时和安装后使用时的行为
$<BUILD_INTERFACE:content>
Step5: Installing and Testing
在执行 cmake --build . 构建成功后,install 实际做的就是拷贝 build 目录里的目标文件
cmake --install .
对于多配置,使用 --config 参数指定
cmake --install . --config Release
使用 --prefix 指定安装路径,这会影响 cmake 变量 CMAKE_INSTALL_PREFIX
cmake --install . --prefix "/home/myuser/installdir"
Exercise 1: Install Rules
- 安装库目标
对于一个库,我们希望将库和头文件分别安装到 lib 和 include 目录中
# 将要安装的库目标打包到一个变量
set(installable_libs MathFunctions tutorial_compiler_flags)
if(TARGET SqrtLibrary)
list(APPEND installable_libs SqrtLibrary)
endif()
install(TARGETS ${installable_libs} DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
DESTINATION 指定安装目标的路径,lib 表示将这些目标安装到安装目录下的 lib 子目录中,安装目录的根路径由 CMAKE_INSTALL_PREFIX 决定,默认是 /usr/local(Linux/macOS)或 C:/Program Files
- 安装可执行目标
对于可执行文件,我们希望将可执行文件和配置的头文件分别安装到 bin 和 include 目录中
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include
)
Exercise 2: Testing Support
ctest -N # 列出所有测试
ctest -VV # 运行所有测试
如果使用多配置生成器(例如 Visual Studio),还需要指定类型 -C <Debug | Release>
VV 表示 “Very Verbose”
- 测试可执行文件
启动测试
enable_testing()
使用 add_test() 添加测试项,这里我们没有检查输出是否正确
add_test(NAME Runs COMMAND Tutorial 25)
NAME:指定测试的名称。Runs:测试的名称,用户可以通过这个名称识别和运行测试。例如,运行ctest -R Runs会只执行这个测试。COMMAND:指定测试运行时要执行的命令。Tutorial:要运行的可执行文件的名称25:传递给Tutorial可执行文件的参数
ctest -R Runs 并不会让程序输出结果,仅仅显示是否运行通过
使用 PASS_REGULAR_EXPRESSION 验证输出字符串是否正确
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
- 使用函数来快速添加测试项
function(do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction()
# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is (-nan|nan|0)")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
Step 6: Adding Support for a Testing Dashboard
ctest [-VV] -D Experimental
-D Experimental:以实验性模式运行测试,并将结果提交到 CDash
cmake 其他测试模式
Nightly:用于夜间构建和测试。Continuous:用于持续集成测试。
夜间构建(Nightly Build) 是一种软件开发中的自动化构建和测试策略,通常在每天的固定时间(例如午夜)触发。它的主要目的是在开发者工作时间之外,自动构建项目、运行测试并生成报告,以便在第二天开发者开始工作时可以查看构建和测试的结果。
在 CTestConfig.cmake 中配置了 CDash
# ./CTestConfig.cmake
set(CTEST_NIGHTLY_START_TIME "00:00:00 EST") # 夜间开始时间
set(CTEST_SUBMIT_URL "https://my.cdash.org/submit.php?project=CMakeTutorial") # 要发送到的url
Exercise1: Send Results to a Testing Dashboard
- 添加仪表盘支持
唯一要做的就是修改 enable_testing()为 include(CTest),后者的作用如下
- 启用测试功能,使得后续的
add_test()和do_test()函数可以正常工作。 - 配合
CTestConfig.cmake文件,支持将测试结果提交到 CDash。
# Replace enable_testing() with include(CTest)
# enable_testing()
include(CTest)
Step 7: Adding System Introspection (系统探查)
System Introspection(系统探查) 是指通过 CMake 脚本检测和查询目标系统的特性、工具链、库、头文件、编译器功能等信息的过程。这种探查通常用于确保项目能够在不同的平台和环境中正确构建。
有这样一个问题,我们想知道当前头文件是否提供了某些函数,例如 log 和 exp ,使用cmake脚本可以完成这件事
- System Introspection
要解决这个问题,需要引入 CheckCXXSourceCompiles 模块
# ./CMakeLists.txt
include(CheckCXXSourceCompiles)
这个模块提供了 check_cxx_source_compiles 宏
check_cxx_source_compiles(SOURCE_CODE VARIABLE)
SOURCE_CODE: 一段 C++ 源代码(以字符串形式提供)。VARIABLE: 一个变量名,用于存储编译结果。如果代码能够成功编译,变量会被设置为1,否则为0。
使用如下代码验证是否存在 log 和 exp 函数,并将结果保存在 HAVE_LOG 和 HAVE_EXP 变量中
check_cxx_source_compiles("
#include <cmath>
int main() {
std::log(1.0);
return 0;
}
" HAVE_LOG)
check_cxx_source_compiles("
#include <cmath>
int main() {
std::exp(1.0);
return 0;
}
" HAVE_EXP)
传递变量到目标
if(HAVE_LOG AND HAVE_EXP)
target_compile_definitions(SqrtLibrary
PRIVATE "HAVE_LOG" "HAVE_EXP"
)
endif()
这样在我们的源代码中,就可以使用这两个宏 HAVE_LOG HAVE_EXP 来分情况编写代码了
#if defined(HAVE_LOG) && defined(HAVE_EXP)
double result = std::exp(std::log(x) * 0.5);
std::cout << "Computing sqrt of " << x << " to be " << result
<< " using log and exp" << std::endl;
#else
double result = x;
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
#endif
Step 8: Adding a Custom Command and Generated File
下面的示例演示的是,在构建过程中执行步骤:运行自定义命令来生成文件,并将文件添加到我们的源代码列表中
具体来说,我们编写了 MakeTable.cxx ,其生成可执行文件 MakeTable ,运行 MakeTable <filepath> 生成一个 Table.h 文件,之后我们 MathFunction 库的源代码要使用 Table.h
- 编写第一部分,运行自定义命令生成文件
声明:以下的 ./ 目录指的都是 build 目录
我们在 ./MathFuctions 下新建 MakeTable.cmake 文件,这个文件将来会在 ./MathFuctions/CMakeLists.txt 中被 include
# ./MathFuctions/MakeTable.cmake
add_executable(MakeTable MakeTable.cxx)
target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
message("CMAKE_CURRENT_BINARY_DIR: " ${CMAKE_CURRENT_BINARY_DIR})
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
# ./MathFuctions/CMakeLists.txt
include(MakeTable.cmake)
add_custom_command 用于定义一个自定义命令
add_custom_command(
OUTPUT <output-file> # 指定生成的文件
COMMAND <command> [args...] # 要执行的命令及其参数
DEPENDS <dependencies> # 依赖的文件或目标
WORKING_DIRECTORY <dir> # (可选)指定命令执行的工作目录
COMMENT <comment> # (可选)构建时显示的注释
VERBATIM # (可选)确保命令参数被正确转义
)
tips
这里 ${CMAKE_CURRENT_BINARY_DIR} 的值是 ./MathFuctions,因为我们是在 ./MathFuctions/CMakeLists.txt 中运行这些命令的 ( 见 include(MakeTable.cmake) ),同时可执行文件 MakeTable 也生成在这个目录,因此不要忘了添加 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
顶层cmake配置中 ${CMAKE_CURRENT_BINARY_DIR} 值是根目录也就是 build 目录,此外这里我们为了简便用的是相对路径,实际上是绝对路径
接下来修改 MathFunctions/CMakeLists.txt,为 SqrtLibrary 库添加新的文件
add_library(SqrtLibrary STATIC
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
# 添加 include 搜索路径
target_include_directories(SqrtLibrary PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
注意!!
这里 ${CMAKE_CURRENT_BINARY_DIR}/Table.h 被添加到了源文件列表,但是头文件一般不是以 target_include_directories 方式添加搜索路径就可以了吗?
实际上,添加搜索路径只是让编译器知道,头文件可以在这些路径搜索,但是在这个示例中,Table.h 是依赖构建系统动态生成的,而 CMake 需要显式知道哪些文件是构建目标的一部分,这就需要显示地将 ${CMAKE_CURRENT_BINARY_DIR}/Table.h 添加到 SqrtLibrary 的源文件列表中来实现
CMake 会将 Table.h 的生成规则(由 add_custom_command 定义)与 SqrtLibrary 的构建关联起来,从而确保在构建 SqrtLibrary 之前,Table.h 已经被正确生成
Step 9: Packaging an Installer
我们想将项目分发给他人,这个需求具体来说是,我们希望在各种平台上提供源代码和二进制发行版, CPack可以实现这个功能
- 向顶层
CMakeLists.txt添加 CPack 相关配置
# ./CMakeLists.txt
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
set(CPACK_GENERATOR "TGZ")
set(CPACK_SOURCE_GENERATOR "TGZ")
include(CPack)
-
include(InstallRequiredSystemLibraries)该模块会自动检测并包含当前系统所需的运行时库(例如,Windows 上的 Visual C++ 运行时库),确保生成的二进制程序在目标平台上可以正常运行 -
set() 设置 CPACK 变量,主要有许可证路径、版本号、二进制安装包格式、源代码包格式
-
include(CPack)启用 CPack 模块
设置源代码打包忽略文件
set(CPACK_SOURCE_IGNORE_FILES
"/build/[^/];/.git/;~$;${CPACK_SOURCE_IGNORE_FILES};/.cache"
)
- CPack 相关命令
# 根据 CPACK_GENERATOR 设置生成二进制程序包
# 或全写为 cpack --config CPackConfig.cmake
cpack
# 根据 CPACK_SOURCE_GENERATOR 打包源代码
cpack --config CPackSourceConfig.cmake
Step 10: Selecting Static or Shared Libraries
使用变量来控制 add_libraries 的行为,即控制如何构建没有显式指明类型 (STATIC SHARED MODULE OBJECT) 的库
使用 option 创建一个变量允许用户自行选择 ( 关于 option 见 Step2 :: Exercies2 )
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
设置静态库和共享库的输出目录,注意 MathFunciotns 目录的 ${PROJECT_BINARY_DIR} 值是 ./build/MathFunctions 而非 ./build
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
修改 MathFunctions/MathFunctions.h 以使用 dll 导出
// MathFunctions/MathFunctions.h
#if defined(_WIN32)
# if defined(EXPORTING_MYMATH)
# define DECLSPEC __declspec(dllexport)
# else
# define DECLSPEC __declspec(dllimport)
# endif
#else // non windows
# define DECLSPEC
#endif
namespace mathfunctions {
double DECLSPEC sqrt(double x); // 使用 DECLSPEC 导出符号
}
并且向其传递宏 EXPORTING_MYMATH
## MathFunctions/CMakeLists.txt
# define the symbol stating we are using the declspec(dllexport) when
# building on windows
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
在 MathFunctions/CMakeLists.txt 中添加,通过变量控制未显示指定库类型的 add_libraries 的行为,这里实际做的是设置库目标生成位置无关代码,因为默认的非位置无关代码只能生成静态库
## MathFunctions/CMakeLists.txt
# state that SqrtLibrary need PIC when the default is shared libraries
set_target_properties(SqrtLibrary PROPERTIES
POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
)
运行并编译
cmake .. -G "Unix Makefiles" -DBUILD_SHARED_LIBS=ON
cmake --build .
注意动态库和可执行文件没有在一个目录,直接运行会找不到 .dll 文件
Step 11: Adding Export Configuration
在 Step5: Installing and Testing 实现了安装库和头文件到本地目录的功能,在 Step 9: Packaging an Installer 实现了打包项目以发送给别人的功能。
现在有一个问题,他人拿到我们的项目包,解压并安装后,这些库是存放在他人自己的系统路径下的,而每个人的系统路径可能不一样,这样无法实现他人直接使用我们的项目。
- 修改
install命令,使其导出库的配置文件
原先的 install 包含了要安装的目标 TARGET 和位置 DESTINATION,现在还需要添加 EXPORT MathFunctionsTargets
# MathFunctions/CMakeLists.txt
install(TARGETS ${installable_libs}
EXPORT MathFunctionsTargets
DESTINATION lib)
这样会导出一个配置文件 MathFunctionsTargets.cmake
- 安装导出库的配置文件
在顶层添加
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
- 修改源目录路径
按照上述设置完后运行 cmake 会出现一个错误
PS D:\Codes\CMake_Tutorial\Step11\build> cmake ..
-- Configuring done (0.2s)
CMake Error in MathFunctions/CMakeLists.txt:
Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:
"D:/Codes/CMake_Tutorial/Step11/MathFunctions"
which is prefixed in the source directory.
-- Generating done (0.0s)
CMake Generate step failed. Build files cannot be regenerated correctly.
这个错误是因为 MathFunctions 的 INTERFACE_INCLUDE_DIRECTORIES 属性包含了一个源目录路径(MathFunctions),而 CMake 不允许在安装时将源目录路径暴露给其他项目。这是因为安装后的项目可能无法访问源目录路径。
查找 MathFunctions/CMakeLists.txt 关于 MathFunctions 的相关信息可以看到这里使用了源路径
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
将其修改为如下,这里使用了生成器表达式语法,BUILD_INTERFACE 和 INSTALL_INTERFACE 用于区分构建时和安装时
target_include_directories(MathFunctions
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
- 生成一个
MathFunctionsConfig.cmake文件,以便CMake的find_package()命令可以找到我们的项目
引入CMakePackageConfigHelpers 模块 ,其提供了 configure_package_config_file,使用上和 configure_file() 有细微不同
include(CMakePackageConfigHelpers)
# generate the config file that includes the exports
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
INSTALL_DESTINATION "lib/cmake/MathFunctions"
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
前两行是输入和输出文件路径,和 configure_file() 用法类似,${CMAKE_CURRENT_SOURCE_DIR} 指的是源代码根目录${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake" 是指定配置文件的安装路径。
剩下两个我也不知道有什么用,这是ai的解释:
NO_SET_AND_CHECK_MACRO禁用生成 set_and_check 宏。
NO_CHECK_REQUIRED_COMPONENTS_MACRO 禁用生成的宏中对组件依赖的检查。
要输入的 Config.cmake.in 模板文件如下。
当 CMake 处理一个 .cmake 文件时,CMAKE_CURRENT_LIST_DIR 会自动设置为该文件所在的目录路径。这在需要引用与当前脚本文件相关的资源(如其他 .cmake 文件或目标文件)时非常有用。当其他项目通过 find_package(MathFunctions) 加载 MathFunctionsConfig.cmake 时,CMAKE_CURRENT_LIST_DIR 会指向安装目录中的 lib/cmake/MathFunctions。
@PACKAGE_INIT@ 是 CMake 的一个占位符,它会在 configure_package_config_file 命令执行时被替换为一段初始化代码。这段代码通常用于设置一些与包相关的变量和环境。这段初始化代码确保生成的配置文件(如 MathFunctionsConfig.cmake)能够正确设置包的基本信息。它为后续的 find_package 和依赖管理提供必要的上下文。
// Config.cmake.in
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake") // 这是刚刚导出库的配置
使用 write_basic_package_version_file 写入一个文件,该文件由find_package()使用,记录所需包的版本和兼容性。
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
COMPATIBILITY AnyNewerVersion
)
安装上述两个文件
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake
DESTINATION lib/cmake/MathFunctions
)
此时,我们已经为我们的项目生成了一个可重定位的CMake配置,可以在项目安装或打包后使用。如果我们也希望我们的项目可以从构建目录中使用,我们只需在顶级CMakeLists.txt的底部添加以下内容
export(EXPORT MathFunctionsTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)
其中 MathFunctionsTargets 是我们在 MathFunctions\CMakeLists.txt 中的导出库的配置
之后,其他项目要使用我们的项目时,就可以通过如下方法
find_package(MathFunctions REQUIRED)
add_executable(MyExecutable main.cpp)
target_link_libraries(MyExecutable PRIVATE MathFunctions) # 链接我们的库
Step 12: Packaging Debug and Release
通过 CPack 打包一个同时包含 debug 和 release 版本的二进制文件包
设置 debug 版本的二进制文件后缀
# CMakeLists.txt
set(CMAKE_DEBUG_POSTFIX -debug)
添加 DEBUG_POSTFIX 属性
# CMakeLists.txt
add_executable(Tutorial tutorial.cxx)
set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
为 MathFunctions 库添加版本号,在 MathFunctions/CMakeLists.txt 中,设置 VERSION 和 SOVERSION 属性
# MathFunctions/CMakeLists.txt
set_property(TARGET MathFunctions PROPERTY VERSION "1.0.0")
set_property(TARGET MathFunctions PROPERTY SOVERSION "1")
创建如下所示目录
- Step12
- debug
- release
使用 CMAKE_BUILD_TYPE来设置配置类型
cd debug
cmake -DCMAKE_BUILD_TYPE=Debug .. -G "Unix Makefiles"
cmake --build .
cd ../release
cmake -DCMAKE_BUILD_TYPE=Release .. -G "Unix Makefiles"
cmake --build .
自定义配置文件将这两个构建打包成一个单一的发布。在 Step12 目录中,创建一个名为 MultiCPackConfig.cmake 的文件。在此文件中,首先包含由 cmake 可执行文件创建的默认配置文件。
接下来,使用 CPACK_INSTALL_CMAKE_PROJECTS 变量来指定要安装的项目。在这种情况下,我们想同时安装调试版和发布版。
# MultiCPackConfig.cmake
include("release/CPackConfig.cmake")
set(CPACK_INSTALL_CMAKE_PROJECTS
"debug;Tutorial;ALL;/"
"release;Tutorial;ALL;/"
)
在 Step12 目录中,运行 cpack,并使用 config 选项指定我们的自定义配置文件
cpack --config MultiCPackConfig.cmake
Vscode 中的 cmake 配置
基础
使用的工具链:cmake + clangd + clang llvm
工作流:cmake 配置 => 在 build 目录下生成 compile_commands.json => clangd 语言服务器读取 compile_commands.json实现语法提示 => cmake 生成
完整配置
/* clangd */
"clangd.path": "clangd",
"clangd.enableHover": true,
"clangd.onConfigChanged": "restart",
"clangd.arguments": [
// 在后台自动分析文件(基于complie_commands)
"--background-index",
// 全局补全(会自动补充头文件)
"--all-scopes-completion",
// Clang-Tidy 以提供「静态检查」
"--clang-tidy",
// compelie_commands.json 文件的目录位置(相对于工作区,由于 CMake 生成的该文件默认在 build 文件夹中,故设置为 build)
"--compile-commands-dir=build",
// 建议风格:打包(重载函数只会给出一个建议);反可以设置为detailed
"--completion-style=bundled",
// 启用 .clangd 配置文件
"--enable-config",
// 默认格式化风格: 谷歌开源项目代码指南(可用的有 LLVM, Google, Chromium, Mozilla, Webkit, Microsoft, GNU 等)
"--fallback-style=Google",
// 启用这项时,补全函数时,将会给参数提供占位符,键入后按 Tab 可以切换到下一占位符,乃至函数末
// 我选择禁用
"--function-arg-placeholders=false",
// 输入建议中,已包含头文件的项与还未包含头文件的项会以圆点加以区分
"--header-insertion-decorators",
// 允许自动补充头文件
"--header-insertion=iwyu",
// 让 Clangd 生成更详细的日志
"--log=verbose",
// pch优化的位置(memory 或 disk,选择memory会增加内存开销,但会提升性能)
"--pch-storage=memory",
// 输出的 JSON 文件更美观
"--pretty",
// 建议排序模型
"--ranking-model=heuristics",
// 同时开启的任务数量
"-j=12"
],
// 编译失败备选标志,例如添加-Ipath
"clangd.fallbackFlags": [],
"clangd.enableCodeCompletion": true,
"clangd.enable": true,
/** cmake **/
// 保存 cmake.sourceDirectory 或 CMakeLists.txt 内容时,不自动配置 CMake 项目目录
"cmake.configureOnEdit": false,
// 在 CMake 项目目录打开时自动对其进行配置
"cmake.configureOnOpen": false,
// 成功配置后,将 compile_commands.json 复制到此位置
"cmake.copyCompileCommands": "", // clangd 已设置 --compile-commands-dir=build,所以无需导出 compile_commands.json
"cmake.debugConfig": {
"type": "lldb"
},
"cmake.cmakePath": "cmake",
"cmake.outputLogEncoding": "65001", // utf-8 编码,如果你使用 GBK 则需要修改
"cmake.generator": "Unix Makefiles", // 可以换成 "Ninja",实际这里就是 -G 参数后的内容,你可以用 cmake -G 查看可选的生成器
关键设置
"clangd.path": "clangd",
"clangd.arguments": [
// compelie_commands.json 文件的目录位置(相对于工作区,由于 CMake 生成的该文件默认在 build 文件夹中,故设置为 build)
"--compile-commands-dir=build",
]
// 保存 cmake.sourceDirectory 或 CMakeLists.txt 内容时,不自动配置 CMake 项目目录
"cmake.configureOnEdit": false,
"cmake.cmakePath": "cmake",
// 在 CMake 项目目录打开时自动对其进行配置
"cmake.configureOnOpen": false,
"cmake.generator": "Unix Makefiles", // 可以换成 "Ninja",实际这里就是 -G 参数后的内容,你可以用 cmake -G 查看可选的生成器
进阶 - 配置 CMakePresets.json 和 CMakeUserPresets.json
CMakePresets.json 和 CMakeUserPresets.json 是 CMake 3.19 引入的一种配置文件,用于简化和标准化 CMake 构建配置的管理。它们允许开发者定义和共享构建配置、工具链、变量等信息。
CMakePresets.json 定义了整个项目的配置,放置在根 CMakeLists.txt 文件同级的目录下,一般作为团队共享配置上传到版本控制软件,下面以一个例子来说明如何配置
{
"version": 2,
"configurePresets": [
{
"name": "default",
"hidden": false,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "vcpkg",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}
]
}
configurePresets: 定义配置预设。name: 预设名称。generator: 指定生成器(如 Ninja、Unix Makefiles)。binaryDir: 构建目录。cacheVariables: 设置 CMake 缓存变量。
也就是说定义了一些配置的元信息,以及一组 configurePresets,在启动cmake时可以选择一个预设配置,使用以下命令进行配置:cmake .. --preset <preset_name> ( 假设当前在 build 目录,CMakeLists.txt 在 .. 目录 )
cmake .. --preset default
cmake .. --preset vcpkg
需要注意的是,vscode 提供的 cmake 插件在我们没有预设配置时,会使用很多有用的配置项,如选择编译器(工具包),导出 compile_command.json 文件等,但是如果你使用自己的 configurePresets.json 预设配置,就需要设置好这些,例如default 中 cacheVariables 项中设置了这些,否则如前所述,没有 compile_command.json 会导致 clangd 无法引用的某些解析头文件,此外编译器的不同可能会带来一些编译错误,尤其是对 MSVC 来说
CMakeUserPresets.json 继承自一个 CMakePresets.json 中的配置,并可以添加新的配置内容,例如下面
{
"version": 3,
"configurePresets": [
{
"name": "custom-debug",
"inherits": "default",
"hidden": false,
"cacheVariables": {
"MY_CUSTOM_OPTION": "ON"
}
}
]
}
inherits: 继承CMakePresets.json中的预设,这个例子中继承的是defaultcacheVariables: 添加或覆盖变量hidden: false: 该预设会显示在cmake --list-presets的输出中,用户可以轻松查看和选择

912

被折叠的 条评论
为什么被折叠?



