CMake使用学习记录

本文详细介绍了CMake的使用,从创建基本的C++项目,设置C++标准,管理版本号,到创建静态库,使用预处理指令和条件判断,以及通过接口库管理编译选项。此外,还涵盖了使用生成器表达式适应不同编译器和配置,以及如何在CMake中进行安装和测试的步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考文档: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")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值