基础概念
CMake是什么?
CMake是一个元构建系统(meta build-system),用于生产其他构建系统文件(如Makefile或Ninja)。
基础操作方式
CMake
使用一个CMakeLists.txt
文件描述配置,然后使用cmake
驱动这个文件生成对应构建系统文件。
如果不想使用cmake
命令行驱动CMakeLists.txt
,也可以用cmake-gui
进行可视化完成.
.
└── CMakeLists.txt
1 directory, 1 file
在这个目录执行如下命令可以生成默认的构建系统文件(一般为Makefile)
# -S 指定源码目录, -B 指定生产的构建文件的目录
cmake -S . -B build
运行后会在build目录下生成很多文件。
tree -L 2
.
├── CMakeLists.txt
└── build├── CMakeCache.txt├── CMakeFiles├── Makefile└── cmake_install.cmake3 directories, 4 files
你会惊讶的发现在build文件夹下有一个Makefile文件。如果你想生成Ninja可以使用-G命令完成。
cmake -G "Ninja" -S . -B build
如果想查询支持的构建系统用cmkae --help
查询,如下所示
由于cmake
会生成不同的构建系统。比如makefile
你会继续调用make
命令去完成编译等流程。但是如果是ninja
就要执行ninja命令去编译。为了屏蔽这个差异make
提供了下列命令去无差别编译安装
# --VERBOSE用于指定输出详细信息
camke --build . --verbose
camke --install .
但可惜的cmake没有提供相关的clean命令,需要你自己根据平台调用如ninja clean
何 make clean
变量类型
在CMAKE
中变量可以大致分两种:
- 缓存变量
- 非缓存变量
所以我们先明白这个非常重要的基础概念才能方便我们编写文件。
缓存变量
所有声明的变量会在cmake生成配置文件后会放入一个CMakeCache.txt
文件中。
如果你再次修改CMakeLists.txt
去修改一个缓存变量你会发现CMakeCache.txt
不会更新,除非你手动删除这个文件。(如果使用命令行CMAKE -DKEY=VALUE
的缓存变量会强制更新CMakeCache.txt
)
缓存变量特性:
- 全局修改会被其他cmake感知
- 全局可访问(同级cmake也可以)
- 除显示强制覆盖否则不会重写该数值
- 修改CMakeLists.txt中的缓存变量在次用cmake重生成构建文件不会更新CMakeCache.txt
- 命令行传入的缓存变量每次cmake生产配置文件都会更新CMakeCache.txt
示例一
证明缓存变量会被写一个CMakeCache.txt文件中
我们看有如下目录文件
├── CMakeLists.txt
1 directory, 1 files
//CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(DEMO)
# 声明一个缓存变量
set(myvar "Hello" CACHE STRING "一个说明行描述可以无视")
# 声明一个非缓存变量
set(myNormalVar "Hello world")
执行如下命令
cmake -S . -B build
生成如下文件和对应目录
.
├── CMakeLists.txt
└── build├── CMakeCache.txt├── CMakeFiles├── Makefile└── cmake_install.cmake
3 directories, 4 files
我们查找这里两个变量是否在CmakeCache.txt中
使用grep查找
grep -E "myvar|myNormalVar" ./build/CMakeCache.txt
输出:
myvar:STRING=Hello
示例二
修改CMakeLists.txt的缓存变量不会引起CmakeCache.txt更新
# CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(DEMO)
# 声明一个缓存变量
set(myvar "Hello" CACHE STRING "一个说明行描述可以无视")
//生成配置文件
cmake -S . -B build
//查找变量
grep -E "myvar" ./build/CMakeCache.txt
//输出结果
myvar:STRING=Hello
此时我们修改CMakeLists.txt中的myvar变量为world
# CMakeLists.txt//...略
//修改一个变量
set(myvar "World" CACHE STRING "一个说明行描述可以无视")
//...略
//重新生成配置文件
cmake -S . -B build
//在此查找变量
grep -E "myvar" ./build/CMakeCache.txt
//输出结果 发现并没有改为World
myvar:STRING=Hello
示例二
CMake -DKey=value 命令行传入的缓存变量会强制刷新CMakeCache.txt
//第一次运行配置命令并查找
cmake -DmyKey=myvalue -S . -B build ; grep -E "myKey" ./build/CMakeCache.txt
myKey:UNINITIALIZED=myvalue
//第二次运行配置命令并查找
cmake -DmyKey=myvalue22222 -S . -B build ; grep -E "myKey" ./build/CMakeCache.txt
myKey:UNINITIALIZED=myvalue22222
示例三
我们缓存变量作用域和可见性
.
├── CMakeLists.txt
├── childDir01
│ └── CMakeLists.txt
└── childDir02└── CMakeLists.txt
3 directories, 3 files
//.CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(PARENT_PRO)set(myParentNormalVal "I'm myParentNormalVal")
set(myParentCacheVal "I'm myParentCacheVal" CACHE STRING "一个描述")#引入子cmake
add_subdirectory(childDir01)
add_subdirectory(childDir02)
# 打印子make修改后的变量
# 虽然CHILD01_PRO修改普通变量,但是由于作用域问题输出旧数值。 PARENT_PRO myParentNormalVal = I'm myParentNormalVal
message("${PROJECT_NAME} myParentNormalVal = ${myParentNormalVal}")
# 缓存变量全局修改都会生效,输出CHILD01_PRO修改后的数值。 PARENT_PRO myParentCacheVal = I'm myParentCacheValChild01
message("${PROJECT_NAME} myParentCacheVal = ${myParentCacheVal}")
# 父cmake无妨访问子cmake的普通变量
message("${PROJECT_NAME} myChild01NormalVal = ${myChild01NormalVal}")
# 子cmake定义的缓存变量全局都可以访问
message("${PROJECT_NAME} myChild01CacheVal = ${myChild01CacheVal}")//./child01/CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(CHILD01_PRO)
set(myChild01NormalVal "I'm myChild01NormalVal")
set(myChild01CacheVal "I'm myChild01CacheVal" CACHE STRING "一个描述" FORCE)
# 打印父变量
#子cmake可以任意访问父变量(普通变量修改仅所在的Cmake文件生效不能跨全局) 输出 CHILD01_PRO myParentNormalVal = I'm myParentNormalVal
message("${PROJECT_NAME} myParentNormalVal = ${myParentNormalVal}")
#子cmake可以任意访问父变量(注意缓存变量的修改全局都可以生效) 输出 CHILD01_PRO myParentCacheVal = I'm myParentCacheVal
message("${PROJECT_NAME} myParentCacheVal = ${myParentCacheVal}")
message("\r\n${PROJECT_NAME} change myParentNormalVal and myParentCacheVal :\r\n")
# 修改父变量
set(myParentNormalVal "I'm myParentNormalValChild01")
set(myParentCacheVal "I'm myParentCacheValChild01 " CACHE STRING "一个描述" FORCE)
# 打印修改变量后的数值
#修改普通变量仅在当前CMakeLists.txt生效。回到父时依旧是旧值。输出 CHILD01_PRO myParentNormalVal = I'm myParentNormalValChild01
message("${PROJECT_NAME} myParentNormalVal = ${myParentNormalVal}")
#修改缓存变量全局生效。回到父时输出改变后的数值。输出 CHILD01_PRO myParentCacheVal = I'm myParentCacheValChild01
message("${PROJECT_NAME} myParentCacheVal = ${myParentCacheVal}")//./child02/CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(CHILD02_PRO)
# 打印父变量
#子cmake可以任意访问父变量(普通变量修改仅所在的Cmake文件生效不能跨全局) 输出 CHILD02_PRO myParentNormalVal = I'm myParentNormalVal
message("${PROJECT_NAME} myParentNormalVal = ${myParentNormalVal}")
#子cmake可以任意访问父变量(注意缓存变量的修改全局都可以生效) 输出 CHILD02_PRO myParentCacheVal = I'm myParentCacheValChild01
message("${PROJECT_NAME} myParentCacheVal = ${myParentCacheVal}")
#打印同级的Cmake变量
#同级cmake变量无法访问 输出 CHILD02_PRO myChild01NormalVal =
message("${PROJECT_NAME} myChild01NormalVal = ${myChild01NormalVal}")
#缓存变量可以访问 输出 CHILD02_PRO myChild01CacheVal = I'm myChild01CacheVal
message("${PROJECT_NAME} myChild01CacheVal = ${myChild01CacheVal}")
#执行命令
cmake -S . -B build
#输出结果
CHILD01_PRO myParentNormalVal = I'm myParentNormalVal
CHILD01_PRO myParentCacheVal = I'm myParentCacheVal
CHILD01_PRO change myParentNormalVal and myParentCacheVal :
CHILD01_PRO myParentNormalVal = I'm myParentNormalValChild01
CHILD01_PRO myParentCacheVal = I'm myParentCacheValChild01 CHILD02_PRO myParentNormalVal = I'm myParentNormalVal
CHILD02_PRO myParentCacheVal = I'm myParentCacheValChild01
CHILD02_PRO myChild01NormalVal =
CHILD02_PRO myChild01CacheVal = I'm myChild01CacheValPARENT_PRO myParentNormalVal = I'm myParentNormalVal
PARENT_PRO myParentCacheVal = I'm myParentCacheValChild01
PARENT_PRO myChild01NormalVal =
PARENT_PRO myChild01CacheVal = I'm myChild01CacheVa
非缓存变量
反向参考变量
变量声明方式
set
你可以使用set命令如下声明如下变量MYVAR
。(注意此处非缓存变量类型)
set(<variable> <value>... [PARENT_SCOPE])
cmake_minimum_required(VERSION 3.14)
project(DEMO)
set(MYVAR "MYVALUE")
message("变量MYVAR=${MYVAR}")
当然变量可以拼接多个字符串并自动使用;
进行分割
cmake_minimum_required(VERSION 3.14)
project(DEMO)
set(MYVAR "MYVALUE" "MYVALUE2" "MYVALUE3")
message("变量MYVAR=${MYVAR}")
输出:
cmake -S . -B build
变量MYVAR=MYVALUE;MYVALUE2;MYVALUE3
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /Users/fanjack/Desktop/learnMake/build
上面声明的是一个非缓存类型的普通变量。我们来看看声明缓存变量
set(<variable> <value>... CACHE <type> <docstring> [FORCE])
cmake_minimum_required(VERSION 3.14)
project(DEMO)
# 声明一个缓存变量
set(myvar "Hello" CACHE STRING "一个说明")
message("myvar is ${myvar}")
# 想修改缓存变量 但是由于缓存变量一定被设置除非使用FORCE关键字不然不允许重写
set(myvar "Hello2" CACHE STRING "一个说明")
message("myvar is ${myvar}")
# 使用FORCE关键字重写
set(myvar "Hello3" CACHE STRING "一个说明" FORCE)
message("myvar is ${myvar}")
对应的输出:
myvar is Hello
myvar is Hello
myvar is Hello3
选项变量
option(<variable> "<help_text>" [value])
(自动为缓存变量)
option(USE_MYMATH "Use my math implementation" ON)
//为ON表示条件为真输出 Using my math implementation
if(USE_MYMATH)message("Using my math implementation")
else()message("Using standard math library")
endif()
环境变量
访问环境变量 $ENV{XXX}
其中XXX为环境变量名,makefile
会自动将环境转化为makefile
变量。
举例
cmake_minimum_required(VERSION 3.14)
project(DEMO)
#因为环境变量存在PATH所以输出相关数值
message("env $ENV\{PATH\} = $ENV{PATH}")
#因为PATH是环境而不是CMAKE变量所以不会有任何输出
message("env $\{PATH\} = ${PATH}")
传递可见性
在CMAKE有很多函数可以定义头文件目录或宏等,在函数中一个参数叫可见性的属性。这个属性在多CMakeLists中显得尤为重要。假设target A 依赖target B。那么target B部分定义的属性是否对于target A可见?
在cmake一般有如下三个可见性属性
<INTERFACE|PUBLIC|PRIVATE>
- INTERFACE 对自身不可见,但是对于依赖自身的target可见
- PUBLIC 对自身和依赖自身的target都可见
- PRIVATE 仅自身可见
我们举例如下
.
├── CMakeLists.txt
├── main.cpp
└── mycalclib├── CMakeLists.txt├── mycalc.cpp└── mycalc.h2 directories, 5 files
//main.cpp
#include "mycalclib/mycalc.h"
#include <iostream>
using namespace std;
int main(int argc, char *args[]) {mycalc d;d.run();
#ifdef FOOcout<<"main "<<"FOO "<<FOO<<endl;
#elsecout << "main " << "nothing" << endl;
#endifreturn 0;
}
# ./CMakeLists.txt
cmake_minimum_required(VERSION 3.26)
project(learnC)set(CMAKE_CXX_STANDARD 17)
add_subdirectory(mycalclib)
add_executable(learnC main.cpp)
target_link_libraries(learnC mycalc)
# ./mycalclib/CMakeLists.txt
cmake_minimum_required(VERSION 3.26)
project(mycalclib)
set(CMAKE_CXX_STANDARD 17)
add_library(mycalc STATIC mycalc.cpp)
#定义一个宏变量,名为mycalc,且是私有的也就是库本身才可见。
target_compile_definitions(mycalc PRIVATE FOO=1)
//mycalc.cpp
#include "mycalc.h"
#include <iostream>
using namespace std;
mycalc::mycalc() {
}
void mycalc::run() {
#ifdef FOOcout<<"mycalc "<<"FOO "<<FOO<<endl;
#elsecout << "mycalc " << "nothing" << endl;
#endif
}
//mycalc.h
#ifndef LEARNC_MYCALC_H
#define LEARNC_MYCALC_H
class mycalc{
public:mycalc();void run();
};
#endif //LEARNC_MYCALC_H
我们最后编译输出
mycalc FOO 1
main nothing
由于target_compile_definitions(mycalc PRIVATE FOO=1)
是私有定义,在main.cpp是不可见的,但是对于库本身是可见。我们改为INTERFACE
再次运行。
target_compile_definitions(mycalc INTERFACE FOO=1)
:
mycalc nothing
main FOO 1
public运行结果,target_compile_definitions(mycalc PUBLIC FOO=1)
:
mycalc FOO 1
main FOO 1
配置头文件
configure_file文档
camke提供了配置头文件,可以留用这个文件根据条件生成C++中的标准头文件等。
配置文件有一些奇特占位符语法:
- #cmakedefine VAR
- ${VAR}
- @VAR@
- $CACHE{VAR}
- 略
#cmakedefine VAR 表示如果cmake存在一个变量VAR可以让if返回为true,那么这一行会被换为#define VAR
${VAR}/@VAR@/$CACHE{VAR}
VAR 表示如果cmake存在一个变量VAR可以让if返回为true,那么这一行会被换为对应的变量.如果没有那么会替换为空
我们举例如下
假设我们的配置头文件叫foo.h.in
//foo.h.in
#cmakedefine FOO_STRING
#define FOO_STRING2 "${FOO_STRING2_VALUE}"
#cmakedefine FOO_STRING3
对应cmka文件
cmake_minimum_required(VERSION 3.14)
project(DEMO)
# 因为FOO_STRING 被定义了当if条件为变量时会为true.所以#cmakedefine FOO_STRING 转为 # define FOO_STRING
set(FOO_STRING "Enable")
# 因为FOO_STRING2_VALUE 被定义了当if条件为变量时会为true. 因此foo.h.in的FOO_STRING2_VALUE会被替换为I'm FOO_STRING2_VALUE
set(FOO_STRING2_VALUE "I'm FOO_STRING2_VALUE ")
# 定义了一个可选变量由于是OFF if会返回true。#cmakedefine FOO_STRING3 不会转化为# define FOO_STRING3
option(FOO_STRING3 "Use my math implementation" OFF)
#设置配置文件和对应的输出文件
configure_file(foo.h.in foo.h )
执行命令后会在当前目录生产一个foo.h文件
//foo.h
#define FOO_STRING
#define FOO_STRING2 "I'm FOO_STRING2_VALUE "
/* #undef FOO_STRING3 */
配置文件
CMake 保姆级教程【C/C++】
CMake 保姆级教程(上)
CMake 保姆级教程(下)
CMake官方教程