CMake构建工具

编译框架 CMake

组织大型c++项目

样例

good
不错的例子

B站讲解视频,还不错

CMAKE_XXX_DIR等内置变量辨析

在掌握基本的cmake原理和用法的基础上,研究开源项目的cmake文件是如何编写的。

推荐的开源项目
常见的应用场景:

  • general usage
  • qt usage
  • cuda usage
  • python binding usage

更进一步掌握原理-阅读官方文档

构建(Build)

当需要编译的东西很多时,需要说明先编译什么,后编译什么

CMake一般使用流程

CMake提供cmake、ctest、cpack三个命令行工具分别负责构建、测试、打包。

  1. 编写CMakeLists.txt
    编译:确定编译目标需要的源文件
    链接:确定链接时需要依赖的额外的库
  2. 生成构建系统
    cmake -B build,会在项目的根目录创建build目录并在其中生成Makefile文件
  3. 执行构建
    cmake --build build
    or
    cd build && make && cd -
    执行完成后可以在build目录看到以及生成的可执行文件

还有更简单的:
mkdir build
cd build && cmake .. && make

CMake配合脚本一起使用

Win

Linux

命令行

cmake -G"Unix Makefiles" #指定生成的构建系统类型
    -D<var>=<value>   #设置cmake变量的值
    <path-to-source>  #源树的路径,其下必须包含一个CMakeLists.txt文件
    

核心语法

基础配置

add_executable()

include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>] #用于语句的复用.cmake文件
                      [NO_POLICY_SCOPE])
set(<variable> <value>... [PARENT_SCOPE]) #给变量设置值, ${}取值
string(REPLACE <match-string> <replace-string> <out-var> <input>...) #将<input>中所有出现的<match_string>替换为<replace_string>,并将结果存储在<out-var>中。若<match_string>在<input>中未出现,<out-var>的值将为<input>
$ENV{VAR} to read environment variable VAR,To test whether an environment variable is defined, use the signature if(DEFINED ENV{<name>})
set(ENV{变量名} 值) 设置环境变量
target_include_directories(myapp PUBLIC /path) #添加头文件搜索目录
target_link_libraries(myapp PUBLIC hellolib) #添加要链接的库
target_add_definitions(myapp PUBLIC MY_MACRO=1) #添加一个宏定义
target_compile_options(myapp PUBLIC -fopenmp) #添加编译器命令行选项
target_sources(myapp PUBLIC hello.cc other.cc) #添加要编译的源文件
也可以通过以下指令(不推荐使用),把选项加到所有接下来的目标去
include_directories(/path) #添加头文件搜索目录
link_directories(/path/) #添加库文件的搜索路径
add_definitions(-DMY_MACRO)  #添加一个宏定义
add_definitions(-DFOO=${xxx}) #将-D定义的标志添加到源文件中
add_compile(-fopenmp)    #添加编译器命令行选项
add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL]) #添加一个子目录并构建该子目录。source_dir可以是相对也可以是绝对,相对是相对当前目录的,是必选参数。
# 因为add_subdirectory增加的构建子目录,CMake构建工程会自动将该子目录添加到编译和链接的搜索目录中,以保证整个构建工程能满足依赖,这也是为什么使用add_subdirectory后不需要将子文件夹加入到头文件或库文件搜索目录也能搜索到子目录的头文件或库文件。

控制流

foreach(<loop_var> <items>)
  <commands>
endforeach() #<items>是一个item列表,由空格或分号分隔。

进阶

file(<COPY|INSTALL> <files>... DESTINATION <dir>
     [NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS]
     [FILE_PERMISSIONS <permissions>...]
     [DIRECTORY_PERMISSIONS <permissions>...]
     [FOLLOW_SYMLINK_CHAIN]
     [FILES_MATCHING]
     [[PATTERN <pattern> | REGEX <regex>]   #模式匹配
     [EXCLUDE] [PERMISSIONS <permissions>...]] [...])
file(GLOB <variable> [LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS] [<globbing-expressions>...])¶ #生成一个匹配全局表达式的文件列表,并将其存储到变量中
execute_process(COMMAND <cmd1> [<arguments>]
                [COMMAND <cmd2> [<arguments>]]...
                [WORKING_DIRECTORY <directory>]
                [TIMEOUT <seconds>]
                [RESULT_VARIABLE <variable>]
                [RESULTS_VARIABLE <variable>]
                [OUTPUT_VARIABLE <variable>]
                [ERROR_VARIABLE <variable>]
                [INPUT_FILE <file>]
                [OUTPUT_FILE <file>]
                [ERROR_FILE <file>]
                [OUTPUT_QUIET]
                [ERROR_QUIET]
                [COMMAND_ECHO <where>]
                [OUTPUT_STRIP_TRAILING_WHITESPACE]
                [ERROR_STRIP_TRAILING_WHITESPACE]
                [ENCODING <name>]
                [ECHO_OUTPUT_VARIABLE]
                [ECHO_ERROR_VARIABLE]
                [COMMAND_ERROR_IS_FATAL <ANY|LAST>]) #execute_process 命令将从当前正在执行的CMake进程中派生一个或多个子进程,从而提供了在配置项目时运行任意命令的方法。可以在一次调用 execute_process 时执行多个命令。但请注意,每个命令的输出将通过管道传输到下一个命令中
 add_custom_command(TARGET <target>
                  PRE_BUILD | PRE_LINK | POST_BUILD   #分别表示编译之前执行命令,链接之前执行命令,生成目标文件后执行命令
                  COMMAND command1 [ARGS] [args1...]
                  [COMMAND command2 [ARGS] [args2...] ...]
                  [BYPRODUCTS [files...]]
                  [WORKING_DIRECTORY dir]
                  [COMMENT comment]
                  [VERBATIM] [USES_TERMINAL]
                  [COMMAND_EXPAND_LISTS]) #在生成目标文件(add_executable)时自动执行其指定的命令

引入第三方依赖

官方文档-Using Denpendencies Guide
blog: cmake集成第三方库

1.FetchContent 下载并构建源码

1.纯头文件
用法:只需把他的include目录或头文件下载下来,然后
include_directories(xxlib/include) or add_library(mylib INTERFACE)
2.作为子模块引入
把项目的源码放到你工程的根目录,通过add_subdirectory()引入

2.find_package 引用系统中预安装的第三方库

可以通过find_package命令来寻找系统中的包、库
这个包必须是已经安装到系统中。
find_package添加依赖库用法
find_package提供了两种主要的方式来搜索

  1. config mode
    就是你要使用的包已经给你提供了cmake所需的config file(?)
  2. module mode
    有的包不是cmake构建的,没有提供所需的文件,那就需要自己写一个module file——FindSomePackage.cmake 文件来供find_package()命令来使用。FindSomePackage.cmake文件通常放在${CMAKE_CURRENT_SOURCE_DIR}/cmake目录下。官方提供许多已经写好的不同库的 module file。

note:
如果没有module file被提供,cmake将在系统中搜索config file

标准示例:

# Make project-provided Find modules available
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

find_package(SomePackage REQUIRED)
add_executable(MyExe main.cpp)
target_link_libraries(MyExe PRIVATE SomePrefix::LibName)

standard variable name:
Xxx_INCLUDE_DIRS
Xxx_LIBRARIES
Xxx_FOUND
......

编译目标文件

静态库 动态库

可执行文件

安装和打包 版本控制

support CMake install and find_package()

If we write find_package(my_library ...), it will go and look for a file named my_library-config.cmake (among others) in a directory named my_library* under the ${CMAKE_INSTALL_PREFIX}/lib (among many others).

用到的变量:
${CMAKE_INSTALL_PREFIX}——>/usr/local

知乎 Cmake打包

测试

配置框架

Kconfig
广泛用于U-Boot等项目的开源配置框架,支持组件的参数、使能、模块化等配置,参见Kconfig Language。

mconf

mconf 是 Linux 内核中的一个配置工具,用于配置和编辑内核的配置文件。它通常与 Kconfig 构建系统一起使用。以下是一些关键点:

Kconfig 文件: 内核的配置信息存储在一个名为 Kconfig 的文件中。这个文件包含了内核的各种配置选项,包括驱动、功能和系统参数。

Makefile: mconf 通过读取 Kconfig 文件,并根据用户的选择生成内核的配置文件(通常是 .config 文件)。这个文件将被用于后续的编译过程。

菜单界面: mconf 提供了一个文本菜单界面,允许用户通过键盘导航和选择配置选项。用户可以启用或禁用特定功能、模块或驱动

qconf

qconf 是配置工具,用于配置和管理项目的编译选项,主要用于CMake项目。以下是一些关键特点:

图形界面: qconf 提供了一个图形用户界面(GUI),使用户能够通过可视化的方式设置项目的配置选项,而不必手动编辑配置文件。

Kconfig配置文件(输入): 项目的配置信息通常存储在一个名为 Kconfig 的配置文件中,该文件包含了项目的各种编译选项和设置。

预设配置(输入): qconf 支持预设配置,用户可以在启动时加载特定的配置集,以简化不同构建配置之间的切换。预设配置通常是一些事先定义好的配置选项集,用户可以在启动 qconf 时加载这些预设配置。预设配置可以简化特定构建配置的选择

CMake 集成(输出): qconf 可以生成与 CMake 构建系统兼容的配置文件,通常是 config.cmake。这样,可以使用 CMake 来进行项目的构建。

Makefile集成(输出):根据用户的选择生成配置文件(通常是 .config 文件)。这个文件将被用于后续的编译过程。

qconf --fontsize xx
         --prefix "xx"
         --cmakefile xxx/config.cmake #生成用于CMake
         --cfgfile  xxx/.config #生成用于Makefile
         --loadcfg "xxxx" #加载预设配置
         "xxxx/Kconfig"  #配置文件

Kconfig

Kconfig 是一种配置语言,广泛用于 Linux 内核和其他一些开源项目中,用于配置和定制软件的构建选项,参见Kconfig Language。

  • 菜单和菜单配置:
menu "My Configuration Menu"
            hidden if M1
config MENU_OPTION1
    bool "Option 1"
config MENU_OPTION2
    bool "Option 2"
endmenu

MENU_OPTION1 和 MENU_OPTION2 是属于名为 "My Configuration Menu" 的菜单的配置选项。
其中,使用 hidden if 条件表达式,表示当 M1 为真时,显示该菜单,可根据其他配置项来动态控制菜单的显示隐藏。

  • 配置选项:
config MY_FEATURE
    bool "Enable My Feature"
    depends on C1 || C2
    default y if (D1 && (C1 || C2))
    default n if D1
    help_chs
        开启 
    help
        Enable this option to include support for My Feature.

MY_FEATURE 是配置选项的标识符,Enable My Feature 是一个用户友好的描述。
depends on

  • 选择块
choice
    prompt "xxxxx"
    default D1 if D1_CFG
    default D2 if D2_CFG
    help
        xxxxxxxxxxxxx
config D1
    bool "D1"
config D2
    bool "D2"
endchoice

choice 是一个 Kconfig 中的关键字,用于定义一组互斥的配置选项,用户可以从中选择一个,可以添加depend on来控制

  • 其他文件引入
source "xxxx/Kconfig"

编译和配置框架综合样例