常用 CMake 模板,边学边更新

环境

我个人使用的是 Ubuntu18.04,可以通过 sudo apt install cmake 来安装 CMake,在终端输入 cmake --version 可查看版本。

简单介绍

CMake 是个一个开源的跨平台自动化建构系统,用来管理软件建置的程序,并不依赖于某特定编译器,并可支持多层目录、多个应用程序与多个库。 它用配置文件控制建构过程 (build process) 的方式和 Unix 的 make 相似,只是 CMake 的配置文件取名为 CMakeLists.txtCMake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix的MakefileWindows Visual C++的projects/workspaces),然后再依一般的建构方式使用。

上面这句话摘自 Wiki,它说的是什么意思?如果你跟我一样在 linux 环境下工作,那么你一定听过 makemake 能够根据 makefile 文件中的说明来一步步地自动构建目标文件;make 是一种工具(可执行程序),makefile 是文件,里面说明了如何去构建目标文件,make 程序会从 makefile 中一条条地读出相关的指令去执行。那么 CMakemakefile 有什么关系呢?在没有 CMake 之前,我们需要自己动手写 makefile,然后去运行 make,而这需要你去学习 makefile 的语法,十分费时费精力;而现在有了 CMake,它可以根据你的指示(都写在 CMakeLists.txt 中)自动地去生成 makefile,然后你再用 make 去构建目标文件即可。写 CMakeLists.txt 比写 makefile 可容易太多了!

源文件与头文件都在根目录下

这种是最简单的情况,例如我的目录下有这些文件:

1
2
3
4
5
.
├── hello.c
├── main.c
├── hello.h
└── CMakeLists.txt

如果在 g++ 中执行时命令是这样的:g++ hello.c main.c -o main
那么我可以在当前目录下编写 CMakeLists.txt:

1
2
3
4
5
cmake_minimum_required(VERSION 2.8)

project(main VERSION 1.0)

add_executable(main hello.c main.c)

其中:

  • cmake_minimum_required(VERSION) 用来表示可接受的 CMake 最低版本
  • project(name) 用来定义项目名称
  • add_executable(name sources) 第一个参数是项目名称,第二个参数是源文件名(多个文件名之间用空格隔开)

编写完 CMakeLists.txt 文件后,执行以下命令:

1
2
3
4
$ mkdir build
$ cd build
$ cmake ../
$ make

可以看到目标文件已经构建完成!现在的目录层级是这样的:

1
2
3
4
5
6
7
8
.
├── build
│   ├── main
│   └── makefile ...
├── hello.c
├── main.c
├── hello.h
└── CMakeLists.txt

这里创建 build 目录是为了更好的层次化管理文件,build 目录内放置所有的二进制文件,而源文件和头文件一般都在项目根目录下

内置变量

CMake 定义了相当丰富的变量,然而,我常用的也就那几个。

name description
PROJECT_BINARY_DIR 是指包含最近的 project() 命令的 build 目录
PROJECT_SOURCE_DIR 是指包含最近的 project() 命令的 CMakeLists.txt 的目录
CMAKE_CURRENT_BINARY_DIR 当前处理的 CMakeLists.txt 所在的 build 目录
CMAKE_CURRENT_SOURCE_DIR 当前处理的 CMakeLists.txt 所在的目录
CMAKE_SOURCE_DIC 指定义了顶级 CMakeLists.txt 的目录
EXECUTABLE_OUTPUT_PATH 生成的可执行文件的存储目录
LIBRARY_OUTPUT_PATH 生成的库的存储目录
PROJECT_NAME 项目名称
PROJECT_VERSION_MAJOR 项目主版本号(例如 2.8 的主版本号是 2)
PROJECT_VERSION_MINOR 项目次版本号 (例如 2.8 的次版本号是 8)
PROJECT_VERSION_PATCH 项目版本的补丁号(例如 2.8.1 的补丁号是 1)
BUILD_SHARED_LIBS 用于控制 cmake 的 add_library 指令是否默认生成 动态so(yes if flag=on)还是 静态库.a (if flag=off)。默认是 flag=on
CMAKE_C_FLAGS 编译器 gcc 的标志
CMAKE_CXX_FLAGS 编译器 g++ 的标志

记不住变量的值时,可以使用 cmake 的 message 函数输出变量值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cmake_minimum_required(VERSION 2.8)

project(show_vars VERSION 1.0.1)

# 为了分行确定输出内容
message("")

message("1.PROJECT_BINARY_DIR = ${PROJECT_BINARY_DIR}")
message("2.PROJECT_SOURCE _DIR = ${_DIR}")
message("3.CMAKE_CURRRENT_BINARY_DIR = ${CMAKE_CURRRENT_BINARY_DIR}")
message("4.CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("5.CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("6.EXECUTABLE_OUTPUT_PATH = ${EXECUTABLE_OUTPUT_PATH}")
message("7.LIBRARY_OUTPUT_PATH = ${LIBRARY_OUTPUT_PATH}")
message("8.PROJECT_NAME = ${PROJECT_NAME}")
message("9.PROJECT_VERSION_MAJOR = ${PROJECT_VERSION_MAJOR}")
message("10.PROJECT_VERSION_MINOR = ${PROJECT_VERSION_MINOR}")
message("11.PROJECT_VERSION_PATCH = ${PROJECT_VERSION_PATCH}")
message("12.BUILD_SHARED_LIBS = ${BUILD_SHARED_LIBS}")
message("13.CMAKE_C_FLAGS = ${CMAKE_C_FLAGS}")
message("14.CMAKE_CXX_FLAGS = ${CMAKE_CXX_FLAGS}")

message("")

源文件和头文件分开存放

现在有以下目录结构:

1
2
3
4
5
6
7
8
.
├── build
├── CMakeLists.txt
├── include
│   └── hello.h
└── source
├── hello.cc
└── main.cc

这种情况下的 CMakeLists.txt 文件应该这样编写:

1
2
3
4
5
6
7
8
9
10
cmake_minimum_required(VERSION 2.8)

project(main)

set(SOURCES
${PROJECT_SOURCE_DIR}/source/*.cc
)
add_executable(main SOURCES)

target_include_directories(main PRIVATE ${PROJECT_SOURCE_DIR}/include)

set(name value) 可以自己定义一个变量,name 是变量名,value 是变量值

这里 PROJECT_SOURCE_DIR 的值就是 ./ (项目根目录)

target_include_directories( <PRIVATE|INTERFACE|PUBLIC> [item]) 是用来标明头文件路径的,相当于编译器选项的 -I, 这里 target 就是项目名称,<PRIVATE|INTERFACE|PUBLIC> 三种属性我现在还没搞懂。。。*[item]* 就是头文件路径。

你可以在 make 时开启输出模式 make VERBOSE=1 来看看编译器的参数,应该是:
g++ -I 根目录/include 根目录/hello.cc 根目录/main.cc -o 根目录/build/main

找路径

cmake find_path 命令用来寻找包含指定文件名称的目录。

通常,它的签名如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
find_path (
<VAR>
name | NAMES name1 [name2 ...]
[HINTS path1 [path2 ... ENV var]]
[PATHS path1 [path2 ... ENV var]]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[DOC "cache documentation string"]
[REQUIRED]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_SYSTEM_PATH]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH]
)

<VAR> 是一个变量,用于存放该命令得到的结果。它有点像 find 命令,如果在某个目录下找到了指定的文件名,目录名将会存到 <VAR> 变量中,并且 find 搜索将会停止!如果没有找到,结果将会是<VAR>-NOTFOUND

举个栗子就懂了,例如 muduo-tutorial 项目 cmake 目录下的 CMakeLists.txt 文件中有这样两行:

1
2
3
# set(MUDUO_PATH "/opt/muduo_hdrs_libs")
find_path(Muduo_INCLUDE_DIR muduo "${MUDUO_PATH}/include")
find_path(Muduo_LIBRARY_DIR libmuduo_net.a "${MUDUO_PATH}/lib")
1
2
3
4
5
6
$ ls /opt/muduo_hdrs_libs
inlude lib
$ ls /opt/muduo_hdrs_libs/include
muduo
$ ls /opt/muduo_hdrs_libs/lib
libmuduo_base.a libmuduo_http.a libmuduo_inspect.a libmuduo_net.a libmuduo_pubsub.a

因此我这里 Muduo_INCLUDE_DIR 就是 /opt/muduo_hdrs_libs/include,Muduo_LIBRARY_DIR 就是 /opt/muduo_hdrs_libs/lib

生成静态库或共享库文件

可以使用 add_library(libname, srcs) 来生产静态库

参考

1.https://mlog.club/article/1918025
2. https://cmake.org/cmake/help/latest/guide/tutorial/index.html#packaging-debug-and-release-step-12