【工具】CMake学习

CMake 教程

来自知乎的推荐ttroy50/cmake-examples: Useful CMake Examples (github.com)

01-basic

A-hello-cmake

基本的 Helloworld 示例。

1
2
3
4
5
$ tree 
.
├── CMakeLists.txt
├── main.cpp
└── README.adoc
1
2
3
4
5
6
7
8
9
10
# Set the minimum version of CMake that can be used
# To find the cmake version run
# $ cmake --version
cmake_minimum_required(VERSION 3.5)

# Set the project name
project (hello_cmake)

# Add an executable
add_executable(hello_cmake main.cpp)

CMakeLists.txt 是保存 CMake 命令的文件。cmake 运行时会在当前目录中寻找 CMakeLists.txt 文件,如果不存在则会报错。

  • cmake_minimum_required(VERSION 3.5) :指定 cmake 的最小版本要求
  • project (hello_cmake) :提供项目名称信息,也可以包括版本,使用编译语言等信息
  • add_executable(hello_cmake main.cpp)重要! 用于指明可执行文件要从哪个源文件被构建。在这个样例中,源文件是 main.cpp。第一个参数是编译得到的可执行文件名,第二个参数是需要编译的若干个源文件。

接下来编译这个例子。输入 cmake . 在当前目录下生成 Makefile 即可。也可以在其他目录下编译,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ mkdir build
$ cd build && cmake ..
$ $ tree .
.
├── CMakeCache.txt
├── CMakeFiles
│ ├── 3.10.2
│ │ ├── CMakeCCompiler.cmake
│ │ ├── CMakeCXXCompiler.cmake
│ │ ├── CMakeDetermineCompilerABI_C.bin
│ │ ├── CMakeDetermineCompilerABI_CXX.bin

... ...

├── cmake_install.cmake
└── Makefile

8 directories, 29 files

这样的好处是生成的若干中间文件不会冲散原有的目录结构。

B-hello-headers

1
2
3
4
5
6
7
8
9
10
11
$ tree
.
├── CMakeLists.txt
├── include
│ └── Hello.h
├── README.adoc
└── src
├── Hello.cpp
└── main.cpp

2 directories, 5 files

有头文件,也有两个源文件。放在不同的目录下。

目录路径

cmake 语法指明了一些变量,可用于帮助在您的项目或源代码树中找到有用的目录。

Variable Info
CMAKE_SOURCE_DIR The root source directory
CMAKE_CURRENT_SOURCE_DIR The current source directory if using sub-projects and directories.
PROJECT_SOURCE_DIR The source directory of the current cmake project.
CMAKE_BINARY_DIR The root binary / build directory. This is the directory where you ran the cmake command.
CMAKE_CURRENT_BINARY_DIR The build directory you are currently in.
PROJECT_BINARY_DIR The build directory for the current project.

源文件变量

除了上面提到的预定义的变量,也可以自己添加变量。例如可以创建一个包含源文件的变量,这样就能够更清晰地描述这些文件,并简便地将它们添加到其他命令语句中,例如add_executable()

1
2
3
4
5
6
7
# Create a sources variable with a link to all cpp files to compile
set(SOURCES
src/Hello.cpp
src/main.cpp
)

add_executable(${PROJECT_NAME} ${SOURCES})

Tip

现在的 cmake 并不推荐为源文件设置一个变量。相反,直接在 add_xxx 函数中直接输入源文件的名字更为通用。(那你前面介绍个棒槌??)

引用目录

当你有不同的 include 目录时,可以使用 target_include_derectories() 函数来告知编译器。这样编译时会添加 -I 参数来添加引用目录,例如 -I/directory/path

1
2
3
4
target_include_directories(target
PRIVATE
${PROJECT_SOURCE_DIR}/include
)

PRIVATE 指明了 include 的范围。这个会在下个部分解释,也可以参考这个函数的说明文档

编译

1
$ mkdir build && cd build && cmake ..

make 冗余输出(Verbose Output)

在前面的例子中,运行 make 时只能看到 build 的状态。如果想看到完整的调试信息,可以在 make 时添加 VERBOSE=1

1
2
3
4
5
6
$ make VERBOSE=1
/usr/bin/cmake -H/home/amjac/workspace/cmake-examples/01-basic/B-hello-headers -B/home/amjac/workspace/cmake-examples/01-basic/B-hello-headers/build --check-build-system CMakeFiles/Makefile.cmake 0
/usr/bin/cmake -E cmake_progress_start /home/amjac/workspace/cmake-examples/01-basic/B-hello-headers/build/CMakeFiles /home/amjac/workspace/cmake-examples/01-basic/B-hello-headers/build/CMakeFiles/progress.marks
make -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/amjac/workspace/cmake-examples/01-basic/B-hello-headers/build'
... ...

C-static-library

静态库指的是 .a

1
2
3
4
5
6
7
8
9
10
11
12
$ tree
.
├── CMakeLists.txt
├── include
│ └── static
│ └── Hello.h
├── README.adoc
└── src
├── Hello.cpp
└── main.cpp

3 directories, 5 files

add_library 用于创建一个库。STATIC

1
2
3
4
#Generate the static library from the library sources
add_library(hello_library STATIC
src/Hello.cpp
)

充填引用目录

我们再次用到了 target_include_directories 来设置 include 目录。这次设定的范围是 PUBLIC

1
2
3
4
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)

设定为 PUBLIC 将会导致 include 目录被用于以下场景:

  • 编译库时(hello_library)
  • 编译链接该库的任何附加目标时

这里解释范围的概念:

  • PRIVATE:仅用于编译该目标本身
  • INTERFACE:仅编译依赖于该目标的其他目标
  • PUBLIC:上述两种情况都包含

Tip

对于公共头文件,将 include 目录用子目录命名是一个明智的决定。因为传递给 target_include_directories 的路径应该是 include 目录的根路径(即应该把所有头文件放在一个目录下,可以在目录内部再组织新的目录,但是传递给该函数的时候传递根目录)。引用时也应该这样:

1
#include "static/Hello.h"

链接到一个库

当编译一个可执行文件必须用到库时,必须告知编译器。可以使用 target_link_libraries 函数。

1
2
3
4
5
6
7
8
9
10
# Add an executable with the above sources
add_executable(hello_binary
src/main.cpp
)

# link the new hello_library target with the hello_binary target
target_link_libraries( hello_binary
PRIVATE
hello_library
)

也可以使用 PUBLIC 或者 INTERFACE 来限定范围。

D-shared-library

1
2
3
4
5
6
7
8
9
10
11
12
$ tree
.
├── CMakeLists.txt
├── include
│ └── shared
│ └── Hello.h
├── README.adoc
└── src
├── Hello.cpp
└── main.cpp

3 directories, 5 files

添加一个动态链接库(共享库)

add_library 中添加 SHARED 即可:

1
2
3
add_library(hello_library SHARED
src/Hello.cpp
)

此时会创建 hello_library.so

别名目标(Alias Target)

顾名思义,别名目标是目标的替代名称,可用于代替只读上下文中的真实目标名称。

1
add_library(hello::library ALIAS hello_library)

这允许您在将目标链接到其他目标时使用别名来引用目标。

我不知道这个别名有什么用。百度了一下,ALIAS 目标主要用于为目标提供更多拼写或结构化名称,例如添加一个“命名空间”(例子中的 hello::)

将目标程序链接到一个共享库

仍然是 add_executabletarget_link_library 的组合。

1
2
3
4
5
6
7
8
9
10
# Add an executable with the above sources
add_executable(hello_binary
src/main.cpp
)

# link the new hello_library target with the hello_binary target
target_link_libraries( hello_binary
PRIVATE
hello::library
)

build

1
2
3
4
5
6
7
8
9
10
$ mkdir build && cd build && cmake ..
$ make
$ ldd hello_binary
linux-vdso.so.1 (0x00007fffc7f42000)
libhello_library.so => /home/amjac/workspace/cmake-examples/01-basic/D-shared-library/build/libhello_library.so (0x00007f63e3283000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f63e2e92000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f63e2b09000)
/lib64/ld-linux-x86-64.so.2 (0x00007f63e3687000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f63e276b000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f63e2553000)

可以看到,编译出的程序依赖 libhello_library.so

E-installing(跳过)

先跳过。

F-build-type

cmake 包含一系列内建的配置项,可以指明编译时的优化等级,以及是否包含调试信息等,build type 具体如下:

  • Release:添加 -O3 -DNDEBUG 参数
  • Debug : 添加 -g
  • MinSizeRel:添加 -Os -DNDEBUG
  • RelWithDebInfo : 添加 -O2 -g -DNDEBUG

-DNDEBUG 将在编译时定义 NDEBUG 宏。

1
2
3
4
5
6
7
8
$ tree
.
├── cmake-gui-build-type.png
├── CMakeLists.txt
├── main.cpp
└── README.adoc

0 directories, 4 files

设置 build type

有两种选择:

  • 在图形化界面选择(例如 cmake-gui )
  • 在编译时传递给 cmake(cmake .. -DCMAKE_BUILD_TYPE=Release

设置默认的 build type

1
2
3
4
5
6
7
8
# Set a default build type if none was specified
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message("Setting build type to 'RelWithDebInfo' as none was specified.")
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
# Set the possible values of build type for cmake-gui
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
"MinSizeRel" "RelWithDebInfo")
endif()

G-compile-flags

CMake 有多种方式来设置编译时的参数:

  • 使用 target_compile_definitions() 函数
  • 使用 CMAKE_C_FLAGSCMAKE_CXX_FLAGS 变量
1
2
3
4
5
6
7
$ tree
.
├── CMakeLists.txt
├── main.cpp
└── README.adoc

0 directories, 3 files

为每个目标设置 C++ 编译参数

推荐的方式是使用