Cmake·

[#7][Installing and Testing][A菌严肃的CMake手记]

美式A菌

美式A菌

48 3

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
# 设定库的名字和源文件
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: 执行测试

所属系列

从当前文章继续阅读它所在合集中的前后内容。

相关文章

优先推荐同专题、同标签和同作者内容,补足热门文章。

评论 3

登录 后参与评论

评论 3

美式A菌
美式A菌1月14日 10:36

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

美式A菌
美式A菌1月14日 10:44

去っていったアンタなんかは バイバイバイ~~~!!!!!!!!!!!!!!

美式A菌
美式A菌1月14日 11:49

CMAKE在我的認識中非常難學。 可我偏要學,只爲了説服自己,我可以。