学习使用一个工具最好的办法是从它的官方文档入手。
这篇CMake教程搭配的CMake版本是3.25,参考的文档地址:https://cmake.org/cmake/help/v3.25/guide/tutorial/index.html 。
在官网的教程完成以后,就是一日又一日地补充。
第1步 1️⃣从最基本的开始
练习 1、构建一个基本的工程
需要用到的命令/宏
cmake_minimum_required() #申明项目使用的最低CMake版本
project() #为项目取一个名称
add_executable() #将源文件编译成可执行文件
练习 2、确定C++标准
需要用到的命令/宏
CMAKE_CXX_STANDARD #与set搭配,设置项目需要的C++标准
CMAKE_CXX_STANDARD_REQUIRED #与set搭配,设置为True表示必须要在机器中找到该C++标准
set() #“设置”
练习 3、加入版本号和已经配置好的头文件
<PROJECT-NAME>_VERSION_MAJOR #大版本变量
<PROJECT-NAME>_VERSION_MINOR #小版本变量
configure_file() #从包含有define宏命令的.h.in文件中生成.h文件,具体如后图所示
target_include_directories() #指定目标所包含的头文件路径
# target_include_directories()与include_dierctories()都是将指定目录
# 添加到编译器的头文件搜索路径之下
# 区别在于:include_directories()使当前CMakeLists.txt中所有目标都将具有此头文件搜索路径
# target_include_directories()指定某个目标包含头某个文件搜索搜索路径
一级CMakeLists.txt文件:
# TODO 8: Use configure_file to configure and copy TutorialConfig.h.in to
# TutorialConfig.h
configure_file(TutorialConfig.h.in TutorialConfig.h)
TutorialConfig.h.in文件:
// the configured options and settings for Tutorial
// TODO 10: Define Tutorial_VERSION_MAJOR and Tutorial_VERSION_MINOR
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
在build文件夹下构建以后,会出现TutorialConfig.h的文件,包含了版本信息,并且在tutorial.cxx中可以使用。
// the configured options and settings for Tutorial
// TODO 10: Define Tutorial_VERSION_MAJOR and Tutorial_VERSION_MINOR
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
第2步 2️⃣加入一个库
练习 1、创建一个库
需要用到的命令/宏
add_library() #使用指定源文件向项目中添加目标库,类似与add_executable()
add_subdirectory() #在此CMake项目中添加子目录(子目录中也有CMakeLists.txt文件)
target_include_directory() #将指定的头文件目录加入到指定目标的编译过程中(PUBLIC等)
target_link_libraries() #将指定目标链接到指定的库(PUBLIC等)
PROJECT_SOURCE_DIR #CMAKE中的一个内置变量,实际上就是项目的路径,也就是
#“此CMakeLists.txt”文件的路径
在MathFunctions/CMakeLists.txt中,使用add_library指令将mysqrt.cxx文件(默认)编译成静态库
# TODO 1: Add a library called MathFunctions
# Hint: You will need the add_library command
add_library(MathFunctions mysqrt.cxx)
然后,我们一级CMakeLists.txt中,使用add_subdirectory将MathFunctions文件夹添加到项目中(在构建以后,build文件夹下也有了一个MathFunctions的子文件夹)
练习 2、让我们的库成为可选项
对于我们这个小项目来说,这可能是无关紧要的,但是对于更大的项目来说,这是一个很常见的做法。
需要用到的命令/宏:
if()
list()
option()
cmakedefine
首先,在一级CMakeLists.txt中使用option()创建一个变量:USE_MATH
# TODO 7: Create a variable USE_MYMATH using option and set default to ON
option(USE_MATH "Use tutorial provided math implementation" ON)
# 中间引号的文字是提醒开发者这一选项的意思,选项默认是ON
然后,我们就需要使构建和链接MathFunctions库变得是可选的
# 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.
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions) #将库名(MathFunctions)追加到变量EXTRA_LIBS中
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")#将库路径追加到变量EXTRA_INCLUDES中
endif()
# 注意:EXTRA_LIBS和 EXTRA_INCLUDES并不是CMake自带的变量
当USE_MATH为ON,就会生成一个列表加入到我们的项目中,当USE_MATH为OFF的时候列表就会保持为空。通过这种方式来有选择地编译库。
现在,我们有了两份列表,我们需要用它们更新target_link_libraries()和target_link_directories()
# TODO 9: Use EXTRA_LIBS instead of the MathFunctions specific values
# in target_link_libraries.
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
# TODO 3: Use target_link_libraries to link the library to our executable
#target_link_libraries(Tutorial PUBLIC MathFunctions)
# TODO 4: Add MathFunctions to Tutorial's target_include_directories()
# Hint: ${PROJECT_SOURCE_DIR} is a path to the project source. AKA This folder!
# target_include_directories(Tutorial PUBLIC
# "${PROJECT_SOURCE_DIR}"
# "${PROJECT_SOURCE_DIR}/MathFunctions")
# TODO 10: Use EXTRA_INCLUDES instead of the MathFunctions specific values
# in target_include_directories.
target_include_directories(Tutorial PUBLIC
"${PROJECT_SOURCE_DIR}"
${EXTRA_INCLUDES})
我们需要修改tutorial.cxx文件,如果定义了USE_MYMATH,就包含头文件MathFunctions.h
// TODO 11: Only include MathFunctions if USE_MYMATH is defined
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif
同时,我们用USE_MYMATH来控制使用哪一个计算平方根的函数
// TODO 12: Use mysqrt if USE_MYMATH is defined and sqrt otherwise
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif
因为源代码中需要 USE_MYMATH ,我们把它加入到 TutorialConfig.h.in 中
// TODO 13: use cmakedefine to define USE_MYMATH
#cmakedefine USE_MYMATH
温馨提示:在改变USE_MYMATH为 ON 或 OFF ,测试代码的时候,一定要删除build下的文件再CMake。不然?嘿嘿,你试试呗。
第3步 3️⃣为一个库加入使用要求
练习 1、为一个库加入使用要求
在这一练习中,我们将使用现代CMake的方法重构上一步的代码
需要用到的命令/宏
CMAKE_CURRENT_SOURCE_DIR # 当前正在处理的原目录的路径,也就是当前CMakeLists.txt的路径
我们需要在MathFunctions/CMakeLists.txt文件中加入target_include_directories()指令,属性为INTERFACE
# TODO 1: 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})
简单来说,INTERFACE 此CMakeLists.txt的路径,以为着任何使用此库(MathFunctions)生成目标(库或可执行文件)的时候,头文件的搜索路径将包含${CMAKE_CURRENT_SOURCE_DIR}所表示的路径
然后去掉list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions"),原因就是上面所说
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
#list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()
接着去掉${EXTRA_INCLUDES}
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
#${EXTRA_INCLUDES}
)
Tips:"${PROJECT_BINARY_DIR}"的引号加和不加都不影响最终的编译
总结:在 CMake 中,INTERFACE 是用于指定目标属性的一种关键字。当在 target_include_directories 命令中使用 INTERFACE 关键字时,它表示这些包含目录是用于特定目标的接口。
接口目标是一种特殊类型的目标,它不会生成任何实际的构建输出,而是用于将属性传递给依赖于它的其他目标。通过为接口目标指定包含目录,可以确保这些包含目录也被传递给依赖于该接口目标的其他目标。
当一个目标依赖于一个具有接口属性的目标时,它会继承该目标的接口属性。这意味着依赖目标将自动获得与接口目标相同的包含目录设置。
具体来说,在上面的示例中,我们创建了一个库或可执行文件的目标 my_library 或 my_executable。然后,我们使用 target_include_directories 命令为 my_library 指定了包含目录。通过指定 INTERFACE 关键字,我们将这些包含目录设置为接口属性。
此时,如果其他目标依赖于 my_library,它们将继承 my_library 的接口属性,包括包含目录设置。这样,其他目标就能够在编译过程中找到所需的头文件。
归根结底,INTERFACE 关键字用于将包含目录(以及其他属性)指定为接口属性,以便在依赖于该接口目标的其他目标中继承这些属性。这样可以方便地管理和共享构建设置,确保各个目标之间的一致性。
第4步 4️⃣加入生成器表达式
练习 1、用接口库设置C++标准
goal:加入一个接口库确定需要的C++标准
需要用到的命令/宏
add_library()
target_compile_features()
target_link_libraries()
在开始之前,要移除两处set()指令
#set(CMAKE_CXX_STANDARD 11)
#set(CMAKE_CXX_STANDARD_REQUIRED True)
我们需要创建一个接口库,tutorial_compiler_flags,并且使用target_compile_features()加入编译器特性cxx_std_11。
# 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)
最后,我们需要链接我们的可执行目标和我们的MathFunctions库到我们新的库(tutorial_compiler_flags)。
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS} tutorial_compiler_flags)
target_link_libraries(MathFunctions tutorial_compiler_flags)
至此,我们的代码还是用C++ 11构建。但是,使用这种方法能够让我们确定什么样的目标需要什么样的要求。
练习 2、在生成器表达式中添加编译器警告标志
生成器表达式的一个常见用法是有条件地添加编译器标志,例如用于语言级别或警告的标志。一个很好的模式是将此信息与允许传播此信息的INTERFACE目标关联。
goal:生成时添加编译器警告标志,但不针对已安装的版本。
需要用到的指令/宏
cmake_minimum_required()
set()
target_link_options()
首先需要将CMake支持的最低版本调整为3.15
# TODO 4: Update the minimum required version to 3.15
cmake_minimum_required(VERSION 3.15)
接着,我们需要确定我们的系统用于构建的编译器是哪一个,因为警告标志因为编译器的不同而不同。需要使用的编译器表达式:COMPILE_LANG_AND_ID.我们把结果设置到gcc_like_cxx和msvc_cxx两个变量中。
# 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>")
然后,我们要为我们的项目加入我们想要的编译器警告标志。使用我们前面定义的变量gcc_like_cxx和msvc_cxx,当它们为真的时候,我们可以用另一个生成器表达式分别部署标志。我们需要用target_compile_options()将这些标志应用到我们的接口库上。
# TODO 6: Add warning flag compile options to the interface library
# tutorial_compiler_flags.
# * For gcc_like_cxx, add flags -Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused
# * For msvc_cxx, add flags -W3
# Hint: Use target_compile_options()
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:-Wall;-Wetra;-Wshadow;-Wformat=2;-Wunused>"
"$<${msvc_cxx}:-W3>")
此target_compile_options命令为tutorial_compiler_flags目标添加编译选项,INTERFACE关键字意味着这些选项会被传递给链接到此目标的其他目标。
$<$<gcc_like_cxx>:-Wall;-Wetra;-Wshadow;-Wformat=2;-Wunused>的含义是: 如果编译的语言是C++(CXX),并且编译器ID是ARMClang、AppleClang、Clang、GNU或LCC中的任意一个,那么就添加-Wall -Wextra -Wshadow -Wformat=2 -Wunused这些编译选项。
● -Wall:开启所有警告信息。
● -Wextra:开启额外的警告信息。
● -Wshadow:警告任何变量声明遮蔽了另一个作用域中的变量。
● -Wformat=2:检查printf、scanf等格式字符串的错误。
● -Wunused:警告未使用的变量。
$<$<msvc_cxx>:-W3>的含义是:如果编译的语言是C++并且编译器是MSVC,那么就添加-W3这个编译选项。
● -W3:在MSVC中,这代表开启等级3的警告,等级3包含了大多数警告。相当于Linux下的-Wall
同样的,上面的指令可以写成:
target_compile_options(tutorial_compiler_flags INTERFACE
"$<$<COMPILE_LANG_ANG_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>:-Wall;-Wetra;-Wshadow;-Wformat=2;-Wunused>"
"$<$<COMPILE_LANG_AND_ID:CXX,MSVC>:-W3>")
最后,我们只想这些警告标志只在构建的时候使用。我们项目的消费者(其他使用此目标的目标)不应该继承这些标志。为了达到这一点,我们使用BUILD_INTERFACE将标志包装在生成器表达式中。(BUILD_INTERFACE的含义: 当目标作为其他目标的依赖时,这些选项才会被应用 )
# 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>>")
第5步 5️⃣安装与测试
练习 1、安装规则
在更多的时候,构建一个可执行程序是不够的,我们还要它是可安装的,在CMake中,我们可以用install指定我们的安装规则。
需要用到的命令/宏
install()
我们的这个项目的安装规则十分简单:
● 对于MathFunctions,我们想把库文件和头文件分别安装到lib文件夹和include文件夹中。
● 对于Tutorial,我们想把可执行程序和配置性头文件分别安装到bin和include文件夹中。
首先创建一个变量installable_libs存储MathFunctions库和tutorial_compiler_flags库
利用install()命令和TARGET、DESTINANTION变量将库文件安装到lib中
# TODO 1: Create a variable called installable_libs that is a list of all
# libraries we want to install (e.g. MathFunctions and tutorial_compiler_flags)
# Then install the installable libraries to the lib folder.
# Hint: Use the TARGETS and DESTINATION parameters
set(installable_libs MathFunctions tutorial_compiler_flags)
install(TARGET ${installable_libs} DESTINATION lib)
然后利用install命令和FILES、DESTINATION变量将头文件安装到include中
# TODO 2: Install the library headers to the include folder.
# Hint: Use the FILES and DESTINATION parameters
install(TARGET MathFunctions.h DESTINATION include)
同理,将Tutorial安装到bin中,将配置性头文件TutorialConfig.h安装到include中
# TODO 3: Install Tutorial in the bin directory
# Hint: Use the TARGETS and DESTINATION parameters
install(TARGETS Tutorial DESTINATION bin)
# TODO 4: Install Tutorial.h to the include directory
# Hint: Use the FILES and DESTINATION parameters
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
这样,我们就完成了基本的本地(local)安装。
所以说,我们安装到的文件夹有/usr/local/bin(可执行程序)、/usr/local/include(头文件)、和/usr/local/lib(库文件)
这就更好地理解了从github上面下载一些库执行cmake、make和sudo make install背后的含义了。
练习2、测试支持
goal:使用CTest为我们的可执行文件创建一个单元支持
需要用到的命令/宏:
enable_testing()
add_test()
function()
set_tests_properties()
ctest
首先我们要打开测试功能,然后使用add_test()命令为我们的项目加入测试。后面我们将加入3个简单的测试。
打开测试功能
# TODO 5: Enable testing
enable_testing()
在打开测试功能以后,我们将测试应用是否正常工作。
首先,我们使用add_test()创建一个带参数25运行Tutorial可执行程序的测试案例。这个测试并不是为了检查可执行程序计算得对不对,而是检查它有没有正常运行。这是CTest测试的基本模式。
# TODO 6: Add a test called Runs which runs the following command:
# $ Tutorial 25
add_test(NAME Runs COMMAND Tutorial 25)
接着,使用PASS_REGULAR_EXPRESSION测试属性来验证测试的输出是否包含某些字符串。在这种情况下,验证在提供了不正确数量的参数时是否打印了使用消息。
再接着,我们来测试计算结果是否正确。
# TODO 8: Add a test which runs the following command:
# $ Tutorial 4
# Make sure the result is correct.
# Hint: Use the PASS_REGULAR_EXPRESSION property with "4 is 2"
add_test(NAME StandardUse COMMAND Tutorial 4)
set_tests_properties(StandardUse
PROPERTIES PASS_REGULAR_EXPRESSION "4 is 2")
就这一个测试是不够的。为了更简单地添加更多的测试,我们创建一个叫do_test()的函数,然后批量执行
# TODO 9: Add more tests. Create a function called do_test to avoid copy +
# paste. Test the following values: 4, 9, 5, 7, 25, -25 and 0.00001.
function(do_test target arg result)
add_test(NAME test_${arg} COMMAND ${target} ${arg})
set_tests_properties(test_${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endfunction()
#test
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
do_test(Tutorial -25 "-25 is (-nan|nan|0)")
(NAME,而不是Name)
标签:教程,CMake,target,MathFunctions,add,include,TODO,Tutorial From: https://www.cnblogs.com/qizhengxun/p/18058896