cmake-CMake记录
前篇
- 官方
- 下载: https://github.com/Kitware/CMake/releases
- cmake tutorial - https://cmake.org/cmake-tutorial
- CMake官方教程 - https://www.jianshu.com/p/6df3857462cd
- cmake使用教程 (翻译自官方) - https://juejin.im/post/5a6f32e86fb9a01ca6031230
- CMake-Cookbook - https://chenxiaowei.gitbook.io/cmake-cookbook/
CMake编译原理
CMake 是一种跨平台编译工具,比 make 更为高级,使用起来要方便得多。CMake 主要是编写 CMakeLists.txt 文件,然后用 cmake 命令将 CMakeLists.txt 文件转化为 make 所需要的 makefile 文件,最后用 make 命令编译源码生成可执行程序或共享库(so(shared object))。因此 CMake 的编译基本就两个步骤:
- cmake
- make
CMake说明
一般把 CMakeLists.txt 文件放在工程目录下,使用时,先创建一个叫 build 的文件夹(这个并非必须,因为 cmake 命令指向CMakeLists.txt 所在的目录,例如 cmake .. 表示 CMakeLists.txt 在当前目录的上一级目录。cmake 后会生成很多编译的中间文件以及 makefile 文件,所以一般建议新建一个新的目录,专门用来编译),然后执行下列操作:
1 | cd build |
其中 cmake .. 在build里生成 Makefile,make根据生成 makefile 文件,编译程序,make 应当在有 Makefile 的目录下,根据 Makefile 生成可执行文件。
最简单 CMakeLists.txt 文件
最简单的一个工程需要有一个这样的 CMakeLists.txt 文件
1 | cmake_minimum_required(VERSION 3.15) |
ubuntu 安装 cmake
可以使用 apt install cmake
, 不过 18.04 安装的 cmake 版本是 3.10.2.
安装指定版本
去 GitHub 上下载指定的版本, 如: cmake-3.15.0.tar.gz
解压 并 编译
1
2
3
4
5$ tar -xvzf cmake-3.15.0.tar.gz
$ cd cd cmake-3.15.0
$ ./bootstrap #执行引导文件, 该命令执行需要一定时间,请耐心等待。成功执行结束之后,末尾提示:CMake has bootstrapped. Now run make.
$ make
$ sudo make install生成的可执行文件都在 bin 目录下
拷贝 可执行文件 到系统目录
1
2
3$ cp bin/* /usr/bin/
$ cmake --version # 查看版本
cmake version 3.15.0清理安装源代码
1
2$ cd ..
$ rm -fr cmake-3.15.0
常用的预定义变量
- CMake中常用的预定义变量 - https://blog.csdn.net/u012086173/article/details/86480886
PROJECT_NAME
: 通过PROJECT指定的项目名称
1 | project(Demo) |
PROJECT_SOURCE_DIR
: 工程的根目录,上图中的Demo
目录PROJECT_BINARY_DIR
: 执行cmake
命令的目录,一般是在build
目录,在此目录执行cmake ..
CMAKE_CURRENT_SOURCE_DIR
: 当前CMakeLists.txt文件所在目录CMAKE_CURRENT_BINARY_DIR
: 编译目录,可使用ADD_SUBDIRECTORY
来修改此变量
1 | # 添加cmake执行子目录 |
EXECUTABLE_OUTPUT_PATH
: 二进制可执行文件输出位置
1 | # 设置可执行文件的输出路径为 build/bin |
LIBRARY_OUTPUT_PATH
: 库文件输出位置
1 | set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) |
常用系统信息变量
1 | $ cmake --version |
CMAKE_MAJOR_VERSION
: cmake 的 主版本号cmake version 3.11.2
中的3
CMAKE_MINOR_VERSION
: cmake 的 次版本号cmake version 3.11.2
中的11
CMAKE_PATCH_VERSION
: cmake 的 补丁等级cmake version 3.11.2
中的2
CMAKE_SYSTEM
: 系统名称,带版本号CMAKE_SYSTEM_NAME
: 系统名称,不带版本号CMAKE_SYSTEM_VERSION
: 系统版本号CMAKE_SYSTEM_PROCESSOR
: 处理器名称
编译选项
BUILD_SHARED_LIBS
: 默认的库编译方式(shared
or static
),默认为static
,一般在 ADD_LIBRARY
时直接指定编译库的类型CMAKE_C_FLAGS
: 设置C编译选项CMAKE_CXX_FLAGS
: 设置C++编译选项
CMAKE_CXX_FLAGS_DEBUG
: 设置编译类型为 Debug 时的编译选项CMAKE_CXX_FLAGS_RELEASE
: 设置编译类型为 Release 时的编译选项
CMAKE_CXX_COMPILER
: 设置C++编译器
1 | set (CMAKE_BUILD_TYPE "Debug") |
语法
拷贝文件 单个
语法:
1 | configure_file(<input> <output> |
示例
1 | # 拷贝文件 |
拷贝文件 批量
1 | set(libDstPath "${LINK_DIR}") |
设置导出名称
1 | set_target_properties(tool01 PROPERTIES OUTPUT_NAME "hello") # 修改导出的库名. 将原来导出为 libtool01.so 的库名修改为 libhello.so |
添加子目录
可以参考: 有源码
作用是: 可以加入含有 CMakeLists.txt 的子目录, 一起构建, 可以理解为 有源码的库
语法:
1 | ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) |
例如
1 | PROJECT(HELLO) |
构建完后, src 生成的中间文件会在 执行命令时所在的目录的 bin 目录下
添加编译选项
把 CMAKE_CXX_FLAGS 内置变量拼接一下即可, 如: 拼接上 -lmingw32
g++ 增加 -std=c++0x
参数
1 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -lmingw32") |
glob 匹配文件 - 批量导入
如果每加一个 .h/.cpp 文件都要在 add_executable 编译中加入, 未免有点麻烦
可以使用 glob 去匹配文件实现 批量导入
非递归 GLOB
1 | # set(SOURCE_FILES main.cpp test.cpp test.h) # 指定文件 |
递归 GLOB_RECURSE
1 | file(GLOB_RECURSE SRC_aaa # 递归 aaa 目录及子目录 中的所有 h/cpp 文件 |
添加 预编译宏
1 | # 添加 预编译宏 |
cpp 中使用
1 |
|
添加 第三方库
无源码
这种方式适用于 第三方库 没有源码, 只有 头文件 和 库文件
- CLion中使用CMake导入第三方库的方法 - https://blog.csdn.net/Haoran823/article/details/71657602
编写根目录 CMakeLists.txt 文件
引入 头文件 路径 和 动态库 路径, 位置要在
add_executable
之前1
2
3
4
5set(INC_DIR E:/ws_cpp/Tool)
set(LINK_DIR E:/ws_cpp/Tool/cmake-build-debug)
include_directories(${INC_DIR})
link_directories(${LINK_DIR})这里引入动态库文件是 libTool.dll, 所以这里写 Tool (lib的写法就是:
lib[name].dll
)连接库, 位置要在
add_executable
之后1
target_link_libraries(TestCpp Tool)
build 一下.
1
2
3"D:\CLion 2019.3.3\bin\cmake\win\bin\cmake.exe" --build E:\ws_cpp\TestCpp\cmake-build-debug --target TestCpp -- -j 6
[ 33%] Linking CXX executable TestCpp.exe
[100%] Built target TestCppLinux 环境下的编译
1
2
3
4$ mkdir build # 新建一个 build 临时目录用来存放临时生产的 中间文件
$ cd build
$ cmake .. # .. 是指 CMakeLists.txt 文件所在目录, 这里也就是上一级目录
$ make # 可执行程序就出来了构建完执行 TestCpp.exe 会报错:
Process finished with exit code -1073741515 (0xC0000135)
, 原因是找不到 libTool.dll, 可以通过两种方式解决:- 需要将 libTool.dll 丢到与 TestCpp.exe 同一目录.
- 将 libTool.dll 所在的目录加入环境变量.
附:
完整 CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12cmake_minimum_required(VERSION 3.15)
project(TestCpp)
set(CMAKE_CXX_STANDARD 14)
set(SOURCE_FILES main.cpp test.cpp test.h)
set(INC_DIR E:/ws_cpp/Tool)
set(LINK_DIR E:/ws_cpp/TestCpp/cmake-build-debug)
include_directories(${INC_DIR}) # 相当于gcc/clang 中的-I(i的大写字母)参数, 引入 头文件 目录
link_directories(${LINK_DIR}) # 相当于gcc 中的-L参数, 引入 库文件 目录
add_executable(TestCpp ${SOURCE_FILES})
target_link_libraries(TestCpp Tool) # 相当于gcc中的-l(小写的l)参数, 链接 库文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
#### 有源码
这种方式适用于 第三方库 有源码, 只有 *头文件* 和 *cpp文件*
一般可视为项目的其他模块, 连同其他模块一起编译, 比如: 项目根目录下有个模块 *Tool02*
1. 编写模块 *Tool02* 的 CMakeLists.txt 文件.
```json
add_library(Tool02 library.cpp)
编写根目录 CMakeLists.txt 文件
引入 头文件 路径 和 模块目录 Tool02
1
2include_directories ("${PROJECT_SOURCE_DIR}/Tool02")
add_subdirectory (Tool02)连接库, 位置要在
add_executable
之后1
target_link_libraries(TestCpp Tool02)
build 一下.
1
2
3"D:\CLion 2019.3.3\bin\cmake\win\bin\cmake.exe" --build E:\ws_cpp\TestCpp\cmake-build-debug --target TestCpp -- -j 6
[ 33%] Linking CXX executable TestCpp.exe
[100%] Built target TestCpp
附:
完整 CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13cmake_minimum_required(VERSION 3.15)
project(TestCpp)
set(CMAKE_CXX_STANDARD 14)
set(SOURCE_FILES main.cpp test.cpp test.h)
include_directories ("${PROJECT_SOURCE_DIR}/Tool02")
add_subdirectory (Tool02)
add_executable(TestCpp ${SOURCE_FILES})
target_link_libraries(TestCpp Tool02)
动态库 静态库
- 静态库与动态库构建 - https://www.cnblogs.com/52php/p/5681755.html
语法:
1 | ADD_LIBRARY(hello [SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL]source1 source2 ... sourceN) |
你不需要写全libhello.so,只需要填写hello即可,cmake系统会自动为你生成libhello.X。类型有三种:
- SHARED,动态库(扩展名为.so)
- STATIC,静态库(扩展名为.a)
- MODULE,在使用dyld的系统有效,如果不支持dyld,则被当作SHARED对待。
- EXCLUDE_FROM_ALL 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。
库 版本号
语法:
1 | SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1) |
VERSION指代动态库版本,SOVERSION指代API版本。将上述指令加入lib/CMakeLists.txt中,重新构建看看结果。
在build/lib目录会生成:
1 | libhello.so.1.2 |
安装 共享库/头文件
将libhello.a, libhello.so.x以及hello.h安装到系统目录,才能真正让其他人开发使用,在本例中我们将hello的共享库安装到
CMakeLists.txt中添加如下指令:
1 | INSTALL(TARGETS hello hello1 hello2 |
注意,静态库要使用 ARCHIVE 关键字通过:
1 | cmake -DCMAKE_INSTALL_PREFIX=/usr .. |
就可以将头文件和共享库安装到系统目录 /usr/lib 和 /usr/include/hello 中了。
debug/release 构建
两种方式都可以
cmake 命令时指定
1
2
3
4mkdir Release
cd Release
cmake -DCMAKE_BUILD_TYPE=Release .. # Release or Debug
makeCMakeLists.txt 文件中指定
1
set(CMAKE_BUILD_TYPE "Release") # Release or Debug
clion 中添加构建版本. 参考: c++_编辑器CLion.md 中的 debug/release 构建
安装
这里需要引入一个新的 cmake 指令 INSTALL和一个非常有用的变量 CMAKE_INSTALL_PREFIX
指定该值的两种方式
CMAKE_INSTALL_PREFIX 变量类似于 configure 脚本的 –prefix,可以在 cmake 编译时指定变量值
1
cmake -D CMAKE_INSTALL_PREFIX=/usr .
在 CMakeLists.txt 中指定
1
set(CMAKE_INSTALL_PREFIX "/usr")
CMAKE_INSTALL_PREFIX 的默认定义是 /usr/local
INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。
INSTALL 指令包含了各种安装类型,我们需要一个个分开解释:
目标文件的安装:
1 | INSTALL(TARGETS targets... |
参数中的 TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的目标文件,可能是可执行二进制、动态库、静态库。目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。
DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候 CMAKE_INSTALL_PREFIX 其实就无效了。如果你希望使用 CMAKE_INSTALL_PREFIX 来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
举个简单的例子:
1 | INSTALL(TARGETS myrun mylib mystaticlib |
上面的例子会将:
可执行二进制 myrun 安装到 ${CMAKE_INSTALL_PREFIX}/bin
目录, 动态库 libmylib 安装到 ${CMAKE_INSTALL_PREFIX}/lib
目录,静态库 libmystaticlib 安装到 ${CMAKE_INSTALL_PREFIX}/libstatic
目录,特别注意的是:你不需要关心 TARGETS 具体生成的路径,只需要写上 TARGETS 名称就可以了。
打包
- 11.1 生成源代码和二进制包 - https://chenxiaowei.gitbook.io/cmake-cookbook/11.0-chinese/11.1-chinese
- 使用cpack打包源码 (主要参考) - https://juejin.im/post/5b51b3e76fb9a04fc34c0c7b
CPack是作为一个模块出现在cmake构建系统中的,它是一个非常强大的打包工具,可以用来打包二进制文件或者源码。打包好的二进制文件中包含了所有的cmake install命令需要的安装文件。在打包源码时,也可以生成对应的压缩包。 cpack可以依赖cmake构建生成的config文件,也可以自己编写配置文件
流程
编写好 CMakeLists.txt 打包指令. 然后 build, 会生成打包配置文件:
1
2
3
4
5
6
7
8
9# 打包 源码
set(CPACK_SOURCE_GENERATOR "TGZ")
set(CPACK_SOURCE_PACKAGE_FILE_NAME ${PROJECT_NAME}-${PROJECT_VERSION_FULL}-src) # 打包文件名称
# 生成打包 源码 的配置: cmake-build-debug/CPackSourceConfig.cmake
# 打包 可执行文件
set(CPACK_GENERATOR "TGZ")
set(CPACK_PACKAGE_FILE_NAME ${PROJECT_NAME}-${PROJECT_VERSION_FULL}-bin) # 打包文件名称
# 生成打包 可执行文件 的配置: cmake-build-debug/CPackConfig.cmake使用 cpack 指定配置文件进行打包
1
$ cpack --config CPackSourceConfig.cmake
示例:
1
2
3
4
5
6
7E:\ws_cpp\CMakeLab\cmake-build-debug (master -> origin)
$ cpack --config CPackSourceConfig.cmake
CPack: Create package using TGZ
CPack: Install projects
CPack: - Install directory: E:/ws_cpp/CMakeLab
CPack: Create package
CPack: - package: E:/ws_cpp/CMakeLab/pack/CMakeLab-1.3.1-src.tar.gz generated.
打包格式
set(CPACK_SOURCE_GENERATOR “TGZ”)
- 7Z-7Zzip-(.7z)
- TBZ2(tar.bz2)
- TGZ(.tar.gz)
- TXZ(.tar.xz)
- TZ(.tar.Z)
- ZIP(.zip)
踩坑
报错: undefined reference to `WinMain’
是编译 可执行文件 时, 找不到 main 入口, 可能两个原因:
main 入口写错了, 正确的写法
1
2
3int main(int argc, char *argv[]) {
return 0;
}包含 main 入口的文件未加入到编译中, 应该
1
2
3set(SOURCE_FILES main.cpp)
message(STATUS "--- SOURCE_FILES: ${SOURCE_FILES}") # 查看所有文件
add_executable(CMakeLab ${SOURCE_FILES})
报错: exit code -1073741515 (0xC0000135)
原因是找不到 动态库, 可以通过两种方式解决:
- 需要将 动态库 丢到与 TestCpp.exe 同一目录.
- 将 动态库 所在的目录加入环境变量.