title: cmake-CMake记录
categories: CMake
tags: [cmake, makefile, 跨平台, 记录]
date: 2020-02-08 15:41:39
comments: false以前
测试工程: https://github.com/yangxuan0261/CMakeLab
前篇
- 官方
- 下载: 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 文件,所以一般建议新建一个新的目录,专门用来编译),然后执行下列操作:
cd build
cmake ..
make
其中 cmake … 在build里生成 Makefile,make根据生成 makefile 文件,编译程序,make 应当在有 Makefile 的目录下,根据 Makefile 生成可执行文件。
最简单 CMakeLists.txt 文件
最简单的一个工程需要有一个这样的 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.15)
project(Tutorial)
add_executable(Tutorial tutorial.cxx) // 或者 库: add_library(Tool01 SHARED library.cpp library.h)
ubuntu 安装 cmake
可以使用 apt install cmake
, 不过 18.04 安装的 cmake 版本是 3.10.2.
安装指定版本
-
去 GitHub 上下载指定的版本, 如: cmake-3.15.0.tar.gz
-
解压 并 编译
$ 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 目录下
-
拷贝 可执行文件 到系统目录
$ cp bin/* /usr/bin/ $ cmake --version # 查看版本 cmake version 3.15.0
-
清理安装源代码
$ cd .. $ rm -fr cmake-3.15.0
常用的预定义变量
- CMake中常用的预定义变量 - https://blog.csdn.net/u012086173/article/details/86480886
PROJECT_NAME
: 通过PROJECT指定的项目名称
project(Demo)
PROJECT_SOURCE_DIR
: 工程的根目录,上图中的Demo
目录
PROJECT_BINARY_DIR
: 执行cmake
命令的目录,一般是在build
目录,在此目录执行cmake ..
CMAKE_CURRENT_SOURCE_DIR
: 当前CMakeLists.txt文件所在目录
CMAKE_CURRENT_BINARY_DIR
: 编译目录,可使用ADD_SUBDIRECTORY
来修改此变量
# 添加cmake执行子目录
ADD_SUBDIRECTORY(example)
EXECUTABLE_OUTPUT_PATH
: 二进制可执行文件输出位置
# 设置可执行文件的输出路径为 build/bin
set(EXECUTABLE_OUTPUT_PATH ${
PROJECT_BINARY_DIR}/bin)
LIBRARY_OUTPUT_PATH
: 库文件输出位置
set(LIBRARY_OUTPUT_PATH ${
PROJECT_BINARY_DIR}/lib)
常用系统信息变量
$ cmake --version
cmake version 3.11.2
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++编译器
# 设置C++编译器为g++
set(CMAKE_CXX_COMPILER "g++")
# 设置标准库版本为c++17 并开启警告
set(CMAKE_CXX_FLAGS "-std=c++17 -Wall")
# 设置Debug模式下,不开启优化,开启调试,生成更详细的gdb调试信息
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb")
# 设置Release模式下,开启最高级优化
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
语法
- 蛋疼的语法 - https://juejin.im/post/5a73eba75188257a64266c15
拷贝文件
语法:
configure_file(<input> <output>
[COPYONLY] [ESCAPE_QUOTES] [@ONLY]
[NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ]
)
示例
# 拷贝文件
configure_file(
"${LINK_DIR}/libTool01.dll"
"${PROJECT_BINARY_DIR}/libTool01.dll"
COPYONLY
)
设置导出名称
set_target_properties(tool01 PROPERTIES OUTPUT_NAME "hello") # 修改导出的库名. 将原来导出为 libtool01.so 的库名修改为 libhello.so
添加子目录
可以参考: 有源码
作用是: 可以加入含有 CMakeLists.txt 的子目录, 一起构建, 可以理解为 有源码的库
语法:
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
例如
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
构建完后, src 生成的中间文件会在 执行命令时所在的目录的 bin 目录下
添加编译选项
把 CMAKE_CXX_FLAGS 内置变量拼接一下即可, 如: 拼接上 -lmingw32
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -lmingw32")
glob 匹配文件 - 批量导入
- 官方文档 - https://cmake.org/cmake/help/v3.12/command/file.html#glob-recurse
如果每加一个 .h/.cpp 文件都要在 add_executable 编译中加入, 未免有点麻烦
可以使用 glob 去匹配文件实现 批量导入
非递归 GLOB
# set(SOURCE_FILES main.cpp test.cpp test.h) # 指定文件
file(GLOB SOURCES_H_CPP # glob 匹配导入根目录文件, 不能递归子目录
*.h
*.cpp
)
set(SOURCE_FILES ${
SOURCES_H_CPP})
add_executable(TestCpp ${
SOURCE_FILES})
递归 GLOB_RECURSE
file(GLOB_RECURSE SRC_aaa # 递归 aaa 目录及子目录 中的所有 h/cpp 文件
aaa/**.h
aaa/**.cpp
)
set(SOURCE_FILES main.cpp ${SRC_aaa} )
add_executable(TestCpp ${SOURCE_FILES})
添加 预编译宏
# 添加 预编译宏
add_definitions(
-D USE_LOG=3 # 定义值
-D USE_FILTER
)
cpp 中使用
#if (USE_LOG == 3)
std::cout << USE_LOG << std::endl;
std::cout << "USE_LOG yes! " << std::endl;
#endif
#ifdef USE_FILTER
std::cout << "USE_FILTER yes!" << std::endl;
#endif
添加 第三方库
无源码
这种方式适用于 第三方库 没有源码, 只有 头文件 和 库文件
- CLion中使用CMake导入第三方库的方法 - https://blog.csdn.net/Haoran823/article/details/71657602
-
编写根目录 CMakeLists.txt 文件
-
引入 头文件 路径 和 动态库 路径, 位置要在
add_executable
之前set(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
之后target_link_libraries(TestCpp Tool)
-
-
build 一下.
"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
构建完执行 TestCpp.exe 会报错:
Process finished with exit code -1073741515 (0xC0000135)
, 原因是找不到 libTool.dll, 需要将 libTool.dll 掉与 TestCpp.exe 同一目录.-
Linux 环境下的编译
$ mkdir build # 新建一个 build 临时目录用来存放临时生产的 中间文件 $ cd build $ cmake .. # .. 是指 CMakeLists.txt 文件所在目录, 这里也就是上一级目录 $ make # 可执行程序就出来了
-
附:
-
完整 CMakeLists.txt
cmake_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)参数, 链接 库文件
```
有源码
这种方式适用于 第三方库 有源码, 只有 头文件 和 cpp文件
一般可视为项目的其他模块, 连同其他模块一起编译, 比如: 项目根目录下有个模块 Tool02
-
编写模块 Tool02 的 CMakeLists.txt 文件.
add_library(Tool02 library.cpp)
-
编写根目录 CMakeLists.txt 文件
-
引入 头文件 路径 和 模块目录 Tool02
include_directories ("${PROJECT_SOURCE_DIR}/Tool02") add_subdirectory (Tool02)
-
连接库, 位置要在
add_executable
之后target_link_libraries(TestCpp Tool02)
-
-
build 一下.
"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
cmake_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
语法:
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 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。
库 版本号
语法:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION指代动态库版本,SOVERSION指代API版本。将上述指令加入lib/CMakeLists.txt中,重新构建看看结果。
在build/lib目录会生成:
libhello.so.1.2
libhello.so.1->libhello.so.1.2
libhello.so ->libhello.so.1
安装 共享库/头文件
将libhello.a, libhello.so.x以及hello.h安装到系统目录,才能真正让其他人开发使用,在本例中我们将hello的共享库安装到/lib目录,将hello.h安装到/include/hello目录。
CMakeLists.txt中添加如下指令:
INSTALL(TARGETS hello hello1 hello2
LIBRARY DESTINATION lib # 动态库目的目录
ARCHIVE DESTINATION libstatic) # 静态库目的目录
INSTALL(FILES hello.h DESTINATION include/hello) # 文件的目录
注意,静态库要使用 ARCHIVE 关键字通过:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
make install
就可以将头文件和共享库安装到系统目录 /usr/lib 和 /usr/include/hello 中了。
安装
这里需要引入一个新的 cmake 指令 INSTALL和一个非常有用的变量
CMAKE_INSTALL_PREFIX
CMAKE_INSTALL_PREFIX 变量类似于 configure 脚本的 –prefix,常见的使用方法看起来是这个样子:
cmake -D CMAKE_INSTALL_PREFIX=/usr .
CMAKE_INSTALL_PREFIX 的默认定义是 /usr/local
INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。
INSTALL 指令包含了各种安装类型,我们需要一个个分开解释:
目标文件的安装:
INSTALL(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
] [...])
参数中的 TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的目标文件,可能是可执行二进制、动态库、静态库。目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。
DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候 CMAKE_INSTALL_PREFIX 其实就无效了。如果你希望使用 CMAKE_INSTALL_PREFIX 来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
举个简单的例子:
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
上面的例子会将:
可执行二进制 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, 会生成打包配置文件:
# 打包 源码 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 指定配置文件进行打包
$ cpack --config CPackSourceConfig.cmake
示例:
E:\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)