
Intro
复习
$<condition:true_string>$<COMPILE_LANG_AND_ID:language,compiler_ids>
今天我们学习,如何通过CMake和make来将你的程式(或者lib)安装到系统中,成为系统的一部分。
直接使用上篇文章的代码或者复制 Code这份代码准备开始。
开始
首先我们要简单的了解一下Linux系统的目录。
Linux系统中有一个目录的路径为/usr/local- 它的主要目的是将
系统管理员手动安装的软件与系统包管理器安装的软件``区分开来。这样可以避免系统升级时覆盖或删除本地安装的软件`。 - local 目录下通常包含以下子目录:
- bin/:存放本地安装的可执行文件。例如,nvim 就是一个可执行文件。
- etc/:存放本地安装的软件的配置文件。
- games/:存放本地安装的游戏相关文件。
- include/:存放本地安装的软件的头文件。
- lib/:存放本地安装的软件库文件。
- man/:存放本地安装的软件的手册页,帮助文档。
- sbin/:存放本地安装的系统管理程序和工具。
- share/:存放本地安装的软件的共享数据。子目录如 applications/、ca-certificates/、fonts/、icons/、locale/、man/、nvim/ 和 zsh/ 分别存放不同类型的共享数据。
- src/:存放本地安装的软件的源代码。

Linux系统中有一个环境变量叫做PATH$PATH是一个环境变量,它定义了操作系统在寻找可执行文件时的目录列表。当你在终端中输入一个命令时,系统会按照$PATH中定义的目录顺序搜索该命令对应的可执行文件。

在图中我们可以看到$PATH中存有/usr/local/bin和/usr/local/sbin,所以当我们将二进制程序放到这个目录下,就可以类似使用ls,cd,rm这种方式去使用我们自己的程式了。
再看我们的代码结构
.
├── CMakeLists.txt
├── MathFunctions 整体的lib
│ ├── CMakeLists.txt
│ ├── MathFunctions.cxx
│ ├── MathFunctions.h 头文件
│ ├── mysqrt.cxx
│ └── mysqrt.h 头文件
├── TutorialConfig.h.in 头文件
└── tutorial.cxx 二进制文件
聪明的同学已经发现了:目录结构中的每一份文件都可以对应到
/usr/local目录中的文件夹。
我们今天要学习的几个指令,就是帮助我们快速的将程式编译安装到系统目录中去。
- MathFunctions/CMakeLists.txt
- install
- TARGET
- FILES
- install
# 设定库的名字和源文件
add_library(MathFunctions MathFunctions.cxx)
# 将当前目录添加到库的包含目录
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if (USE_MYMATH)
# 向程式添加预处理器宏 名为USE_MYMATH
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
# 添加库SqrtLibrary
add_library(SqrtLibrary STATIC mysqrt.cxx)
# 链接库tutorial_compiler_flags到SqrtLibrary
target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
# 链接库SqrtLibrary到MathFunctions
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
# 如果USE_MYMATH为OFF以上链接不会被执行
# 这里重新链接库tutorial_compiler_flags到MathFunctions 传递编译器标志和其他信息
target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
# 设置一个变量名为installable_libs 包含MathFunctions并且传递tutorial_compiler_flags
set(installable_libs MathFunctions tutorial_compiler_flags)
# TARGET 是用来检查某个目标(比如库或可执行文件)是否已经定义的。
# 如果USE_MYMATH为ON则SqrtLibrary一定被定义了
if(TARGET SqrtLibrary)
# 将SqrtLibrary添加到installable_libs
list(APPEND installable_libs SqrtLibrary)
endif()
# 安装installable_libs里的文件
# DESTINATION指定安装的目录
# lib为 /usr/local/lib
install(TARGETS ${installable_libs} DESTINATION lib)
# 安装MathFunctions.h到include目录
# include为 /usr/local/include
install(FILES MathFunctions.h DESTINATION include)
# TARGET 用于安装构建目标,如库和可执行文件。 语法:install(TARGETS target1 target2 ... DESTINATION <dir>)
# 用于安装单个文件或一组文件。 语法:install(FILES file1 file2 ... DESTINATION <dir>)
# `MathFunctions` `tutorial_compiler_flags`都为程式定义的 所以用TARGETS
# `MathFunctions.h`为真实文件 所以用FILES
- 根目录\CMakeLists.txt
# 设置 CMake 的最低版本要求
cmake_minimum_required(VERSION 3.15)
# 定义项目名称 版本
project(Tutorial VERSION 1.0)
# 定义一个接口库名为 tutorial_compiler_flags
add_library(tutorial_compiler_flags INTERFACE)
# 设置接口库的编译特性
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
# 两种编译器,使用表达式,
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
# 表达式判断到底是 gcc 还是 msvc(通过 COMPILE_LANG_AND_ID判断)
# 然后指定编译选项
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)
# 动态生成 TutorialConfig.h
configure_file(TutorialConfig.h.in TutorialConfig.h)
# 添加子目录
add_subdirectory(MathFunctions)
# 添加可执行文件
add_executable(Tutorial tutorial.cxx)
# 链接 MathFunctions 库到 Tutorial并且链接 tutorial_compiler_flags传递编译器标志和其他信息
target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
# 指定编译给定目标时要使用的包含目录。
# 命名的 <target> 必须由 add_executable() 或 add_library() 等命令创建,并且不能是 ALIAS 目标。
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
# 安装 Tutorial 可执行文件到 /usr/local/bin 目录
install(TARGETS Tutorial DESTINATION bin)
# 安装 TutorialConfig.h 到 /usr/local/include 目录
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include)
我们来测试一下


这里出现了错误Permission denied意为权限不够,因为我们要向系统中添加文件,又没有使用sudo或者没有给当前用户分配/usr/local目录的权限。

我们使用sudo来执行安装,可以看到日志信息,向/usr/local/目录中安装了一些文件,最后直接执行Tutorial来进行测试,不要像 A菌一样写错命令名称哦。
代码测试功能
- 根目录\CMakeLists.txt
# 设置 CMake 的最低版本要求
cmake_minimum_required(VERSION 3.15)
# 定义项目名称 版本
project(Tutorial VERSION 1.0)
# 定义一个接口库名为 tutorial_compiler_flags
add_library(tutorial_compiler_flags INTERFACE)
# 设置接口库的编译特性
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
# 两种编译器,使用表达式,
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
# 表达式判断到底是 gcc 还是 msvc(通过 COMPILE_LANG_AND_ID判断)
# 然后指定编译选项
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)
# 动态生成 TutorialConfig.h
configure_file(TutorialConfig.h.in TutorialConfig.h)
# 添加子目录
add_subdirectory(MathFunctions)
# 添加可执行文件
add_executable(Tutorial tutorial.cxx)
# 链接 MathFunctions 库到 Tutorial并且链接 tutorial_compiler_flags传递编译器标志和其他信息
target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
# 指定编译给定目标时要使用的包含目录。
# 命名的 <target> 必须由 add_executable() 或 add_library() 等命令创建,并且不能是 ALIAS 目标。
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
# 安装 Tutorial 可执行文件到 /usr/local/bin 目录
install(TARGETS Tutorial DESTINATION bin)
# 安装 TutorialConfig.h 到 /usr/local/include 目录
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include)
# 开启测试
enable_testing()
# 添加一个名为 <name> 的测试。测试名称可以包含任意字符,必要时以带引号的参数或括号参数表示。
# COMMAND 指定测试命令行。
# add_test(NAME <name> COMMAND <command> [<arg>...]
# [CONFIGURATIONS <config>...]
# [WORKING_DIRECTORY <dir>]
# [COMMAND_EXPAND_LISTS])
# 添加一个名为 Usage 的测试,调用命令为 Tutorial,也就是我们的二进制打包出来的名字
# 这里arg为空,所以对应 std::cout << "Usage: " << argv[0] << " number" << std::endl; 这部分代码
add_test(NAME Usage COMMAND Tutorial)
# 设置测试属性,PASS_REGULAR_EXPRESSION 指定测试输出中必须包含的文本,文本为 Usage:.*number
set_tests_properties(Usage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")
# 添加一个名为 StandardUse 的测试,调用命令为 Tutorial
# 输入参数为 4
add_test(NAME StandardUse COMMAND Tutorial 4)
# 设置测试属性,PASS_REGULAR_EXPRESSION 指定测试输出中必须包含的文本 文本为 4 is 2
set_tests_properties(StandardUse PROPERTIES PASS_REGULAR_EXPRESSION "4 is 2")
# 定义一个函数
# 函数名称为 do_test
# 参数为 target arg result
function(do_test target arg result)
# 添加一个名为 Comp${arg} 的测试,调用命令为 target, 参数为 arg
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
# 设置测试属性,PASS_REGULAR_EXPRESSION 指定测试输出中必须包含的文本 文本为 result
set_tests_properties(Comp${arg} PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endfunction()
# 调用 do_test 函数 传入 Tutorial[target] 4[arg] "4 is 2"[result]
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")
执行测试,查看测试结果


总结

install用来安装文件/源码/可执行文件/包信息或其他内容到DESTINATION指定目录
enable_testing: 开启测试add_test(NAME <name> COMMAND <command> [<arg>...]: 添加测试名称指定测试命令和输入参数set_tests_properties: 设置测试属性,PASS_REGULAR_EXPRESSION指定测试输出中必须包含的文本,类似assert
定义函数
# 定义一个函数
# 函数名称为 do_test
# 参数为 target arg result
function(do_test target arg result)
# 添加一个名为 Comp${arg} 的测试,调用命令为 target, 参数为 arg
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
# 设置测试属性,PASS_REGULAR_EXPRESSION 指定测试输出中必须包含的文本 文本为 result
set_tests_properties(Comp${arg} PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endfunction()
函数调用
do_test(Tutorial 4 "4 is 2")
ctest -C Debug -VV: 执行测试

這一篇寫的真有夠詳細的額額額額額額の