CMake
快速入门
一个大项目(Project)内嵌多个子项目(SubProject)
一个子项目内有src、include、CMakeLists.txt,其中有一个子项目中有main.cpp
最外面的CMakeLists.txt,负责连接所有子项目:
cmake_minimum_required(VERSION 3.20) |
subProject1(main.cpp所在的子项目)下面的CMakeLists.txt:
file(GLOB_RECURSE srcs CONFIGURE_DEPENDS src/*.cpp include/*.h) |
subProject2下面的CMakeLists.txt:
file(GLOB_RECURSE srcs CONFIGURE_DEPENDS src/*.cpp include/*.h) |
生成sln项目
$cmake -G "Visual Studio 16 2019" |
用vs打开sln项目能看到2+n个项目,其中
ALL_BUILD
:编译该项目会编译整个工程ZERO_CHECK
:监视CMakeLists.txt
的变化,一旦改变会告诉编译器重新构建工程
或者可以用make构建项目
$cmake -H. -Bbuild |
一:CMake基础语法
CMakeLists.txt
我们将CMake指令放在CMakeLists.txt
文件中
#设置CMake所需最低版本 |
- CMake语言不区分大小写,但参数区分大小写
- CMake的缺省默认语言为C++
构建
写好CMakeLists.txt
文件后,在命令行中输入:
cmake -H. -Bbuild |
这个命令会搜索当前目录下的根CMakeLists.txt
文件,创建一个build
目录,在其中生成所有的代码
然后再build目录中输入命令,以完成编译
cmake --build . |
一般我们不会在源码内部构建,因为这会污染源码的目录树
链接
如果项目中有多个文件,如
可以改目标生成
add_executable(hello main.cpp Message.cpp Message.h) |
但是这种改法太麻烦了,每添加一个文件就要在后面添一端,最后这东西会特别长
我们可以把这个类编译成一个(静态)库,然后再将库链接到可执行文件中(你还记得c++编译器的编译步骤吗?)
cmake_minimum_required(VERSION 3.20) |
此外,我们能在buid目录中找到一个名为/形如libmessage.a
的文件,这就是编译得到的静态库
add_library
生成一个名叫message
的库
add_library(message STATIC Message.h Message.cpp) |
-
第一个参数是目标名,后续可以使用该名来引用库
-
第二个参数是库的种类
- STATIC:静态库
- SHARED:动态库
- OBJECT:对象库(将代码编译到可执行文件内部的静态库)
- MODULE:一种不会链接到项目中任何目标的动态共享对象(DSO),可以运行时动态加载
此外CMake还有一些不会出现在构建系统里的库
- IMPORTED:项目外部的库,用于对现有依赖项进行构建,认为是不可变的
- INTERFACE:也是项目之外的库,但是可变
- ALIAS:对已有的库做别名
条件语句
在讲链接时,我们给出了两种编译方法,我们希望能在两种方式间切换
cmake_minimum_required(VERSION 3.20) |
逻辑变量
- true:
1
、ON
、YES
、true
、Y
、非零数 - false:
0
、OFF
、NO
、false
、N
、IGNORE
、NOTFOUND
、空字符串、以-NOTFOUND
为后缀
全局变量
CMake有一些全局变量,修改他们可以起到配置作用,这里设置的
set(BUILD_SHARED_LIBS OFF) |
当设置为OFF时,可以使add_library
不用传递第二个参数
变量名 | 含义 |
---|---|
CMAKE_RUNTIME_OUTPUT_DIRECTORY | .exe、.dll文件的输出路径 |
CMAKE_ARCHIVE_OUTPUT_DIRECTORY | .a文件的输出路径 |
CMAKE_LIBRARY_OUTPUT_DIRECTORY | .so文件的输出路径 |
CMAKE_CURRENT_SOURCE_DIR | 当前CMakeLists.txt所在路径 |
PROJECT_NAME | 项目名字 |
CMAKE_MODULE_PATH | cmake模块所在路径 |
用户选项
在上面我们引入了一个条件语句,但是是硬编码的。我们希望用户可以控制USE_LIBRARY
,于是可以使用option
#set(USE_LIBRARY OFF) |
将上面下面的set
替换为option
,运行
cmake -D USE_LIBRARY=ON |
如果是Clion可以配置
构建类型
类型 | 有无优化 |
---|---|
Debug | 没有优化,带调试符号 |
Release | 有优化,没有调试符号 |
RelWithDebInfo | 有少量优化,带调试符号 |
MinSizeRel | 不增加代码大小来优化 |
编译选项
cmake_minimum_required(VERSION 3.20) |
可见性 | 含义 |
---|---|
PRIVATE | 编译选项仅对目标生效,不会传递(hello链接了message,但不会接受message的编译选项) |
INTERFACE | 编译选项对目标生效,并传递给相关目标 |
PUBLIC | 编译选项对目标和使用它的目标生效 |
-Wall
、-Wextra
等是警告标志
如果A模块链接了core模块,B模块链接了A模块,如果B模块想用core的头文件,A在链接core时需要是PUBLIC
MSVC
配置MSVC编译选项
if(MSVC) |
循环
foreach(_source ${sources_with_lower_optimization}) |
命令
cmake中可以添加一些自定义命令,常用于实现编译前文件拷贝操作,比如将第三方.dll
文件复制到可执行文件到项目根路径
add_custom_command(TARGET ${PROJECT_NAME} |
命令执行时机:
POST_BUILD
:生成目标文件后PRE_BUILD
:编译前PRE_LINK
:链接前
执行的命令是Linux Command,比如copy ${source} ${target}
,echo ${output_string}
搜索
我们不可能将每一个文件都以单文件的形式写进CMakeLists.txt
中,于是我们需要按照某种规则搜索所有的文件
file(GLOB_RECURSE SRC_FILES_H "${SOURCE_DIR}/*.h") |
排除SRC中部分文件
list(FILTER SRC EXCLUDE REGEX "/ui/*") |
二:环境检查
检查平台
我们要处理如下的C++源码(hello-world.cpp)
std::string say_hello() { |
CMake可以加入
#查询操作系统 |
检查编译器
if(CMAKE_CXX_COMPILER_ID MATCHES Intel) |
检查处理器架构
if(CMAKE_SIZEOF_VOID_P EQUAL 8) |
三:链接外部库
语法
find_package
#查找名为OpenCV的包,如果没找到就报错 |
该函数的本质就是去(先去标准路径)寻找一个包名-config.cmake
文件
在mac,找OpenCV找的可能就是
/usr/lib/cmake/OpenCV/OpenCVConfig.cmake |
如果你安装的位置不是标准路径,你可以
- 在build时手动指定
-xxx_DIR="aaa/lib/cmake/xxx"
- 只有第一次指定,只要不删掉build目录,就不需要重新指定
- 可以在CMakeLists.txt最开头写
set(xx_DIR "aaa/lib/cmake/xxx")
、 - 可以给
xxx_DIR
设置环境变量
链接静态库
- 在项目根目录新建lib文件夹
- 将要链接的静态库(
test_library.a
)复制到lib文件夹中 - 找包
find_library(TEXT_LIBRARY test_library lib) |
- 链接
target_link_libraries(testapp LINK_PUBLIC &{TEST_LIBRARY}) |
链接动态库
常用库
Eigen
Eigen是一个纯头文件实现的线性代数库,在mac上可以使用brew安装
- 安装(记住eigen的版本)
brew install eigen |
- 将Eigen链接到系统文件夹(brew一般会自动链接)
brew link --overwrite eigen |
- 链接
#寻找Eigen包,并附带包版本 |
|
brew的用法
这里提一嘴Homebrew,这是一个mac上非常好用的包管理器,可以非常“优雅”地安装软件
brew会把软件安装在/usr/local/Cellar
目录
- 安装目录软链接到
/usr/local/opt
- bin目录执行文件链接到
/usr/local/bin
中(opt也有可能在根目录)
常用命令
$ brew -v # 安装完成后可以查看版本 |
拷贝动态库
有时为了可拓展性和编译速度,我们会将一个项目切分为多个模块,这些模块编译为dll,然后拷贝到主程序(exe)所在的目录
# 将 OptickCore.dll拷贝到 client.exe所在路径 |
四:项目
模块
我们可以将一个大的CMake源码分成一个个模块,将这些模块放在cmake
文件夹里,后缀为.cmake
如下的项目结构
. |
cmake/colors.cmake
文件内包含了一个色彩输出的定义
macro(define_colors) |
在CMakeLists.txt
引用
#将/cmake目录添加到路径列表 |
函数
function(函数名 参数1 参数2) |