Cmake以及CmakeLists

CMake

CMake是一个开源、跨平台的编译、测试和打包工具,它使用比较简单的语言描述编译、安装的过程,输出Makefile或者project文件,再去执行构建。

1 gcc、make和CMake的关系

gcc(GNU Compiler Collection)将源文件编译(Compile)成可执行文件或者库文件;而当需要编译的东西很多时,需要说明先编译什么,后编译什么,这个过程称为构建(Build)。常用的工具是make,对应的定义构建过程的文件为Makefile;而编写Makefile对于大型项目又比较复杂,通过CMake就可以使用更加简洁的语法定义构建的流程,CMake定义构建过程的文件为CMakeLists.txt。

CMake通过CMakeLists.txt配置项目的构建系统,配合使用cmake命令行工具生成构建系统并执行编译、测试,相比于手动编写构建系统(如Makefile)要高效许多。对于C/C++项目开发,非常值得学习掌握。这里的GCC只是示例,也可以是其他的编译工具。这里的Bin表示目标文件,可以是可执行文件或者库文件.
这里的GCC只是示例,也可以是其他的编译工具。这里的Bin表示目标文件,可以是可执行文件或者库文件

2. CMake一般使用流程

使用cmake一般流程为:生成构建系统(buildsystem,比如make工具对应的Makefile); 执行构建(比如make),生成目标文件;和执行测试、安装或打包。

2.1 生成构建系统

生成构建系统(buildsystem,比如make工具对应的Makefile), 通过cmake命令生成构建系统.

参数含义
-S指定源文件根目录,必须包含一个CMakeLists.txt文件
-B指定构建目录,构建生成的中间文件和目标文件的生成路径
-D指定变量,格式为-D =,-D后面的空格可以省略

指明使用当前目录作为源文件目录,其中包含CMakeLists.txt文件;使用build目录作为构建目录;设定变量CMAKE_BUILD_TYPE的值为Debug,变量AUTHOR的值为RealCoolEngineer:

cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DAUTHOR=RealCoolEngineer

2.2 执行构建(比如make),生成目标文件;

使用cmake --build [

| --preset ]执行构建。这里要指定的目录就是生成构建系统时指定的构建目录。常用的参数如下.在这一步,如果使用的是make构建工具,则可以在构建目录下直接使用make命令。

参数含义
–target指定构建目标代替默认的构建目标,可以指定多个
–parallel/-j [<jobs>]指定构建目标时使用的进程数

2.3 执行测试、安装或打包。

3. CMake具体操作流程

  1. 写CMakeLists.txt: 在具有源码的工作空间建立CMakeLists.txt文件.
  2. 生成构建系统: 在工作空间目录中生成构建系统, 执行cmake -B build, 执行完成后,在项目的根目录下会创建build目录,可以看到其中生成了Makefile文件。
  3. 执行构建: 在工作空间目录下执行cmake --build build, 使用make工具也可cd bbuild && make .
  4. build文件中有生成的可执行文件或者静态库.

4. CMake语法核心概念

CMake的命令有不同类型,包括脚本命令、项目配置命令和测试命令,细节可以查看官网cmake-commands。

CMake语言在项目配置中组织为三种源文件类型:

目录:CMakeLists.txt,针对的是一个目录,描述如何针对目录(Source tree)生成构建系统,会用到项目配置命令;
脚本:

4.1 注释

行注释使用"#“;块注释使用”#[[Some comments can be multi lines or in side the command]]"。

4.2 变量

CMake中使用set和unset命令设置或者取消设置变量。CMake中有以下常用变量类型:

4.2.1一般变量

设置的变量可以是字符串,数字或者列表(直接设置多个值,或者使用分号隔开的字符串格式为"v1;v2;v3"),比如:

# Set variable
set(AUTHOR_NAME Farmer)
set(AUTHOR "Farmer Li")
set(AUTHOR Farmer\ Li)

# Set list
set(SLOGAN_ARR To be)   # Saved as "To;be"
set(SLOGAN_ARR To;be)
set(SLOGAN_ARR "To;be")

set(NUM 30)   # Saved as string, but can compare with other number string
set(FLAG ON)  # Bool value

注意: 如果要设置的变量值包含空格,则需要使用双引号或者使用"“转义,否则可以省略双引号; 如果设置多个值或者字符串值的中间有”;“,则保存成list,同样是以”;"分割的字符串; 变量可以被list命令操作,单个值的变量相当于只有一个元素的列表; 引用变量:${},在if()条件判断中可以简化为只用变量名。

4.2.2 Cache变量

Cache变量(缓存条目,cache entries)的作用主要是为了提供用户配置选项,如果用户没有指定,则使用默认值,设置方法如下:

# set(<variable> <value>... CACHE <type> <docstring> [FORCE])
set(CACHE_VAR "Default cache value" CACHE STRING "A sample for cache variable")

注意: 主要为了提供可配置变量,比如编译开关; 引用CACHE变量 $CACHE{<varialbe>}
Cache变量会被保存在构建目录下的CMakeCache.txt中,缓存起来之后是不变的,除非重新配置更新

4.2.3 环境变量

修改当前处理进程的环境变量,设置和引用格式为:

# set(ENV{<variable>} [<value>])
set(ENV{ENV_VAR} "$ENV{PATH}")
message("Value of ENV_VAR: $ENV{ENV_VAR}")

和CACHE变量类似,要引用环境变量,格式为:$ENV{}。

4.3 条件语句

支持的语法有:
字符串比较,比如:STREQUAL、STRLESS、STRGREATER等;
数值比较,比如:EQUAL、LESS、GREATER等;
布尔运算,AND、OR、NOT;
路径判断,比如:EXISTS、IS_DIRECTORY、IS_ABSOLUTE等;
版本号判断;等等;
使用小括号可以组合多个条件语句,比如:(cond1) AND (cond2 OR (cond3))。

对于常量:

  1. ON、YES、TRUE、Y和非0值均被视为True;
  2. 0、OFF、NO、FALSE、N、IGNORE、空字符串、NOTFOUND、及以"-NOTFOUND"结尾的字符串均视为False。
    对于变量,只要其值不是常量中为False的情形,则均视为True。

5. CMake常用的脚本命令

有了前面的总体概念,下面掌握一些常用的CMake命令,对于CMake脚本编写就可以有不错的基础。

5.1 消息打印

前面已经有演示,即message命令,其实就是打印log,用来打印不同信息,常用命令格式为:

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

其中mode就相当于打印的等级,常用的有这几个选项:
空或者NOTICE:比较重要的信息,如前面演示中的格式
DEBUG:调试信息,主要针对开发者
STATUS:项目使用者可能比较关心的信息,比如提示当前使用的编译器
WARNING:CMake警告,不会打断进程
SEND_ERROR:CMake错误,会继续执行,但是会跳过生成构建系统
FATAL_ERROR:CMake致命错误,会终止进程

5.2 条件分支

这里以if()/elseif()/else()/endif()举个例子,for/while循环也是类似的:

set(EMPTY_STR "")
if (NOT EMPTY_STR AND FLAG AND NUM LESS 50 AND NOT NOT_DEFINE_VAR)
    message("The first if branch...")
elseif (EMPTY_STR)
    message("EMPTY_STR is not empty")
else ()
    message("All other case")
endif()

5.3 列表操作

list也是CMake的一个命令,有很多有用的子命令,比较常用的有:

APPEND,往列表中添加元素;
LENGTH,获取列表元素个数;
JOIN,将列表元素用指定的分隔符连接起来;

5.4 文件操作

CMake的file命令支持的操作比较多,可以读写、创建或复制文件和目录、计算文件hash、下载文件、压缩文件等等。 使用的语法都比较类似,以笔者常用的递归遍历文件为例,下面是获取src目录下两个子目录内所有c文件的列表的示例:GLOB_RECURSE表示执行递归查找,查找目录下所有符合指定正则表达式的文件。

file(GLOB_RECURSE ALL_SRC
        src/module1/*.c
        src/module2/*.c
        )

5.5 配置文件生成

使用configure_file命令可以将配置文件模板中的特定内容替换,生成目标文件。 输入文件中的内容@VAR@或者${VAR}在输出文件中将被对应的变量值替换。 使用方式为:

set(VERSION 1.0.0)
configure_file(version.h.in "${PROJECT_SOURCE_DIR}/version.h")

假设version.in.h的内容为:

#define VERSION "@VERSION@"

那么生成的version.h的内容为:

#define VERSION "1.0.0"

5.6 执行系统命令

使用execute_process命令可以执行一条或者顺序执行多条系统命令,对于需要使用系统命令获取一些变量值是有用的。比如获取当前仓库最新提交的commit的commit id:

execute_process(COMMAND bash "-c" "git rev-parse --short HEAD" OUTPUT_VARIABLE COMMIT_ID)

5.7 查找库文件

通过find_library在指定的路径和相关默认路径下查找指定名字的库,常用的格式如下:

find_library (<VAR> name1 [path1 path2 ...])

找到的库就可以被其他target使用,表明依赖关系。

5.8 include其他模块

include命令将cmake文件或者模块加载并执行。比如:

include(CPack) # 开启打包功能
include(CTest) # 开启测试相关功能

6. CMakeLists详解

介绍如何书写一个完备的CMakeLists.txt文件,满足一般项目的基础构建要求,CMake的语法将会更多介绍项目配置命令,主要有以下内容:
(1)设置一些自定义编译控制开关和自定义编译变量控制编译过程
(2)根据不同编译类型配置不同的编译选项和链接选项
(3)添加头文件路径、编译宏等常规操作
(4)编译生成不同类型的目标文件,包括可执行文件、静态链接库和动态链接库
(5)安装、打包和测试

6.1基础配置

6.1.1设置项目版本和生成version.h

一般来说,项目一般需要设置一个版本号,方便进行版本的发布,也可以根据版本对问题或者特性进行追溯和记录。
通过project命令配置项目信息,如下:

project(CMakeTemplate VERSION 1.0.0 LANGUAGES C CXX)

第一个字段是项目名称;通过VERSION指定版本号,格式为main.minor.patch.tweak,并且CMake会将对应的值分别赋值给以下变量(如果没有设置,则为空字符串):

PROJECT_VERSION, <PROJECT-NAME>_VERSION
PROJECT_VERSION_MAJOR, <PROJECT-NAME>_VERSION_MAJOR
PROJECT_VERSION_MINOR, <PROJECT-NAME>_VERSION_MINOR
PROJECT_VERSION_PATCH, <PROJECT-NAME>_VERSION_PATCH
PROJECT_VERSION_TWEAK, <PROJECT-NAME>_VERSION_TWEAK

因此,结合前一篇文章提到的configure_file命令,可以配置自动生成版本头文件,将头文件版本号定义成对应的宏,或者定义成接口,方便在代码运行的时候了解当前的版本号, 比如:

configure_file(src/c/cmake_template_version.h.in "${PROJECT_SOURCE_DIR}/src/c/cmake_template_version.h")

假如cmake_template_version.h.in内容如下:

#define CMAKE_TEMPLATE_VERSION_MAJOR @CMakeTemplate_VERSION_MAJOR@
#define CMAKE_TEMPLATE_VERSION_MINOR @CMakeTemplate_VERSION_MINOR@
#define CMAKE_TEMPLATE_VERSION_PATCH @CMakeTemplate_VERSION_PATCH@

执行cmake配置构建系统后,将会自动生成文件:cmake_template_version.h,其中@@将会被替换为对应的值:

#define CMAKE_TEMPLATE_VERSION_MAJOR 1
#define CMAKE_TEMPLATE_VERSION_MINOR 0
#define CMAKE_TEMPLATE_VERSION_PATCH 0

6…1.2指定编程语言版本

为了在不同机器上编译更加统一,最好指定语言的版本,比如声明C使用c99标准,C++使用c++11标准:

set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11)

这里设置的变量都是CMAKE_开头(包括project命令自动设置的变量),这类变量都是CMake的内置变量,正是通过修改这些变量的值来配置CMake构建的行为。CMAKE_、_CMAKE或者以下划线开头后面加上任意CMake命令的变量名都是CMake保留的。

6.1.3配置编译选项

通过命令add_compile_options命令可以为所有编译器配置编译选项(同时对多个编译器生效); 通过设置变量CMAKE_C_FLAGS可以配置c编译器的编译选项; 而设置变量CMAKE_CXX_FLAGS可配置针对c++编译器的编译选项。 比如:

add_compile_options(-Wall -Wextra -pedantic -Werror)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -std=c99")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe -std=c++11")

6.1.4 配置编译类型

通过设置变量CMAKE_BUILD_TYPE来配置编译类型,可设置为:DebugReleaseRelWithDebInfoMinSizeRel等,比如:

set(CMAKE_BUILD_TYPE Debug)

当然,更好的方式应该是在执行cmake命令的时候通过参数-D指定:

cmake -B build -DCMAKE_BUILD_TYPE=Debug

可以针对不同的编译类型设置不同的编译选项,比如对于Debug版本,开启调试信息,不进行代码优化:

set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")

对于Release版本,不包含调试信息,优化等级设置为2:

set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")

6.1.5添加全局宏定义

通过命令add_definitions可以添加全局的宏定义,在源码中就可以通过判断不同的宏定义实现相应的代码逻辑。用法如下:

add_definitions(-DDEBUG -DREAL_COOL_ENGINEER)

6.1.6添加include目录

通过命令include_directories来设置头文件的搜索目录,比如:

include_directories(src/c)

6.2编译目标文件

一般来说,编译目标(target)的类型一般有静态库、动态库和可执行文件。 这时编写CMakeLists.txt主要包括两步:
编译:确定编译目标所需要的源文件
链接:确定链接的时候需要依赖的额外的库
下面以开源项目(cmake-template)来演示。项目的目录结构如下:

./cmake-template
├── CMakeLists.txt
├── src
│   └── c
│       ├── cmake_template_version.h
│       ├── cmake_template_version.h.in
│       ├── main.c
│       └── math
│           ├── add.c
│           ├── add.h
│           ├── minus.c
│           └── minus.h
└── test
    └── c
        ├── test_add.c
        └── test_minus.

项目的构建任务为:
将math目录编译成静态库,命名为math
编译main.c为可执行文件demo,依赖math静态库
编译test目录下的测试程序,可以通过命令执行所有的测试
支持通过命令将编译产物安装及打包

6.2.1编译静态库

这一步需要将项目目录路径src/c/math下的源文件编译为静态库,那么需要获取编译此静态库需要的文件列表,可以使用set命令,或者file命令来进行设置。比如:
在src/c/math里的CMakeLists.txt里面编译libmath.o, 静态链接库 add_library, STATIC:

cmake_minimum_required(VERSION 3.5.1)
file(GLOB_RECURSE MATH_LIB_SRC *.c)
add_library(math STATIC ${MATH_LIB_SRC})

使用file命令获取src/c/math目录下所有的*.c文件,然后通过add_library命令编译名为math的静态库,库的类型是第二个参数STATIC指定的. 如果指定为SHARED则编译的就是动态链接库。

6.2.2 编译可执行文件

通过add_executable命令来往构建系统中添加一个可执行构建目标,同样需要指定编译需要的源文件。但是对于可执行文件来说,有时候还会依赖其他的库,则需要使用target_link_libraries命令来声明构建此可执行文件需要链接的库。

在示例项目中,main.c就使用了src/c/math下实现的一些函数接口,所以依赖于前面构建的math库。所以在CMakeLists.txt中添加以下内容:

add_executable(demo src/c/main.c)
target_link_libraries(demo math)

第一行说明编译可执行文件demo需要的源文件(可以指定多个源文件,此处只是以单个文件作为示例);第二行表明对math库存在依赖。
此时可以在项目的根目录下执行构建和编译命令,并执行demo.

6.3安装和打包

6.3.1安装

对于安装来说,其实就是要指定当前项目在执行安装时,需要安装什么内容:通过install命令来说明需要安装的内容及目标路径;通过设置CMAKE_INSTALL_PREFIX变量说明安装的路径;v3.15往后的版本可以使用cmake --install --prefix <install-path>覆盖指定安装路径。

比如,在示例项目中,把math和demo两个目标按文件类型安装:
install(TARGETS math demo
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
这里通过TARGETS参数指定需要安装的目标列表;参数RUNTIME DESTINATION、LIBRARY DESTINATION、ARCHIVE DESTINATION分别指定可执行文件、库文件、归档文件分别应该安装到安装目录下个哪个子目录。

如果指定CMAKE_INSTALL_PREFIX为/usr/local,那么math库将会被安装到路径/usr/local/lib/目录下;而demo可执行文件则在/usr/local/bin目录下。
CMAKE_INSTALL_PREFIX在不同的系统上有不同的默认值,使用的时候最好显式指定路径。

同时,还可以使用install命令安装头文件:

file(GLOB_RECURSE MATH_LIB_HEADERS src/c/math/*.h)
install(FILES ${MATH_LIB_HEADERS} DESTINATION include/math)

假如将安装到当前项目的output文件夹下,可以执行:

# cmake -B cmake-build -DCMAKE_INSTALL_PREFIX=./output# cmake --build cmake-build# cd cmake-build && make install && cd -
Install the project...
-- Install configuration: ""
-- Installing: .../cmake-template/output/lib/libmath.a
-- Installing: .../gitee/cmake-template/output/bin/demo
-- Installing: .../gitee/cmake-template/output/include/math/add.h
-- Installing: .../gitee/cmake-template/output/include/math/minus.h

可以看到安装了前面install命令指定要安装的文件,并且不同类型的目标文件安装到不同子目录。

6.3.2 打包

6.4测试

6.4.1编写测试程序

6.4.2 添加测试

6.4.3 执行测试

------------------------------------end------------------------------------------

Reference:
转自https://zhuanlan.zhihu.com/p/371257515
参考https://blog.csdn.net/rangfei/article/details/108756768
本文仅作个人学习记录, 如有侵权, 立即删除.

  • 4
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值