一、介绍

CMake是一个跨平台的源码构建工具,它只需要编写一次CMakeLists.txt文件,就可以生成不同平台下的构建系统,然后再使用平台原生的构建工具(如VS/make等)进行编译安装。CMake也可以看作是脚本语言,很多大型的开源项目都在使用它,广泛用于C/C++项目中。

二、构建系统

构建系统(Buildsystem)就是指原始的项目工程文件,比如win32平台下VS的.sln .vcxproj工程文件,linux平台下Makefile等

#查看CMake可以生成哪些构建系统
cmake -G

三、构建步骤

  • 通过CMakeLists.txt文件生成构建系统,这里会检查环境配置,比如编译器等,如果出错则直接退出。

        可以使用cmake命令或cmake-gui完成这一步,比如cmake命令: cmake  [选项]  <CMakeLists.txt路径>

  • 生成.vcxproj或Makefile之后就可以使用原生工具VS/nmake/make编译安装

常用选项:

-G:表示生成哪种构建系统

#一般性的构建步骤:
mkdir build    #在项目目录下创建build目录
cd build
cmake -G "Visual Studio 14 2015" ..    #生成VS2015工程文件,..表示CMakeLists.txt在上一级目录中
cmake -G "NMake Makefiles" ..          #生成Windows平台的Makefile
cmake -G "Unix Makefiles"  ..          #生成Linux/Unix平台下标准的Makefile
#根据不同平台选择相应的构建档,在CMake中称之为generator

#接下来使用VS/nmake/make等工具编译安装

-D:定义预处理变量,比如用CMAKE_BUILD_TYPE指定编译类型

cmake -DCMAKE_BUILD_TYPE=Debug|Release .. #生成Debug版或者Release版

-P: 执行CMake语言编写的脚本文件,通常以.cmake为后缀

cmake [{-D <var>=<value>}...] -P <script.cmake>

-S:顶层CMakeLists.txt目录

-B:指定构建目录,若不存在cmake创建它

cd 项目目录
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release #默认构建Debug版本
cmake --build build   #编译
cmake --install build #安装

四、CMake源文件

1、目录(CMakeLists.txt)

当CMake处理工程源码树的时候,入口点是顶层目录的CMakeLists.txt文件,文件中还可能使用add_subdirectory()命令添加子目录,每个子目录也必须有一个CMakeLists.txt

2、脚本(script.cmake)

通常<script>.cmake会使用macro()/function()命令定义一些公共的复用的函数(类似于c语言宏和函数的功能),脚本主要有下面两种执行方式:

  1. 通过cmake -P <script>.cmake单独解释执行,而不会生成构建系统,这种方式可以用来测试脚本文件中是否有语法错误
  2. 在CMakeLists.txt中使用include()命令加载并执行脚本文件中的代码,这样CMakeLists.txt后续的代码就可以直接调用这些宏和函数,通过CMAKE_MODULE_PATH内置变量指定脚本文件的查找路径,如果没找到则会搜索CMake安装目录

3、模块(module.cmake)

find_package命令用来查找项目中的依赖库,有可能是系统库(如OpenGL),也可能是第三方库(如OpenCV)

命令格式:
find_package(<PackageName> [version] [module] [REQUIRED] [[COMPONENTS] [components...]])

该命令首先会在CMAKE_MODULE_PATH指定的路径之中查找,然后在CMake的安装路径中查找并执行Find<PackageName>.cmake文件,在该文件中会搜索PackageName的相关信息,比如.h、.lib、.so的位置等

如果找到PackageName的这些信息,Find<PackageName>.cmake文件就会设置PackageName_FOUND、PackageName_INCLUDE_DIR、PackageName_LIBRARIES等变量,那么在CMakeLists.txt中就可以使用这些变量,比如用target_include_directories、target_link_libraries命令设置依赖库的头文件、库文件。

五、变量

CMake语言的变量都是字符串类型

1、变量范围

  • 函数范围

function()/endfunction()命令会创建一个新的变量范围,在函数中set/unset设定的变量都属于该范围,return之后这些变量就不可见了

  • 目录范围

function()/endfunction()命令之外set/unset的变量属于目录范围,在当前目录及其子目录的CMakeLists.txt都可见

注:add_subdirectory()添加的子目录的CMakeLists.txt会继承父目录CMakeLists.txt的变量

  • 持久缓存

CMake可以持久保存一些变量的值,这些变量每次构建都存在,使用set/unset的CACHE选项设置

set(<variable> <value>... CACHE <type> <docstring> [FORCE])

  • variable:变量名称
  • value:变量值列表
  • CACHE:cache变量的标志
  • type:变量类型,取决于变量的值。类型分为:BOOL、FILEPATH、PATH、STRING、INTERNAL
  • docstring:必须是字符串,作为变量概要说明
  • FORCE:强制选项,强制修改变量值

2、变量引用

形式:${<variable>}

引用顺序:函数—>目录—>CACHE,如果没找到设置为空字符串

3、自定义变量

通过set/unset命令在函数范围目录范围定义/取消

除了set之外,还可以用option命令定义布尔类型变量,主要用于控制编译行为,比如是否构建单元测试、是否启用某个功能模块等

option(<option_variable> "help string describing option"
       [initial value])

初始值是可选的,如果没有指定则为OFF

option定义的变量可以通过cmake -D<variable=ON | OFF>指定

# CMakeLists.txt
option(BUILD_UNIT_TEST
    "Set this to OFF if you do not wish to unit tests as part of the default cmake --build"
    ON)
message(STATUS "The BUILD_UNIT_TEST value: ${BUILD_UNIT_TEST}")

[jianjunyang@~/T5.1/oam]$ cmake -P CMakeLists.txt
-- The BUILD_UNIT_TEST value: ON
[jianjunyang@~/T5.1/oam]$ cmake -DBUILD_UNIT_TEST=OFF -P CMakeLists.txt
-- The BUILD_UNIT_TEST value: OFF

4、内置变量

以CMAKE_开头的都属于内置变量,但也不限于CMAKE_开头,这里介绍最常用的几个

变量名含义
PROJECT_NAME由project指令设置的工程名
${ProName}_SOURCE_DIR工程(顶层)CMakeLists.txt所在的目录
PROJECT_SOURCE_DIR工程(顶层)CMakeLists.txt所在的目录,等同于${ProName}_SOURCE_DIR,推荐使用这个
${ProName}_BINARY_DIR 工程(顶层)CMakeLists.txt的构建目录,比如上面的build
PROJECT_BINARY_DIR工程(顶层)CMakeLists.txt的构建目录,等同于${ProName}_BINARY_DIR,推荐使用这个
CMAKE_CURRENT_SOURCE_DIR当前CMakeLists.txt所在的目录
CMAKE_CURRENT_BINARY_DIR当前CMakeLists.txt的构建目录
CMAKE_RUNTIME_OUTPUT_DIRECTORY指定exe的生成目录
CMAKE_LIBRARY_OUTPUT_DIRECTORY 指定dll/lib的生成目录
CMAKE_ARCHIVE_OUTPUT_DIRECTORY指定静态库生成目录

注意:PROJECT_SOURCE_DIR是固定不变的,就是指最顶层的CMakeLists.txt,包含project指令的那个

          CMAKE_CURRENT_SOURCE_DIR是变化的,指当前CMakeLists.txt,通过add_subdirectory()添加的每个子目录都有一个CMakeLists.txt

5、环境变量

环境变量的范围是全局的,每个CMakeLists.txt都可以引用,它不会被缓存。CMake进程启动时进行初始化,可以通过set/unset改变

引用:$ENV{variable}

修改:set(ENV{<variable>} [<value>])

           unset(ENV{<variable>})

六、平台相关变量

WIN32        如果是windows平台则为True

UNIX          如果是unix-like平台则为True

APPLE        如果是apple平台则为True

七、条件表达式

格式:
if(<condition>)
  <commands>
elseif(<condition>)
  <commands>
else()
  <commands>
endif()

条件判断关键字:

关键字意义
AND
OR
NOT
EXISTS文件或目录是否存在
COMMAND是否为命令
DEFINED变量是否定义
TARGET是否为目标,通过add_executable或add_library添加
MATCHES匹配正则表达式
IS_DIRECTORY是否为目录
IS_NEWER_THAN文件1是否比文件2新
EQUAL,LESS,LESS_EQUAL,GREATER,GREATER_EQUAL数字比较

STREQUAL,STRLESS,STRLESS_EQUAL

STRGREATER,STRGREATER_EQUAL

字符串比较

VERSION_EQUAL,VERSION_LESS,VERSION_LESS_EQUAL

VERSION_GREATER,VERSION_GREATER_EQUAL

版本比较

八、循环

  1. while(<condition>) #condition跟if的语法一样
      <commands>
    endwhile()
  2. foreach(<loop_var> <items>) #items是用分号或空格分隔的列表,loop_var每次迭代一个列表项
      <commands>
    endforeach()
  3. foreach(<loop_var> RANGE <start> <stop> [<step>]) #数字范围循环,start和step可以省略
  4. foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]]) #列表(项)循环

        注:break()/continue()相当于C语言的break和continue关键字

九、宏/函数

  1. macro(<name> [<arg1> ...])
      <commands>
    endmacro()
  2. function(<name> [<arg1> ...])
      <commands>
    endfunction()

        注:宏和函数一旦定义之后,就可以想普通命令一样去调用

十、生成二进制目标

1、生成可执行文件

命令格式:
add_executable(<name> [WIN32] [source1] [source2 ...])

选项:

WIN32:表示生成win32应用程序,即程序入口点为WinMain

source1 source2 ...指定要编译的源文件(.h,.c,.cpp,.ui,资源文件等)

2、生成链接库

命令格式:
add_library(<name> [STATIC | SHARED] [<source>...])

选项:

 STATIC:生成静态库

SHARED:生成动态库

3、源文件指定方式 

  • 通过set命令把要编译的文件放在变量中
  • 通过file(GLOB ...)命令生成源文件列表
  • add_executable/add_library命令中不指定源文件,通过target_source命令指定
命令格式:
file(GLOB <variable>
     [LIST_DIRECTORIES true|false] [RELATIVE <path>] [<globbing-expr>...])
file(GLOB_RECURSE <variable> 
     [LIST_DIRECTORIES true|false] [RELATIVE <path>] [<globbing-expr>...])

作用: 

根据globbing-expr表达式匹配目录下的文件,globbing-expr类似于正则表达式,常用的通配符有:

*  任意个任意字符

? 一个任意字符

[] 必有一个中括号的字符

示例:*.cpp,*.vt?,f[3-5].txt

选项: 

LIST_DIRECTORIES :是否列出目录,默认为true

RELATIVE:相对路径

GLOB_RECURSE模式会递归查找子目录

#头文件
set(HDR_LIST common.h)
#源文件
set(SRC_LIST main.cpp utils.cpp log.cpp)
#UI文件
set(UI_LIST frame.ui)
#资源文件
set(RC_LIST dolphin.rc)

add_executable(test ${HDR_LIST} ${SRC_LIST} ${UI_LIST} ${RC_LIST})

#通过下面两步设置与上面等价
#add_executable(test)
#target_source(test PRIVATE ${HDR_LIST} ${SRC_LIST} ${UI_LIST} ${RC_LIST})

#================================================================
file (GLOB SRCS LIST_DIRECTORIES false
            "${CMAKE_CURRENT_SOURCE_DIR}/*.h"
            "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
            "${CMAKE_CURRENT_SOURCE_DIR}/*.ui"
            "${CMAKE_CURRENT_SOURCE_DIR}/*.rc"
)
add_executable(test ${SRCS})

十一、指定头文件目录

target_include_directories(<target> [SYSTEM] <PUBLIC|PRIVATE> [items1...])

选项: PUBLIC:为当前target及其依赖目标(通过target_link_libraries命令指定)指定包含目录

            PRIVATE:只为当前target指定包含目录

注:老式的指定包含目录的命令为:include_directories([SYSTEM] dir1 [dir2 ...]),它会为当前CMakeLists.txt中的所有target都指定这个包含目录

十二、指定编译选项

target_compile_options(<target> <PUBLIC|PRIVATE> [items1...])

十三、指定链接库

target_link_libraries(<target> <PRIVATE|PUBLIC> <item>...)

例如:target_compile_options(<target> PRIVATE -Wextra -Werror)

十四、指定库文件路径

target_link_directories(<target> <PUBLIC|PRIVATE> [items1...])

十五、添加预处理宏

ADD_DEFINITIONS、ADD_COMPILE_DEFINITIONS预处理宏

set_target_properties(<target> PROPERTIES COMPILE_DEFINITIONS [items1...])

target_compile_definitions(<target> <PUBLIC|PRIVATE> [items1...]) 更推荐的用法

十六、安装

install(<TYPE> files... DESTINATION <dir>
        [PERMISSIONS permissions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [OPTIONAL] [NAMELINK_ONLY|NAMELINK_SKIP])

在这个结构中,我们可以看到以下几个关键部分:

  • <TYPE>:这是一个必选参数,它定义了我们要安装的内容的类型。这个参数可以是TARGETS(目标),FILES(文件),DIRECTORY(目录)等

  • files...:这是一个或多个我们要安装的文件或目标。对于TARGETS,这将是我们在add_executable或add_library中定义的目标名称。对于FILES和DIRECTORY,这将是文件或目录的路径

  • DESTINATION <dir>:这是一个必选参数,它定义了我们要将文件或目标安装到哪个目录

  • [PERMISSIONS permissions...]:这是一个可选参数,它允许我们定义安装的文件或目标的权限。如果我们不指定这个参数,CMake将使用默认的权限

  • [CONFIGURATIONS [Debug|Release|...]]:这是一个可选参数,它允许我们定义在哪些构建配置中执行安装命令。如果我们不指定这个参数,CMake将在所有的构建配置中执行安装命令

  • [COMPONENT <component>]:这是一个可选参数,它允许我们将安装的文件或目标分组到一个组件中。这个参数在创建安装包时非常有用

  • [OPTIONAL]:这是一个可选参数,它允许我们定义如果文件或目标不存在,CMake是否应该继续执行安装命令

  • [NAMELINK_ONLY|NAMELINK_SKIP]:这是一个可选参数,它只对库目标有效。它允许我们定义是否安装库的名字链接

安装路径:

CMAKE_INSTALL_PREFIX安装前缀(默认值为/usr/local)
CMAKE_INSTALL_LIBDIR安装库文件的目录名称(默认值为lib)
CMAKE_INSTALL_INCLUDEDIR头文件目录名称(默认值为include)
CMAKE_INSTALL_DATADIRSHARE目录名称(默认值为share)
CMAKE_INSTALL_SYSCONFDIR系统配置文件目录名称(默认值为/etc)
CMAKE_INSTALL_BINDIR可执行文件目录名称(默认值为bin)

十七、打包

在顶层CMakeLists.txt中通过设置CPack相关的变量来配置打包信息,添加include(CPack)引入CPack模块

string(TIMESTAMP PROJECT_BUILD_TIME "%Y%m%d_%H%M%S")
set(CPACK_GENERATOR "STGZ")
set(CPACK_SOURCE_GENERATOR "TGZ")
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_VERSION "0.1.1")
set(CPACK_PACKAGE_FILE_NAME ${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${PROJECT_BUILD_TIME})
include(CPack)
#构建(编译)并安装,也可以分开两步执行
cmake --build build --target install
#构建(编译)并打包,也可以分开两步执行
cmake --build build --target package
#打包源码
cmake --build build --target package_source

单独执行打包过程

cd build
cpack -G STGZ -C Debug|Release --config CPackConfig.cmake
cpack -G TGZ  -C Debug|Release --config CPackSourceConfig.cmake

-G:指定生成器,即安装包格式,比如TGZ,STGZ、ZIP,DEB,RPM等

-C:指定安装包配置格式

--config:读取的配置文件,默认为CPackConfig.cmake

配置、编译、安装、打包分步执行

cd 项目目录
mkdir build
cd build
cmake ..
make
make install
make package
make package_source

STGZ包简介

 Linux下的STGZ包后缀通常是.sh或.run,是由一个shell脚本和tar.gz压缩包通过cat命令拼接在一起生成的。shell脚本中定义了安装软件包的一些操作,比如校验,解压,将文件拷贝到指定目录、设置环境变量等,可以根据需要进行定义。压缩包通常就是软件包的可执行文件和库文件等

CMake STGZ生成器默认的安装脚本为:{cmake安装路径/Modules/Internal/CPack/CPack.STGZ_Header.sh.in}

如果觉得默认的安装脚本比较简单,也可以自定义功能更丰富的脚本,并通过CPACK_STGZ_HEADER_FILE变量指定自定义脚本的位置

set(CPACK_GENERATOR "STGZ")
set(CPACK_STGZ_HEADER_FILE ${PROJECT_SOURCE_DIR}/etc/CPack.STGZ_Header.sh.in)

.in是CMake中的模板文件,文件中可以通过@CPACK_xxx@引用在CMakeLists.txt中设置的CPACK_xxx变量(注意:不能引用CMAKE_xxx类型变量)

十八、常用命令

1、字符串string

字符串查找:
string(FIND <string> <substring> <output_variable> [REVERSE])

   说明: 

        查找子字符串首次出现的位置,并把所有保存在output_variable中,若没找到则为-1

        REVERSE表示反向查找,即最后一次出现的位置

字符串替换:
string(REPLACE <match_str> <replace_str> <result_str> <src_str1> [<src_str2>...])

  说明: 

        在源字符串src_str中查找匹配的子字符串match_str,并替换为replace_str,替换后的字符串保存在result_str中,源字符串src_str保持不变

字符串拼接:
string(APPEND <string_variable> [<input>...])
string(PREPEND <string_variable> [<input>...])
string(CONCAT <output_variable> [<input>...])
string(JOIN <glue> <output_variable> [<input>...])

  说明:

APPEND在变量最后添加

PREPEND在变量前面添加

CONCAT把参数字符串直接连接在一起

JOIN把参数字符串用glue连接在一起

string(APPEND str1 "hello " "cmake")     #str1: hello cmake
string(PREPEND str2 "Lists.txt" "CMake") #str2: CMakeLists.txt
string(CONCAT str3 "Build" "System")     #str3: BuildSystem
string(JOIN "+" str4 "1" "2" "3" "4")    #str4: 1+2+3+4
大小写转换:
string(TOLOWER <string> <output_variable>)
string(TOUPPER <string> <output_variable>)
字符串长度:
string(LENGTH <string> <output_variable>)
子字符串提取:
string(SUBSTRING <string> <begin> <length> <output_variable>)
删除首尾空白:
string(STRIP <string> <output_variable>)
字符串比较:
string(COMPARE LESS <string1> <string2> <output_variable>)
string(COMPARE GREATER <string1> <string2> <output_variable>)
string(COMPARE EQUAL <string1> <string2> <output_variable>)
string(COMPARE NOTEQUAL <string1> <string2> <output_variable>)
string(COMPARE LESS_EQUAL <string1> <string2> <output_variable>)
string(COMPARE GREATER_EQUAL <string1> <string2> <output_variable>)

  说明:

        比较结果保存在变量output_variable,其值为true或false

2、列表list

3、信息打印message

message([<mode>] "message text" ...)

常用的mode选项有:

  • FATAL_ERROR:致命错误,终止构建
  • SEND_ERROR:一般错误,继续构建
  • WARNING:警告信息
  • NOTICE:需要注意的信息
  • STATUS:一般输出信息
  • DEBUG:调试信息

4、文件file

file(COPY <file|dir> DESTINATION <dir>        复制文件或目录

file(GLOB ...) 文件通配符,用于搜索指定模式的文件

file(GLOB <variable>
     [RELATIVE <path>]
     [<globbing-expressions>...])

RELATIVE:指定相对路径起点,通常是${CMAKE_CURRENT_SOURCE_DIR} 

file(GLOB sources RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} cm/*.h)

5、源文件分组source_group

source_group(<name> [FILES <src>...] [REGULAR_EXPRESSION <regex>])

把源文件、头文件、ui文件等进行分组归类,在IDE中放在一起显示

FILES:要添加的文件

REGULAR_EXPRESSION:正则表达式匹配

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐