CMake 的简单使用
这是一篇简要介绍如何使用CMake的文章
引言
相比于make的繁琐,cmake不仅可以跨平台编译,还可以跨平台使用同一套代码。cmake的语法也比make简单很多,下面是一些常用的cmake命令和语法。
基础框架
1
2
3
4
cmake_minimum_required(VERSION 3.9) # cmake版本要求
project(hello) # 项目名称
set(CMAKE_CXX_STANDARD 11) # C++标准要求
add_executable(hello main.cpp) # 生成可执行文件hello,源文件为main.cpp
注意,不想make,在add_executable中不需要添加.o文件与.hpp文件,cmake会自动处理。
cmake基础命令
1 2 3 4 5 6 # 生成Makefile文件 cmake -S . -B build # -S指定源文件目录,-B指定输出目录 # 执行构建 cmake --build build # --build指定输出目录 # 运行程序 ./build/hello # hello为可执行文件名ps:第一条很多情况下是手动创建build目录,之后在build目录下执行
cmake ..
命令,-S .
一般省略。
cmake 中的库
对于一个程序main.cpp,比如他依赖另一个文件lib.cpp和lib.hpp,lib.cpp中有一个函数foo(),我们可以将lib.cpp编译成一个库,然后在main.cpp中调用foo()。这样的好处是,可以提升编译效率,如果库的代码有所改动,只需要重新编译库,而不需要重新编译main.cpp。
1
2
3
4
add_executable(main.cpp lib.cpp) # 直接编译成可执行文件
add_libiary(lib static lib.cpp) # 编译成静态库lib.a
target_link_libraries(main lib) # 链接库lib.a
注意:静态库的后缀名为.a,动态库的后缀名为.so。
目录层级
像上述那样将程序编译成库了之后,就可以将这部分程序单独放在一个文件夹中,不然所有文件都在一个层级下,代码会很乱。
比如放lib.cpp和lib.hpp放在lib文件夹下,这时在CMakeLists.txt中就需要添加lib目录了。 文件目录结构如下:
1
2
3
4
5
6
7
8
.
├── CMakeLists.txt
├── lib
│ ├── CMakeLists.txt
│ ├── include
│ │ └── lib.hpp
│ └── lib.cpp
└── main.cpp
1
add_subdirectory(lib) # 添加lib目录
编译库的命令则要放到lib目录下的CMakeLists.txt中。
1
2
3
add_libiary(lib static lib.cpp)
# 为该库添加头文件搜索路径
target_include_directories(lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) # CMAKE_CURRENT_SOURCE_DIR为当前cmakelist当前目录,pulic指的是可以被外部看到
一般库的头文件放在include目录下,且我们调用这个库的时候也只需要包含lib.hpp就可以了,因此搜索路径添加的是include目录。使用的时候
#include <lib.hpp>
即可。
查找库
在程序中如果使用一些系统库与第三方库,比如opencv,boost等,我们需要在CMakeLists.txt中添加查找库的命令。
1
2
3
4
find_package(OpenCV REQUIRED) # 查找opencv库,REQUIRED表示必须找到,否则报错
....
# 在最后将这个库链接进去
target_link_libraries(lib private ${OpenCV_LIBS}) # lib为库名,OpenCV_LIBS为opencv库的变量名
多层调用的情况
假设当前的目录结构为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.
├── CMakeLists.txt
├── lib
│ ├── CMakeLists.txt
│ ├── include
│ │ └── lib.hpp
│ └── lib.cpp
├── lib2
│ ├── CMakeLists.txt
│ ├── include
│ │ └── lib2.hpp
│ └── lib2.cpp
└── lib3
├── CMakeLists.txt
├── include
│ └── lib3.hpp
└── lib3.cpp
└── main.cpp
假设他们之间的调用关系为lib3依赖lib2,lib2依赖lib,main.cpp依赖lib3。 那么在lib3的CMakeLists.txt中需要添加lib2的目录,lib2的CMakeLists.txt中需要添加lib的目录。 而在main.cpp的CMakeLists.txt中需要添加这三个的目录的依赖,但是只需要添加lib3的链接。
1
2
3
4
5
6
# main.cpp的CMakeLists.txt
add_subdirectory(lib1)
add_subdirectory(lib2)
add_subdirectory(lib3)
target_link_libraries(main lib3) # 只需要链接lib3
变量
1
2
3
4
set(MY_VAR "hello") # 定义变量MY_VAR
set(MY_VAR "hello" CACHE STRING "hello") # 定义变量MY_VAR,并添加到缓存中,方便在cmake-gui中修改
target_compile_definitions(hello PRIVATE MY_VAR=${MY_VAR}) # 将变量MY_VAR添加到编译选项中,作用是在代码中可以使用这个变量
cache变量:用于持久化存储配置信息,其核心作用是 跨多次 CMake 运行保留用户或系统的配置选择。举例来说BUILD_TYPE变量用于指定是否开启debug模式,或者是否开启编译器优化等。
如何使用这个变量
1 2 3 4 5 6 7 #include <iostream> #include <lib.hpp> using namespace std; int main() { cout << MY_VAR << endl; // 输出hello return 0; }注意:变量的作用域是当前CMakeLists.txt文件,如果想在子目录中使用,需要使用
set(MY_VAR "hello" PARENT_SCOPE)
,或者在子目录中重新定义。在哪定义这个变量
1 cmake -D MY_VAR="hello" .. # 在命令行中定义变量注意:在命令行中定义的变量会覆盖CMakeLists.txt中定义的变量。也可以将cmake改为ccmake,这样会弹出一个界面,可以在里面修改变量的值。包括修改build-type等。
加载一些未安装的第三方库的方法
比如单元测试库googletest没有安装在系统中,我们可以下载并放在项目目录下。
1
2
3
4
5
6
FetchContent_Declare(
gtest
URL https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip
)
FetchContent_MakeAvailable(googletest) # 下载并解压
target_link_libraries(main gtest gtest_main) # 链接库
一般这种第三方库具体在cmake部分会有说明,直接复制粘贴就可以了。
注意:FetchContent_Declare的URL可以是本地路径,也可以是网络路径。
macro与function
类似于函数的作用,主要是为了简化代码。二者的区别在于,macro是直接展开的,而function是调用的。也就是说marco中set的变量后面还可以使用,而function中set的变量在函数外部是不可见的。
1
2
3
4
5
6
macro(hello name) # 定义宏hello,参数为name
message("hello ${name}") # 输出hello name
endmacro() # 结束宏定义
hello("world") # 调用宏hello,参数为world