0%

Cpp-Tools-CMake

阅读更多

1 tutorial

1.1 step1: A Basic Starting Point

tutorial.cxx内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string>

int main(int argc, char* argv[])
{
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}

// convert input to double
const double inputValue = atof(argv[1]);

// calculate square root
const double outputValue = sqrt(inputValue);
std::cout << "The square root of " << inputValue << " is " << outputValue
<< std::endl;
return 0;
}

CMakeLists.txt内容如下:

  • cmake_minimum_required:用于指定cmake的最小版本,避免出现兼容性问题(包含了高级版本的特性,但是实际的cmake版本较小)
  • project:用于设置项目名称,并存储到CMAKE_PROJECT_NAME变量中,cmake中的一些环境变量会以这里指定的项目名称作为前缀,例如
    • PROJECT_SOURCE_DIR<PROJECT-NAME>_SOURCE_DIR
    • PROJECT_BINARY_DIR<PROJECT-NAME>_BINARY_DIR
  • set:用于设置一些变量
  • add_executable:添加目标可执行文件
1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# add the executable
add_executable(Tutorial tutorial.cxx)

此时文件结构如下:

1
2
3
.
├── CMakeLists.txt
└── tutorial.cxx

测试:

1
2
3
4
5
mkdir build
cd build
cmake ..
make
./Tutorial 256

1.2 step2: Adding a Library and Adding Usage Requirements for a Library

接下来,我们用自己实现的求开方的函数替换标准库中的实现。创建MathFunctions子目录,并在该子目录添加MathFunctions.h以及mysqrt.cxxCMakeLists.txt三个文件

MathFunctions/MathFunctions.h内容如下:

1
double mysqrt(double x);

MathFunctions/mysqrt.cxx内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}

double result = x;

// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}

MathFunctions/CMakeLists.txt内容如下:

1
add_library(MathFunctions mysqrt.cxx)

添加TutorialConfig.h.in文件,内容如下:

1
#cmakedefine USE_MYMATH

修改tutorial.cxx文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string>
#include "TutorialConfig.h"
#ifdef USE_MYMATH
# include "MathFunctions.h"
#endif

int main(int argc, char* argv[])
{
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}

// convert input to double
const double inputValue = atof(argv[1]);

// calculate square root
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif
std::cout << "The square root of " << inputValue << " is " << outputValue
<< std::endl;
return 0;
}

修改CMakeLists.txt文件,内容如下:

  • option:用于添加cmake选项,可以通过-D<OPTION-NAME>=ON/OFF参数来选择打开或者关闭该选项
    • 例如cmake .. -DUSE_MYMATH=OFF
  • configure_file:一般用于根据cmake选项动态生成头文件
  • if statement:控制流
  • add_subdirectory:用于将子目录添加到构建任务中
  • list:容器相关的操作
  • target_link_libraries:指定库文件
  • target_include_directories:指定头文件搜索路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

option(USE_MYMATH "Use tutorial provided math implementation" ON)

# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)

if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()

# add the executable
add_executable(Tutorial tutorial.cxx)

target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}
)

此时目录结构如下:

1
2
3
4
5
6
7
8
.
├── CMakeLists.txt
├── MathFunctions
│   ├── CMakeLists.txt
│   ├── MathFunctions.h
│   └── mysqrt.cxx
├── TutorialConfig.h.in
└── tutorial.cxx

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 使用的是自定义的sqrt函数
mkdir build
cd build
cmake ..
make
./Tutorial 256

# 使用的是标准库的sqrt函数
mkdir build
cd build
cmake .. -DUSE_MYMATH=OFF
make
./Tutorial 256

1.3 step3: Installing

现在,我们要安装make后产生的二进制、库文件、头文件

step2的基础上,修改MathFunctions/CMakeLists.txt文件,追加如下内容:

  • 其中这里指定了两个相对路径libinclude。前缀由cmake变量CMAKE_INSTALL_PREFIX确定,默认值为/usr/local
1
2
3
# add the install targets
install (TARGETS MathFunctions DESTINATION bin)
install(FILES MathFunctions.h DESTINATION include)

step2的基础上,修改CMakeLists.txt文件,追加如下内容:

1
2
3
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include)

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 使用默认的安装路径
mkdir build
cd build
cmake ..
make
make install

# 指定安装路径
mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/tmp/mydir
make
make install

1.4 step4: Testing

接下来,增加测试功能。在step2的基础上,修改CMakeLists.txt文件,追加如下内容:

  • add_test:用于增加测试,其中NAME指定的是测试用例的名称,RUN指定的是测试的命令
  • function:用于定义一个方法
  • set_tests_properties:用于设置测试项的属性,这里指定了测试结果的通配符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
enable_testing()

# does the application run
add_test(NAME Runs COMMAND Tutorial 25)

# does the usage message work?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)

# define a function to simplify adding tests
function(do_test target arg result)
add_test(NAME Comp_${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp_${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction(do_test)

# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is [-nan|nan|0]")
do_test(Tutorial 0.0001 "0.0001 is 0.01")

测试:

1
2
3
4
5
mkdir build
cd build
cmake ..
make
make test

1.5 step5: Adding System Introspection

同一个库,在不同平台上的实现可能不同,例如A平台有方法funcA,而B平台没有funcA,因此我们需要有一种机制来检测这种差异

接下来,增加测试功能。在step2的基础上,修改MathFunctions/CMakeLists.txt文件,追加如下内容:

  • include:加载cmake模块,这里加载了CheckSymbolExists模块,该模块用于检测指定文件中的指定符号是否存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
include(CheckSymbolExists)
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(NOT (HAVE_LOG AND HAVE_EXP))
unset(HAVE_LOG CACHE)
unset(HAVE_EXP CACHE)
set(CMAKE_REQUIRED_LIBRARIES "m")
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(HAVE_LOG AND HAVE_EXP)
target_link_libraries(MathFunctions PRIVATE m)
endif()
endif()

if(HAVE_LOG AND HAVE_EXP)
target_compile_definitions(MathFunctions
PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()

修改MathFunctions/mysqrt.cxx文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <cmath>

// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}

#if defined(HAVE_LOG) && defined(HAVE_EXP)
double result = exp(log(x) * 0.5);
std::cout << "Computing sqrt of " << x << " to be " << result
<< " using log and exp" << std::endl;
#else
double result = x;

// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
#endif
return result;
}

测试:

1
2
3
4
5
mkdir build
cd build
cmake ..
make
./Tutorial 25

2 target

cmake可以使用add_executableadd_libraryadd_custom_target等命令来定义目标target。与变量不同,目标在每个作用域都可见,且可以使用get_propertyset_property获取或设置其属性

3 variables

3.1 Frequently-Used Variables

参考(cmake-variables):

  • CMAKE_BINARY_DIRPROJECT_BINARY_DIR<PROJECT-NAME>_BINARY_DIR:指的是工程编译发生的目录。在递归处理子项目时,该变量不会发生改变
    • 若指定了-B参数,即-B参数指定的目录
    • 若没有指定-B参数,即执行cmake命令时所在的目录
  • CMAKE_SOURCE_DIRPROJECT_SOURCE_DIR<PROJECT-NAME>_SOURCE_DIR:指的是工程顶层目录。在递归处理子项目时,该变量不会发生改变
  • CMAKE_CURRENT_SOURCE_DIR:指的是当前处理的cmake项目的工程目录,一般来说是CMakeLists.txt所在目录。在递归处理子项目时,该变量会发生改变
  • CMAKE_CURRENT_BINARY_DIR:指的是工程编译结果存放的目标目录。在递归处理子项目时,该变量会发生改变。可以通过set命令或ADD_SUBDIRECTORY(src bin)改变这个变量的值,但是set(EXECUTABLE_OUTPUT_PATH <new_paht>)并不改变这个变量的值,只会影响最终的保存路径
  • CMAKE_CURRENT_LIST_DIR:指的是当前处理的CMakeLists.txt所在目录。在递归处理子项目时,该变量会发生改变
  • CMAKE_CURRENT_LIST_FILE:指的是当前处理的CMakeLists.txt的路径。在递归处理子项目时,该变量会发生改变
  • CMAKE_MODULE_PATHinclude()find_package()命令的模块搜索路径
  • EXECUTABLE_OUTPUT_PATHLIBRARY_OUTPUT_PATH:定义最终编译结果的二进制执行文件和库文件的存放目录
  • PROJECT_NAME:指的是通过set设置的PROJECT的名称
  • CMAKE_INCLUDE_PATHCMAKE_LIBRARY_PATH:这两个是系统变量而不是cmake变量,需要在bash中用export设置
  • CMAKE_MAJOR_VERSIONCMAKE_MINOR_VERSIONCMAKE_PATCH_VERSION:主版本号、次版本号,补丁版本号,2.4.6中的246
  • CMAKE_SYSTEM:系统名称,比如Linux-2.6.22
  • CMAKE_SYSTEM_NAME:不包含版本的系统名,比如Linux
  • CMAKE_SYSTEM_VERSION:系统版本,比如2.6.22
  • CMAKE_SYSTEM_PROCESSOR:处理器名称,比如i686
  • UNIX:在所有的类UNIX平台为TRUE,包括OS Xcygwin
  • WIN32:在所有的win32平台为TRUE,包括cygwin
  • ENV{NAME}:环境变量,通过set(ENV{NAME} value)设置,通过$ENV{NAME}引用
  • CMAKE_INSTALL_PREFIX:安装路径前缀

3.2 BUILD_SHARED_LIBS

该参数用于控制add_library指令,在缺省类型参数的情况下,生成静态还是动态库

4 property

4.1 INCLUDE_DIRECTORIES

去哪找头文件.h-I(GCC)

  1. include_directories:该方法会在全局维度添加include的搜索路径。这些搜索路径会被添加到所有target中去(包括所有sub target),会追加到所有targetINCLUDE_DIRECTORIES属性中去
  2. target_include_directories:该方法为指定target添加include的搜索路径,会追加到该targetINCLUDE_DIRECTORIES属性中去

如何查看全局维度以及target维度的INCLUDE_DIRECTORIES属性值

1
2
3
4
5
6
7
8
9
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
foreach(dir ${dirs})
message(STATUS "dir of include_directories='${dir}'")
endforeach()

get_target_property(target_dirs <TARGET-NAME> INCLUDE_DIRECTORIES)
foreach(target_dir ${target_dirs})
message(STATUS "dir of target_include_directories='${target_dir}'")
endforeach()

去哪找库文件.so/.dll/.lib/.dylib/...-L(GCC)

需要链接的库文件的名字:-l(GCC)

5 command

5.1 message

用于打印信息

格式:message([<mode>] "message text" ...)

合法的mode包括

  • FATAL_ERROR:致命错误,终止构建
  • SEND_ERROR:继续构建,终止generation
  • WARNING:告警信息,继续构建
  • AUTHOR_WARNING:告警信息,继续构建
  • DEPRECATION:当CMAKE_ERROR_DEPRECATEDCMAKE_WARN_DEPRECATED参数启用时,若使用了deprecated的特性,会打印error或者warn信息
  • NOTICE:通过stderr打印的信息
  • STATUS:用户最应该关注的信息
  • VERBOSE:项目构建者需要关注的信息
  • DEBUG:项目调试需要关注的信息
  • TRACE:最低级别的信息

5.2 set

set用于设置:cmake变量或环境变量

格式:

  • cmake变量:set(<variable> <value>... [PARENT_SCOPE])
  • 环境变量:set(ENV{<variable>} [<value>]

变量如何引用:

  • cmake变量:${<variable>}
  • 环境变量:$ENV{<variable>}

5.3 option

option用于设置构建选项

格式:option(<variable> "<help_text>" [value])

  • 其中value的可选值就是ONOFF,其中OFF是默认值

5.4 file

file用于文件操作

格式:

  • file(READ <filename> <out-var> [...])
  • file({WRITE | APPEND} <filename> <content>...)

操作类型说明:

  • READ:读取文件到变量中
  • WRITE:覆盖写,文件不存在就创建
  • APPEND:追加写,文件不存在就创建

5.5 add_executable

add_executable用于添加可执行文件,示例如下:

1
2
3
4
5
6
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])

# client.cpp ${PROTO_SRC} ${PROTO_HEADER} 都是源文件
add_executable(echo_client client.cpp ${PROTO_SRC} ${PROTO_HEADER})

5.6 add_library

add_library用于生成库文件,格式和示例如下:

  • <name>target名称
  • 第二个参数用于指定库文件类型,可以省略,由BUILD_SHARED_LIBS变量控制
    • STATIC:静态库
    • SHARED:动态库
1
2
3
4
5
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[<source>...])

add_library(Exec STATIC ${EXEC_FILES})

5.7 set_target_properties

为指定target设置属性

1
2
3
4
5
set_target_properties(xxx PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)

5.8 target_compile_options

为指定target设置编译参数

1
target_compile_options(xxx PUBLIC "-O3")

link_libraries

Link libraries to all targets added later.

1
2
link_libraries([item1 [item2 [...]]]
[[debug|optimized|general] <item>] ...)

target_link_libraries

Specify libraries or flags to use when linking a given target and/or its dependents. Usage requirements from linked library targets will be propagated. Usage requirements of a target’s dependencies affect compilation of its own sources.

Each <item> may be:

  • A library target name
  • A full path to a library file
  • A plain library name, like thread, dl
  • A link flag
1
2
3
4
target_link_libraries(<target> ... <item>... ...)

# example
target_link_libraries(echo_client ${BRPC_LIB} ${DYNAMIC_LIB})

When using the target_link_libraries command in CMake to link a target (such as a library), the related header file paths may automatically be included. This is because modern CMake manages projects based on the concept of “targets,” which allows targets to own and propagate attributes used for building and usage, such as include directories, definitions, compile options, etc.

This behavior is primarily achieved through “usage requirements.” When you set INTERFACE, PUBLIC, or PRIVATE properties for a target, these properties affect both the target itself and any other targets that link to it:

  • PRIVATE: These properties are only visible to the target that defines them and do not propagate to other targets that depend on this target.
  • PUBLIC: These properties apply to both the target that defines them and automatically propagate to any other targets that depend on this target.
  • INTERFACE: These properties do not apply to the target that defines them but do propagate to any other targets that depend on this target.

When a library specifies its header file paths in its CMake configuration file using the target_include_directories with the PUBLIC or INTERFACE keywords, these paths automatically become part of the include paths for targets that depend on this library. Therefore, when you link to such a library using target_link_libraries, you also implicitly gain access to these include paths.

This design greatly simplifies dependency management within projects, allowing maintainers to avoid explicitly specifying include paths, compiler, and linker configurations repeatedly. This is also one of the recommended best practices in modern CMake.

link_directories

Adds the paths in which the linker should search for libraries. Adds the paths in which the linker should search for libraries. Relative paths given to this command are interpreted as relative to the current source directory. The command will apply only to targets created after it is called.

By default the directories specified are appended onto the current list of directories. This default behavior can be changed by setting CMAKE_LINK_DIRECTORIES_BEFORE to ON. By using AFTER or BEFORE explicitly, you can select between appending and prepending, independent of the default.

1
link_directories([AFTER|BEFORE] directory1 [directory2 ...])

See the below example, only /pathA/lib will be added to the library search path.

1
2
3
4
5
link_directories(/pathA/lib)

add_executable(main)

link_directories(/pathB/lib)

target_link_directories

Add link directories to a target. Specifies the paths in which the linker should search for libraries when linking a given target. Each item can be an absolute or relative path, with the latter being interpreted as relative to the current source directory. These items will be added to the link command.

By using AFTER or BEFORE explicitly, you can select between appending and prepending, independent of the default.

1
2
3
target_link_directories(<target> [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

5.11 Include Directories

5.11.1 include_directories

include_directories

Add the given directories to those the compiler uses to search for include files. Relative paths are interpreted as relative to the current source directory.

The include directories are added to the INCLUDE_DIRECTORIES directory property for the current CMakeLists file. They are also added to the INCLUDE_DIRECTORIES target property for each target in the current CMakeLists file. The target property values are the ones used by the generators.

By default the directories specified are appended onto the current list of directories. This default behavior can be changed by setting CMAKE_INCLUDE_DIRECTORIES_BEFORE to ON. By using AFTER or BEFORE explicitly, you can select between appending and prepending, independent of the default.

1
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

5.11.2 target_include_directories

target_include_directories

The INTERFACE, PUBLIC and PRIVATE keywords are required to specify the scope of the following arguments. PRIVATE and PUBLIC items will populate the INCLUDE_DIRECTORIES property of <target>. PUBLIC and INTERFACE items will populate the INTERFACE_INCLUDE_DIRECTORIES property of <target>. The following arguments specify include directories.

1
2
3
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

5.12 add_subdirectory

add_subdirectory:用于引入一个cmake子项目

  • source_dir:子项目路径,该路径下必须包含CMakeLists.txt文件。且必须是子目录,不能是外层目录
  • binary_dir:二进制路径,生成的可执行文件或者库文件的放置路径
  • EXCLUDE_FROM_ALL:当指定该参数时,构建父项目时,若无明确依赖子项目(例如通过target_link_libraries添加依赖),那么子项目不会被自动构建。若有明确依赖,那么仍然会构建
1
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])

5.13 include

include:用于引入一个cmake子项目。例如include(src/merger/CMakeLists.txt)

includeadd_subdirectory这两个命令都可以用来引入一个cmake子项目,它们的区别在于:

  • add_subdirectory:该子项目会作为一个独立的cmake项目进行处理。所有CURRENT相关的变量都会进行切换。此外,CMakeLists.txt文件中涉及的所有相对路径,其base路径也会切换成add_subdirectory指定的目录
  • include:该子项目不会作为一个独立的cmake项目进行处理。只有CMAKE_CURRENT_LIST_DIRCMAKE_CURRENT_LIST_FILE这两个CURRENT变量会进行切换,而CMAKE_CURRENT_BINARY_DIRCMAKE_CURRENT_SOURCE_DIR不会进行切换。此外,CMakeLists.txt文件中涉及的所有相对路径,其base路径保持不变

5.14 find_package

本小节转载摘录自Cmake之深入理解find_package()的用法

为了方便我们在项目中引入外部依赖包,cmake官方为我们预定义了许多寻找依赖包的Module,他们存储在path_to_your_cmake/share/cmake-<version>/Modules目录下(例如/usr/local/lib/cmake-3.21.2-linux-x86_64/share/cmake-3.21/Modules)。每个以Find<LibaryName>.cmake命名的文件都可以帮我们找到一个包。注意,find_package(<LibaryName>)Find<LibaryName>.cmake中的<LibaryName>部分,大小写必须完全一致

我们以curl库为例,假设我们项目需要引入这个库,从网站中请求网页到本地,我们看到官方已经定义好了FindCURL.cmake。所以我们在CMakeLists.txt中可以直接用find_pakcage进行引用

对于系统预定义的Find<LibaryName>.cmake模块,使用方法如下,每一个模块都会定义以下几个变量(这些信息会在Find<LibaryName>.cmake文件的最上方注释中说明)。注意,这些变量命名只是规范,命名中<LibaryName>部分是全部大写还是包含大小写完全由Find<LibaryName>.cmake文件决定。一般来说是大写的,例如FindDemo.cmake中定义的变量名为DEMO_FOUND

  • <LibaryName>_FOUND
  • <LibaryName>_INCLUDE_DIR
  • <LibaryName>_LIBRARY:该模块通过add_library定义的名称
  • <LibaryName>_STATIC_LIB
1
2
3
4
5
6
7
8
find_package(CURL)
add_executable(curltest curltest.cc)
if(CURL_FOUND)
target_include_directories(clib PRIVATE ${CURL_INCLUDE_DIR})
target_link_libraries(curltest ${CURL_LIBRARY})
else(CURL_FOUND)
message(FATAL_ERROR ”CURL library not found”)
endif(CURL_FOUND)

你可以通过<LibaryName>_FOUND来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。如果<LibaryName>_FOUND为真,则将<LibaryName>_INCLUDE_DIR加入INCLUDE_DIRECTORIES

5.14.1 Add Non-Official Library

通过find_package引入非官方的库,该方式只对支持cmake编译安装的库有效

一般来说说,对于需要引入的三方库xxx,步骤通常如下

1
2
3
4
5
6
7
8
9
git clone https://github.com/xxx.git
cd xxx

mkdir build
cd build

cmake ..
make
make install

假设此时我们需要引入glog库来进行日志的记录,我们在Module目录下并没有找到FindGlog.cmake。所以我们需要自行安装glog库,再进行引用

1
2
3
4
5
6
7
8
9
10
git clone https://github.com/google/glog.git 
cd glog

mkdir build
cd build

# BUILD_SHARED_LIBS用于控制生成动态库还是静态库,默认是动态库,这里我们选择静态库
cmake -DBUILD_SHARED_LIBS=OFF ..
make -j $(( (cores=$(nproc))>1?cores/2:1 ))
make install

此时我们便可以通过与引入curl库一样的方式引入glog库了

1
2
3
4
5
6
7
8
find_package(GLOG)
add_executable(myexec glogtest.cc)
if(GLOG_FOUND)
# 由于glog在连接时将头文件直接链接到了库里面,所以这里不用显示调用target_include_directories
target_link_libraries(myexec glog::glog)
else(GLOG_FOUND)
message(FATAL_ERROR ”GLOG library not found”)
endif(GLOG_FOUND)

5.14.2 Module Mode & Config Mode

通过上文我们了解了通过cmake引入依赖库的基本用法。知其然也要知其所以然,find_package对我们来说是一个黑盒子,那么它是具体通过什么方式来查找到我们依赖的库文件的路径的呢。到这里我们就不得不聊到find_package的两种模式,一种是Module模式,也就是我们引入curl库的方式。另一种叫做Config模式,也就是引入glog库的模式。下面我们来详细介绍着两种方式的运行机制

Module模式中,cmake需要找到一个叫做Find<LibraryName>.cmake的文件。这个文件负责找到库所在的路径,为我们的项目引入头文件路径和库文件路径。cmake搜索这个文件的路径有两个,一个是上文提到的cmake安装目录下的share/cmake-<version>/Modules目录(例如/usr/local/lib/cmake-3.21.2-linux-x86_64/share/cmake-3.21/Modules),另一个使我们指定的CMAKE_MODULE_PATH的所在目录

如果Module模式搜索失败,没有找到对应的Find<LibraryName>.cmake文件,则转入Config模式进行搜索。它主要通过<LibraryName>Config.cmake<lower-case-package-name>-config.cmake这两个文件来引入我们需要的库。以我们刚刚安装的glog库为例,在我们安装之后,它在/usr/local/lib/cmake/glog/目录下生成了glog-config.cmake文件,而/usr/local/lib/cmake/glog/正是find_package函数的搜索路径之一

5.14.3 Create Customized Find<LibraryName>.cmake

假设我们编写了一个新的函数库,我们希望别的项目可以通过find_package对它进行引用我们应该怎么办呢。

我们在当前目录下新建一个ModuleMode的文件夹,在里面我们编写一个计算两个整数之和的一个简单的函数库。库函数以手工编写Makefile的方式进行安装,库文件安装在/usr/lib目录下,头文件放在/usr/include目录下。其中的Makefile文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 1、准备工作,编译方式、目标文件名、依赖库路径的定义。
CC = g++
CFLAGS := -Wall -O3 -std=c++11

OBJS = libadd.o #.o文件与.cpp文件同名
LIB = libadd.so # 目标文件名
INCLUDE = ./ # 头文件目录
HEADER = libadd.h # 头文件

all : $(LIB)

# 2. 生成.o文件
$(OBJS) : libadd.cc
$(CC) $(CFLAGS) -I ./ -fpic -c $< -o $@

# 3. 生成动态库文件
$(LIB) : $(OBJS)
rm -f $@
g++ $(OBJS) -shared -o $@
rm -f $(OBJS)

# 4. 删除中间过程生成的文件
clean:
rm -f $(OBJS) $(TARGET) $(LIB)

# 5.安装文件
install:
cp $(LIB) /usr/lib
cp $(HEADER) /usr/include

编译安装:

1
2
make
make install

接下来我们回到我们的cmake项目中来,在cmake文件夹下新建一个FindAdd.cmake的文件。我们的目标是找到库的头文件所在目录和共享库文件的所在位置

1
2
3
4
5
6
7
# 在指定目录下寻找头文件和动态库文件的位置,可以指定多个目标路径
find_path(ADD_INCLUDE_DIR libadd.h /usr/include/ /usr/local/include ${CMAKE_SOURCE_DIR}/ModuleMode)
find_library(ADD_LIBRARY NAMES add PATHS /usr/lib/add /usr/local/lib/add ${CMAKE_SOURCE_DIR}/ModuleMode)

if (ADD_INCLUDE_DIR AND ADD_LIBRARY)
set(ADD_FOUND TRUE)
endif (ADD_INCLUDE_DIR AND ADD_LIBRARY)

这时我们便可以像引用curl一样引入我们自定义的库了,在CMakeLists.txt添加

1
2
3
4
5
6
7
8
9
10
# 将项目目录下的cmake文件夹加入到CMAKE_MODULE_PATH中,让find_pakcage能够找到我们自定义的函数库
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}")
add_executable(addtest addtest.cc)
find_package(ADD)
if(ADD_FOUND)
target_include_directories(addtest PRIVATE ${ADD_INCLUDE_DIR})
target_link_libraries(addtest ${ADD_LIBRARY})
else(ADD_FOUND)
message(FATAL_ERROR "ADD library not found")
endif(ADD_FOUND)

5.15 find_library

find_library用于查找库文件,示例如下:

  • 所有指定的可能的名字中,只要有一个匹配上了,那么查找过程就终止了
  • CMAKE_FIND_LIBRARY_SUFFIXES可用于控制优先查找静态库还是优先查找动态库
    • set(CMAKE_FIND_LIBRARY_SUFFIXES ".so;.a"):动态库优先,默认值
    • set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so"):静态库优先
1
2
3
4
5
6
set(CMAKE_FIND_LIBRARY_SUFFIXES ".so;.a")
# set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so")
find_library(BOOST_FILESYSTEM_LIBRARY NAMES boost_filesystem Boost_filesystem)
find_library(BOOST_SYSTEM_LIBRARY NAMES boost_system Boost_system)

target_link_libraries(${PROJECT_NAME} PRIVATE ${BOOST_FILESYSTEM_LIBRARY} ${BOOST_SYSTEM_LIBRARY})

5.16 find_path

find_path用于查找包含给定文件的目录,示例如下:

  • 所有指定的可能的名字中,只要有一个匹配上了,那么查找过程就终止了
1
2
# NAMES 后可接多个可能的别名,结果保存到变量 BRPC_INCLUDE_PATH 中
find_path(BRPC_INCLUDE_PATH NAMES brpc/server.h)

5.17 aux_source_directory

Find all source files in a directory

5.18 PUBLIC vs. PRIVATE

In CMake, PUBLIC and PRIVATE are used to specify the visibility of target properties and dependencies. Here’s what they mean:

PUBLIC: A property or dependency marked as PUBLIC is visible to all targets that depend on the current target. This means that the property or dependency will be propagated to any targets that link against the current target.

  • For example, suppose you have a library target called “foo” that depends on another library called “bar”. If you mark the dependency on “bar” as PUBLIC, any target that links against “foo” will also link against “bar”.

PRIVATE: A property or dependency marked as PRIVATE is only visible to the current target. This means that the property or dependency will not be propagated to any targets that depend on the current target.

  • For example, suppose you have a library target called “foo” that uses a header file called “bar.h”. If you mark the header file as PRIVATE, any targets that depend on “foo” will not be able to access “bar.h”.

To summarize, PUBLIC properties and dependencies are visible to all targets that depend on the current target, while PRIVATE properties and dependencies are only visible to the current target.

示例如下:

1
2
3
4
5
6
7
8
9
.
├── CMakeLists.txt
├── bar
│   ├── bar.cpp
│   └── bar.h
├── foo
│   ├── foo.cpp
│   └── foo.h
└── main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# CMakeLists.txt
cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Visibility VERSION 1.0)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# add library bar
add_library(libbar bar/bar.cpp)
target_include_directories(libbar PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/bar)

# add library foo
add_library(libfoo foo/foo.cpp)
target_include_directories(libfoo PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/foo)
target_link_libraries(libfoo PUBLIC libbar)

# add the executable
add_executable(main main.cpp)
target_link_libraries(main PRIVATE libfoo)
EOF

mkdir -p foo

# foo/foo.h
cat > foo/foo.h << 'EOF'
#pragma once

#include <string>

void greet_foo(const std::string& name);
EOF

# foo/foo.cpp
cat > foo/foo.cpp << 'EOF'
#include "foo.h"

#include <iostream>

#include "bar.h"

void greet_foo(const std::string& name) {
std::cout << "Hello " << name << ", this is foo. And I'll take you to meet bar. ";
greet_bar("foo");
}
EOF

mkdir -p bar

# bar/bar.h
cat > bar/bar.h << 'EOF'
#pragma once

#include <string>

void greet_bar(const std::string& name);
EOF

# bar/bar.cpp
cat > bar/bar.cpp << 'EOF'
#include "bar.h"

#include <iostream>

void greet_bar(const std::string& name) {
std::cout << "Hello " << name << ", this is bar." << std::endl;
}
EOF

# main.cpp
cat > main.cpp << 'EOF'
#include "bar.h"
#include "foo.h"

int main() {
greet_foo("main");
greet_bar("main");
return 0;
}
EOF

cmake -B build && cmake --build build
build/main

如果将target_link_libraries(libfoo PUBLIC libbar)中的PUBLIC改成PRIVATE,那么编译将会无法通过,因为main没有显式依赖libbar,会找不到头文件bar.h

6 Tips

6.1 Command Line

  • cmake --help
    • Generators,默认使用Unix Makefiles
  • build
    • cmake <path-to-source>:当前目录作为<build_path>
    • cmake -S <path-to-source>:当前目录作为<build_path>
    • cmake -B <build_path>:当前目录作为<path-to-source>
    • cmake -B <build_path> <path-to-source>
    • cmake -B <build_path> -S <path-to-source>
  • cmake --build <build_path>:等效于在<build_path>中执行make命令
    • cmake --build <build_path> -j $(( (cores=$(nproc))>1?cores/2:1 )):等效于在<build_path>中执行make -j $(( (cores=$(nproc))>1?cores/2:1 ))命令
  • cmake --install <build_path>:等效于在<build_path>中执行make install命令

6.2 Print

6.2.1 Print All Variables

1
2
3
4
get_cmake_property(_variableNames VARIABLES)
foreach (_variableName ${_variableNames})
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()

6.2.2 Print All Envs

1
execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "environment")

6.2.3 Print All Compile Command

TheseWhen using the default generator Unix Makefiles, the following three methods are equivalent:

  • cmake -B <build_path> -DCMAKE_VERBOSE_MAKEFILE=ON
  • make VERBOSE=1
  • cmake --build <build_path> -- VERBOSE=1

6.3 Specify Compiler

6.3.1 Command

1
cmake -DCMAKE_CXX_COMPILER=/usr/local/bin/g++ -DCMAKE_C_COMPILER=/usr/local/bin/gcc ..

6.3.2 CMakeLists.txt

1
2
set(CMAKE_C_COMPILER "/path/to/gcc")
set(CMAKE_CXX_COMPILER "/path/to/g++")

6.4 Add Compile Options

6.4.1 Command

1
cmake -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} -O3" -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -O3" ..

6.4.2 CMakeLists.txt

示例如下:

1
2
3
4
5
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wall -fopt-info-vec")

set(CMAKE_BUILD_TYPE "Release")
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O1 -Wall")

CMAKE_BUILD_TYPE的所有可选值包括

  1. Debug
  2. Release
  3. RelWithDebInfo
  4. MinSizeRel

6.5 Specify Macro Definition

6.5.1 Command

1
2
cmake -B build -DUSE_XXX
cmake -B build -DVALUE_YYY=5

6.5.2 CMakeLists.txt

1
add_definitions(-DUSE_XXX -DVALUE_YYY=5)

6.6 Build Type

1
2
3
4
5
# If you want to build for debug (including source information, i.e. -g) when compiling, use
cmake -DCMAKE_BUILD_TYPE=Debug <path>

# cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo <path>
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo <path>

CMAKE_BUILD_TYPE的所有可选值包括

  1. Debug
  2. Release
  3. RelWithDebInfo
  4. MinSizeRel

6.7 Include All Source File

6.7.1 file

1
2
3
4
5
# Search for all .cpp and .h files in the current directory
file(GLOB MY_PROJECT_SOURCES "*.cpp")

# Add the found files to the executable
add_executable(MyExecutable ${MY_PROJECT_SOURCES})

If your project has a more complex structure and you wish to recursively search all subdirectories for files, you can replace the file(GLOB ...) command with file(GLOB_RECURSE …)`:

1
2
file(GLOB_RECURSE MY_PROJECT_SOURCES "*.cpp")
add_executable(MyExecutable ${MY_PROJECT_SOURCES})

6.7.2 aux_source_directory

如果同一个目录下有多个源文件,那么在使用add_executable命令的时候,如果要一个个填写,那么将会非常麻烦,并且后续维护的代价也很大

1
add_executable(Demo main.cxx opt1.cxx opt2.cxx)

我们可以使用aux_source_directory(<dir> <variable>))命令,该命令可以扫描一个目录下得所有源文件,并将文件列表赋值给一个变量

1
2
3
4
5
6
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)

# 指定生成目标
add_executable(Demo ${DIR_SRCS})

6.8 Library

6.8.1 Build Static Library By Default

Add following config to project’s root CMakeLists.txt, then all sub modules (imported via add_subdirectory) will be built in static way.

1
set(BUILD_SHARED_LIBS FALSE)

6.8.2 Import Library From Unified Thirdparty Directory

Suppose you have put all libraries in ${THIRDPARTY_DIR}/lib, then you can use the following config to import it.

1
2
add_library(protobuf STATIC IMPORTED)
set_target_properties(protobuf PROPERTIES IMPORTED_LOCATION ${THIRDPARTY_DIR}/lib/libprotobuf.a)

6.8.3 Priority: Static Libraries vs. Dynamic Libraries

6.8.3.1 find_package

The factors influencing the choice between static and dynamic libraries by the find_package command may include:

  • The provided CMake configuration files by the package: Some packages offer their own CMake configuration files, such as <PackageName>Config.cmake (Located at /usr/local/lib/cmake). These configuration files may explicitly specify the use of static or dynamic libraries, or make the selection based on the value of the BUILD_SHARED_LIBS variable (maybe).
    • For boost, it offers a variable named Boost_USE_STATIC_LIBS (Defined at /usr/local/lib/cmake/Boost-1.84.0/BoostConfig.cmake) to control whether to use static version or dynamic version.
  • Default behavior: Certain packages may use specific library types based on conventions or default settings. For instance, if a package typically provides dynamic libraries and does not have explicit configuration options to choose static libraries, the find_package command might default to using dynamic libraries.

Example:

1
2
set(Boost_USE_STATIC_LIBS ON)
find_package(Boost REQUIRED COMPONENTS filesystem system)

6.8.3.2 find_library

To control whether find_library should prefer static libraries or dynamic libraries, you typically set the CMAKE_FIND_LIBRARY_SUFFIXES variable. This variable specifies the suffixes that find_library searches for when looking for libraries.

Here’s how you can control find_library to prioritize static libraries or dynamic libraries:

Prioritize static libraries:

1
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so")

Prioritize dynamic libraries:

1
set(CMAKE_FIND_LIBRARY_SUFFIXES ".so;.a")

6.9 compile_commands.json

6.9.1 Manually Generate compile_commands.json

cmake指定参数-DCMAKE_EXPORT_COMPILE_COMMANDS=ON即可。构建完成后,会在构建目录生成compile_commands.json,里面包含了每个源文件的编译命令

6.9.2 Auto generate compile_commands.json and copy to project source root

参考Copy compile_commands.json to project root folder

1
2
3
4
5
6
add_custom_target(
copy-compile-commands ALL
${CMAKE_COMMAND} -E copy_if_different
${CMAKE_BINARY_DIR}/compile_commands.json
${CMAKE_SOURCE_DIR}
)

6.10 How to uninstall

After installation, there will be a install_manifest.txt recording all the installed files. So we can perform uninstallation by this file.

1
xargs rm < install_manifest.txt

6.11 Ignore -Werror

1
cmake --compile-no-warning-as-error -DWERROR=0 ...

7 Install

We can get binary distributions from Get the Software:

1
2
3
4
5
wget https://github.com/Kitware/CMake/releases/download/v3.21.2/cmake-3.21.2-linux-x86_64.tar.gz

tar -zxvf cmake-3.21.2-linux-x86_64.tar.gz -C /usr/local/lib

ln -s /usr/local/lib/cmake-3.21.2-linux-x86_64/bin/cmake /usr/local/bin/cmake

8 Reference