CMake 常用函数、技巧与错误 | C & C++

本文列举了常用的 CMake 中的常用函数、技巧与错误。

指令

find_package

find_package 的作用就是寻找第三方模块的头文件目录和库文件路径,并将其设为变量,返回提供给 CMakeLists.txt 其他部分使用。

  1. find_package 首先会寻找并执行模块相关的 .cmake 文件。寻找顺序如下:

    1. 在寻找模块的时候显式指定了模块目录:find_package(${module_name} REQUIRED PATHS ${module_dir})
    2. ${module_name}_DIR 变量中指定了模块目录
    3. 查找路径的根目录,默认查找目录如下:
      1. ${module_name}_DIR
      2. MAKE_PREFIX_PATH
      3. CMAKE_FRAMEWORK_PATH
      4. CMAKE_APPBUNDLE_PATH
      5. PATH:如果以 bin 或 sbin 结尾,则自动回退到上一级目录
    4. 检查上述目录下的这些目录:
      1. (lib/${arch}|lib|share)/cmake/${module_name}*/
      2. (lib/${arch}|lib|share)/${module_name}*/
      3. (lib/${arch}|lib|share)/${module_name}*/(cmake|CMake)/
    5. 寻找并执行 ${module_name}Config.cmakeFind${module_name}.cmake 脚本
  2. find_package 然后会设置以下几个变量:

  • ${module_name}_FOUND:是否找到该模块
  • ${module_name}_INCLUDE_DIR:模块头文件目录
  • ${module_name}_LIBRARY${module_name}_LIBRARIES:模块库文件路径

以 LibTorch 为例展示一个例子

1
2
3
4
find_package(Torch REQUIRED)  # REQUIRED 表明这个模块是必需的,如果找不到就报错
add_executable(torch_test "torch_test.cpp")
include_directories(${TORCH_INCLUDE_DIRS})
target_link_libraries(torch_test ${TORCH_LIBRARIES})

参考

cmake find_package路径详解

find_package的作用

file

查找某些文件并构成一个文件列表变量

1
2
file(GLOB LIB_CPP_SRCS ${CMAKE_SOURCE_DIR}/src/*.cpp)
file(GLOB_RECURSE LIB_CPP_SRCS ${CMAKE_SOURCE_DIR}/src/*.cpp)

list

1
2
3
4
5
6
7
8
9
10
list(LENGTH <list><output variable>)
list(GET <list> <elementindex> [<element index> ...]<output variable>)
list(APPEND <list><element> [<element> ...])
list(FIND <list> <value><output variable>)
list(INSERT <list><element_index> <element> [<element> ...])
list(REMOVE_ITEM <list> <value>[<value> ...])
list(REMOVE_AT <list><index> [<index> ...])
list(REMOVE_DUPLICATES <list>)
list(REVERSE <list>)
list(SORT <list>)
  • LENGTH:返回 list 的长度
  • GET:返回 list 中 index 的 element 到 value 中
  • APPEND:添加新 element 到 list中
  • FIND:返回 list 中 element 的 index,没有找到返回 -1
  • INSERT:将新 element 插入到 list 中 index 的位置
  • REMOVE_ITEM:从 list 中删除某个 element
  • REMOVE_AT:从 list 中删除指定 index 的 element
  • REMOVE_DUPLICATES:从 list 中删除重复的 element
  • REVERSE:将 list 的内容反转
  • SORT:将 list 按字母顺序排序

include_directories

将指定目录添加到编译器的头文件搜索路径之下,指定的目录被解释成当前源码路径的相对路径

1
include_directories(${CMAKE_SOURCE_DIR}/src)

该命令不会进行递归查找,正常情况下只需要 include src 目录即可,引用头文件时相对于 src 路径填写头文件路径

include

用于包含其他 cmake 文件

1
include_directories(${CMAKE_SOURCE_DIR}/third_path.cmake)

add_subdirectory

添加子目录,子目录中需要也有 cmake 文件,使用该命令即运行子目录的 cmake 文件。子目录中的 set 命令默认有效范围仅子目录和子目录的子目录,如果想令子目录的 set 在上层目录生效,需要加上 PARENT_SCOPE 参数,但是这样在子目录就无法生效了

1
add_subdirectory(src/proto)

add_library

将指定的源文件生成库文件

1
2
3
4
5
6
7
8
9
# SHARED,动态库
# STATIC,静态库
# MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。

SET(LIBHELLO_SRC hello.c)
add_library(hello_shared SHARED ${LIBHELLO_SRC})
set_target_properties(hello_shared PROPERTIES OUTPUT_NAME "hello")
add_library(hello_static STATIC ${LIBHELLO_SRC})
set_target_properties(hello_static PROPERTIES OUTPUT_NAME "hello")

这里 hello_shared 和 hello_static 名字必须不同,否则会忽略掉第二个库,所以使用 set_target_properties 命令重命名

add_executable

和 add_library 类似,将指定的源文件生成可执行文件

set_target_properties

1
set_target_properties(hello PROPERTIES VERSION 1.6.0. SOVERSION 1)

这条命令会生成三个文件:

1
2
3
libhello.so => libhello.so.1*
libhello.so.1 => libhello.so.1.6.0*
libhello.so.1.6.0

其中,libhello.so.1.6.0 为动态库的文件名(realname),libhello.so.1 为动态库的别名(soname),libhello.so 为动态库的链接名(linkname)

为 target 添加需要链接的共享库

1
target_link_libraries(${PROJECT_NAME} opencv_imgcodecs)

option

命令行参数

1
2
3
4
5
6
7
project(hello)

option(USE_XXX "option for use xxx" OFF)
if (USE_XXX)
add_definitions(-DUSE_XXX)
...
endif()
1
cmake -DUSE_XXX=ON ..

install

CMake之install方法的使用
【CMake】cmake的install指令

add_custom_command

和 install 相比,add_custom_command 更加灵活,下面展示一个用 add_custom_command 做拷贝的示例:

1
2
3
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${target_name}>
${path/to/dst})

target_sources

target_sources 用于向 target 中追加源文件,例如:

1
2
3
4
5
6
7
8
9
10
add_library(my_lib "")  # 这里的 "" 不能省略

target_sources(my_lib
PRIVATE
${CMAKE_CURRENT_DIR}/foo.cpp
${CMAKE_CURRENT_DIR}/bar.cpp
PUBLIC
${CMAKE_CURRENT_DIR}/foo.h
${CMAKE_CURRENT_DIR}/bar.h
)

注:

  • 如果在根目录通过 add_library 定义了一个 target,也可以在子目录中用 target_sources 命令往这个 target 中追加源文件
  • C++ 的源文件指定为 PRIVATE,是因为源文件只是在构建库文件时使用,头文件指定为 PUBLIC 是因为构建库文件和使用库文件时都会使用

参考

一步一步学Cmake 之 必学的二十个指令(1-10)

CMake - 使用 target_sources() 提高源文件处理能力

execute_process

1
2
3
4
5
6
7
8
execute_process(
COMMAND <command>
WORKING_DIRECTORY <directory>
RESULT_VARIABLE res_var # 子进程返回码或者错误描述字符串
OUTPUT_VARIABLE out_var # 标准输出
ERROR_VARIABLE err_var # 标准错误
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE)

如果 OUTPUT_VARIABLE 和 ERROR_VARIABLE 变量名相同,它们的输出将按照产生顺序被合并。

技巧

target_**() 中的 PRIVATE、INTERFACE 和 PUBLIC

target_link_libraries 为例:

1
2
target_link_libraries(A PRIVATE/INTERFACE/PUBLIC B)
target_link_libraries(C A)
  • PRIVATE:依赖项 B 仅链接到目标 A,不链接到 C
  • INTERFACE:依赖项 B 仅链接到目标 C,不链接到 A
  • PUBLIC:依赖项 B 链接到目标 A 和 目标 C

target_include_directories 同理

参考

CMake 中的 PUBLIC,PRIVATE,INTERFACE

macro 形参获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cmake_minimum_required (VERSION 3.5)

project(test)

macro(testarg var1 var2)
message("ARGC=${ARGC}")
message("ARGV=${ARGV}")
message("ARGN=${ARGN}")
message("ARGV0=${ARGV0}")
message("ARGV1=${ARGV1}")
message("ARGV2=${ARGV2}")
message("ARGV3=${ARGV3}")
message("ARGV4=${ARGV4}")
endmacro()

message("\n")
testarg(a b c d e)
message("\n")
testarg(a b)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ARGC=5
ARGV=a;b;c;d;e
ARGN=c;d;e
ARGV0=a
ARGV1=b
ARGV2=c
ARGV3=d
ARGV4=e

ARGC=2
ARGV=a;b
ARGN=
ARGV0=a
ARGV1=b
ARGV2=
ARGV3=
ARGV4=

参考

CMake中的ARGV,ARGN参数的理解

错误

Unknown CMake command “cuda_add_library”

在这之前使用 find_package(CUDA) 指令即可。

CMake 常用函数、技巧与错误 | C & C++

http://www.zh0ngtian.tech/posts/cacf258f.html

作者

zhongtian

发布于

2020-07-14

更新于

2023-12-16

许可协议

评论