参考文档:https://cmake.org/cmake/help/latest/guide/tutorial/index.html#guide:CMake%20Tutorial
Step1 基础
1.1 最简单的版本(3行)
CMakeLists.txt:
# TODO 1: Set the minimum required version of CMake to be 3.10
cmake_minimum_required(VERSION 3.10)
# TODO 2: Create a project named Tutorial
project(Tutorial)
# TODO 3: Add an executable called Tutorial to the project
# Hint: Be sure to specify the source file as tutorial.cxx
add_executable(Tutorial tutorial.cxx)
然后执行如下命令,最终生成可执行文件:
#创建build目录
mkdir Step1_build
cd Step1_build
#run cmake to configure the project and generate a native build system
cmake ../Step1
#call that build system to actually compile/link the project:
cmake --build .
1.2 指定C++标准
设置为C++ 11, 这两个set要放在add_executable之前。
CMakeLists.txt(部分):
# TODO 6: Set the variable CMAKE_CXX_STANDARD to 11
# and the variable CMAKE_CXX_STANDARD_REQUIRED to True
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
1.3 设置version number
同时,根据文件模板和变量生成.h文件或配置文件, 并指定头文件目录。
CMakeLists.txt(部分):
# TODO 7: Set the project version number as 1.0 in the above project command
project(Tutorial VERSION 1.0)
# TODO 8: Use configure_file to configure and copy TutorialConfig.h.in to
# TutorialConfig.h
configure_file(TutorialConfig.h.in TutorialConfig.h)
# TODO 9: Use target_include_directories to include ${PROJECT_BINARY_DIR}
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
TutorialConfig.h.in
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
文件中的@Tutorial_VERSION_MAJOR@这种变量会被cmake根据cmake中我们定义的变量替换,project(Tutorial VERSION 1.0)其实就是定义了 Tutorial_VERSION_MAJOR为1, Tutorial_VERSION_MINOR为0。
Step2 生成静态库.a并使用该库
2.1 静态库和子目录
子目录MathFunctions/CMakeLists.txt:
# TODO 1: Add a library called MathFunctions
add_library(MathFunctions mysqrt.cxx)
上层CMakeLists.txt,增加add_subdirectory,并在target_include_directories中增加子目录作为头文件目录:
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)
# TODO 2: Use add_subdirectory() to add MathFunctions to this project
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
# TODO 3: Use target_link_libraries to link the library to our executable
target_link_libraries(Tutorial PUBLIC MathFunctions)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
2.2 使用预处理指令和if/else判断
CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# TODO 7: Create a variable USE_MYMATH using option and set default to ON
option(USE_MYMATH "Use tutorial provided match implementation" ON)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)
# TODO 8: Use list() and APPEND to create a list of optional libraries
# called EXTRA_LIBS and a list of optional include directories called
# EXTRA_INCLUDES. Add the MathFunctions library and source directory to
# the appropriate lists.
#
# Only call add_subdirectory and only add MathFunctions specific values
# to EXTRA_LIBS and EXTRA_INCLUDES if USE_MYMATH is true.
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()
# add the executable
add_executable(Tutorial tutorial.cxx)
# TODO 9: Use EXTRA_LIBS instead of the MathFunctions specific values
# in target_link_libraries.
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
# TODO 10: Use EXTRA_INCLUDES instead of the MathFunctions specific values
# in target_include_directories.
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}
)
C++代码中根据预编译指令走不同逻辑:
#include "TutorialConfig.h"
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif
...
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif
cmake生成C++头文件的模板文件TutorialConfig.h.in:
//cmakedefine命令:只有当CMakeLists.txt中的同名变量为真时才会在生成的头文件中定义
#cmakedefine USE_MYMATH
cmake命令:
#指定USE_MYMATH为OFF
cmake ../Step2 -DUSE_MYMATH=OFF
cmake --build .
#默认USE_MYMATH为ON
cmake ../Step2
cmake --build .
Step3 对Step2中include 子目录头文件的模式做改进
子目录MathFunctions/CMakeLists.txt:
# State that anybody linking to MathFunctions needs to include the
# current source directory, while MathFunctions itself doesn't.
# Hint: Use target_include_directories with the INTERFACE keyword
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
上层CMakeLists.txt,不需要再考虑头文件目录,去掉EXTRA_INCLUDES 。在大型项目中,使用这种方式,比Step2中的方式要方便很多。
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
#list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
# TODO 3: Remove use of EXTRA_INCLUDES
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
#${EXTRA_INCLUDES}
)
Step4 改进之前使用编译选项如C++11的方式
4.1 创建并使用interface library
interface library它没有源文件也不会生成库文件,通常会有一些属性设置在interface library上,让其他目标链接它。
可以创建一个interface library, 然后使用target_compile_features() 来增加编译选项 cxx_std_11。 后面在主目录或者子目录的target_link_libraries时使用该interface library,用来指定编译选项。
主目录CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(Tutorial VERSION 1.0)
# TODO 1: Replace the following code by:
# * Creating an interface library called tutorial_compiler_flags
# Hint: use add_library() with the INTERFACE signature
# * Add compiler feature cxx_std_11 to tutorial_compiler_flags
# Hint: Use target_compile_features()
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
option(USE_MYMATH "Use tutorial provided math implementation" ON)
configure_file(TutorialConfig.h.in TutorialConfig.h)
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()
add_executable(Tutorial tutorial.cxx)
# TODO 2: Link to tutorial_compiler_flags
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
子目录MathFunctions/CMakeLists.txt:
add_library(MathFunctions mysqrt.cxx)
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
# TODO 3: Link to tutorial_compiler_flags
target_link_libraries(MathFunctions tutorial_compiler_flags)
使用这种方式,就可以不用之前的set方式了。
#set(CMAKE_CXX_STANDARD 11)
#set(CMAKE_CXX_STANDARD_REQUIRED True)
用新的这种方式, 可以对不同的目标Targets使用不用的编译选项。 另外,we create a single source of truth in our interface library。
4.2 使用生成器表达式
cmake构建分为config和build阶段,config阶段解析CMakeLists.txt文件, build阶段生成。生成器表达式是CMake在生成时构造,用于生成特定于配置的构建输出。使用生成器表达式,我们不必显式地了解位置和名称。换句话说,生成器表达式用于引用仅在生成时已知,但在配置时未知或难于知晓的信息;对于文件名、文件位置和库文件后缀尤其如此。
生成器表达式在交叉编译时特别有用,一些可用的信息只有解析 CMakeLists.txt 之后,或在多配置 项目后获取,构建系统生成的所有项目可以有不同的配置,比如Debug和Release。
生成器表达式的格式为$<…>,生成器表达式可以嵌套。 生成器表达式分为:Bool生成器表达式、字符串值生成器表达式。
- COMPILE_LANG_AND_ID生成器表达式
$<COMPILE_LANG_AND_ID:language,compiler_ids>:当编译单元的语言与language匹配且CMake编译器id和compiler_ids中任意匹配则为1,否则为0 - 条件生成器表达式
$condition:true_string :当condition为真则返回true_string字符串,否则返回空字符串;
$IF:condition,true_string,false_string: 当condition为真则返回true_string字符串,否则返回false_string字符串;
cmake_minimum_required(VERSION 3.15)
project(Tutorial VERSION 1.0)
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
# TODO 5: Create helper variables to determine which compiler we are using:
# * Create a new variable gcc_like_cxx that is true if we are using CXX and
# any of the following compilers: ARMClang, AppleClang, Clang, GNU, LCC
# * Create a new variable msvc_cxx that is true if we are using CXX and MSVC
# Hint: Use set() and COMPILE_LANG_AND_ID
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
# TODO 7: With nested generator expressions, only use the flags for the
# build-tree
# Hint: Use BUILD_INTERFACE
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)
option(USE_MYMATH "Use tutorial provided math implementation" ON)
configure_file(TutorialConfig.h.in TutorialConfig.h)
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
endif()
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
Step5 Install和Test
5.1 在cmake中install 库、头文件、可执行文件
MathFunctions/CMakeLists.txt: (部分)
set(installable_libs MathFunctions tutorial_compiler_flags)
install(TARGETS ${installable_libs} DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
CMakeLists.txt: (部分)
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include
)
运行cmake:
[root@hecs-x-large-2-win-20201112134544 Step5_build]# cmake ../Step5
-- Configuring done
-- Generating done
-- Build files have been written to: /home/xxx/Download/cmake-3.26.3-tutorial-source/Step5_build
[root@hecs-x-large-2-win-20201112134544 Step5_build]# cmake --build .
[ 50%] Built target MathFunctions
[100%] Built target Tutorial
[root@hecs-x-large-2-win-20201112134544 Step5_build]# cmake --install .
-- Install configuration: ""
-- Up-to-date: /usr/local/lib/libMathFunctions.a
-- Up-to-date: /usr/local/include/MathFunctions.h
-- Up-to-date: /usr/local/bin/Tutorial
-- Installing: /usr/local/include/TutorialConfig.h
5.2 在cmake中添加自动测试
5.2.1 手工加测试用例
CMakeLists.txt: (部分)
# 启用测试
enable_testing()
# 添加一个测试用例,这里不检查结果,只是测试程序能够正常运行,没有segfault或者coredump
add_test(NAME Runs COMMAND Tutorial 25)
# 添加一个测试用例,测试输入错误时是否能够打屏输出Usage提示信息
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
# 添加一个测试用例,测试运行参数为4时候打屏的输出结果中有"4 is 2"
add_test(NAME StandardUse COMMAND Tutorial 4)
set_tests_properties(StandardUse
PROPERTIES PASS_REGULAR_EXPRESSION "4 is 2"
)
运行cmake输出:
[root@hecs-x-large-2-win-20201112134544 Step5_build]# cmake ../Step5 -DUSE_MYMATH=OFF
-- Configuring done
-- Generating done
-- Build files have been written to: /home/xxx/Download/cmake-3.26.3-tutorial-source/Step5_build
[root@hecs-x-large-2-win-20201112134544 Step5_build]# cmake --build .
Scanning dependencies of target Tutorial
[ 50%] Building CXX object CMakeFiles/Tutorial.dir/tutorial.cxx.o
[100%] Linking CXX executable Tutorial
[100%] Built target Tutorial
[root@hecs-x-large-2-win-20201112134544 Step5_build]# ctest -VV
UpdateCTestConfiguration from :/home/xxx/Download/cmake-3.26.3-tutorial-source/Step5_build/DartConfiguration.tcl
UpdateCTestConfiguration from :/home/xxx/Download/cmake-3.26.3-tutorial-source/Step5_build/DartConfiguration.tcl
Test project /home/wqf/Download/cmake-3.26.3-tutorial-source/Step5_build
Constructing a list of tests
Done constructing a list of tests
Checking test dependency graph...
Checking test dependency graph end
test 1
Start 1: Runs
1: Test command: /home/xxx/Download/cmake-3.26.3-tutorial-source/Step5_build/Tutorial "25"
1: Test timeout computed to be: 9.99988e+06
1: The square root of 25 is 5
1/3 Test #1: Runs ............................. Passed 0.00 sec
test 2
Start 2: Usage
2: Test command: /home/xxx/Download/cmake-3.26.3-tutorial-source/Step5_build/Tutorial
2: Test timeout computed to be: 9.99988e+06
2: /home/wqf/Download/cmake-3.26.3-tutorial-source/Step5_build/Tutorial Version 1.0
2: Usage: /home/xxx/Download/cmake-3.26.3-tutorial-source/Step5_build/Tutorial number
2/3 Test #2: Usage ............................ Passed 0.00 sec
test 3
Start 3: StandardUsage
3: Test command: /home/xxx/Download/cmake-3.26.3-tutorial-source/Step5_build/Tutorial "4"
3: Test timeout computed to be: 9.99988e+06
3: The square root of 4 is 2
3/3 Test #3: StandardUsage .................... Passed 0.00 sec
100% tests passed, 0 tests failed out of 3
Total Test time (real) = 0.01 sec
5.2.2 定义函数来加测试用例
CMakeLists.txt: (部分)
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")