本篇文章不是新手入门教学文章,主要是记录笔者个人的学习笔记
CMake入门(三)
- 一、CMake中的脚本指令
- 1. 设置CMake最低版本要求
- 2、指定项目名称
- 3、添加生成的可执行文件
- 4、添加生成库文件
- 5、搜索文件
- 6、生成文件
- 7、添加子目录
- 8、添加头文件的搜索路径
- 9、添加要链接库的路径
- 10、添加要连接的库
- 11、添加依赖
- 12、添加自定义目标
- 13、属性设置
- 14、为目标追加源文件
- 15、将 cmake 缓存变量标记为高级
- 二、日志输出
- 三、CMake内置的变量
一、CMake中的脚本指令
1. 设置CMake最低版本要求
# 用法
cmake_minimum_required(VERSION <版本号>)# 示例
cmake_minimum_required(VERSION 3.10)
2、指定项目名称
如果不做特殊配置,最终生成的可执行文件名与项目名称保持一致。
# 用法
project(<项目名>)# 示例 指定项目名称为Tutorial
project(Tutorial)
3、添加生成的可执行文件
利用指定的源文件在项目中添加可执行文件
# 用法: 源文件可以有多个,用空格隔开
add_executable(<可执行文件名> <源文件列表>)# 示例: 可执行文件名为Tutorial,用到的源文件为tutorial.cxx
add_executable(Tutorial tutorial.cxx)
4、添加生成库文件
# 用法
add_library(<name> [<type>] <sources>...)
# 示例
add_library(MathFunctions SHARED mysqrt.cxx MathFunctions.h)
这里的type
可以使用
STATIC
生成静态库SHARED
生成动态库
5、搜索文件
如果一个项目里边的源文件很多,在编写CMakeLists.txt文件的时候不可能将项目目录的各个文件一一罗列出来,这样太麻烦也不现实。所以,在CMake中为我们提供了搜索文件的命令,可以使用aux_source_directory
命令或者file
命令。
aux_source_directory
命令可以查找某个路径下的所有源文件(.c .cpp .cc .cxx .c++
) ,它不会递归查找子目录中的文件。
# 用法
aux_source_directory(< dir > < variable >)
# 示例
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST) # 搜索 src 目录下的源文件
file
命令用于搜索指定类型的文件
# 用法
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
GLOB
: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
GLOB_RECURSE
:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
# 示例
# 搜索当前目录的src目录下所有的源文件,并存储到变量中
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 示例
# 搜索当前目录的include目录下所有的头文件,并存储到变量中
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.{h,hpp})
6、生成文件
configure_file
是 CMake 中的一个命令,这个命令将一个.in
文件作为模板,将.in
文件复制为输出文件,并把其中的变量引用替换为CMakeLists.txt中定义的变量,如果变量未定义,则替换为空串。
-
输入文件中的变量引用CMakeLists.txt中变量的方式为: @变量名@ 或者 ${变量名}。
-
输入文件默认路径为CMakeLists.txt所在的路径,输出文件的路径默认为cmake生成文件所在的路径。
# 用法
configure_file(<inputfile> <outputfile>)# 示例
configure_file(TutorialConfig.h.in TutorialConfig.h)
在TutorialConfig.h.in 文件中
// 在CMakeLists.txt文件中,Tutorial_VERSION_MAJOR = 1 Tutorial_VERSION_MINOR = 0 STR_VAR = hello world
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR ${Tutorial_VERSION_MINOR}// 因为CMakeLists.txt中定义的字符串都是裸的,所以如果想要最终生成的头文件中变量的值为字符串,需要用双引号包起来
#define STR_VAR "@STR_VAR@"
经过configure_file(TutorialConfig.h.in TutorialConfig.h)
之后会生成一个TutorialConfig.h
的头文件,头文件中的内容是:
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0
#define STR_VAR "hello world"
通过这样的方式:我们可以将CMakeLists.txt
中的信息写入到头文件中,再编译到so库文件或者可执行程序中,这样,就可以通过提供库文件的接口或者可执行程序的打印中得到这些值了。
扩展1:
在模板文件.in
中支持一种语法叫#cmakedefine
, 其用法与#define
相同,用在configure_file
的输入文件当中进行宏定义,不同点在于:
#define
本身就是C/C++
当中的宏定义,所以不论对应的变量是否在CMakeLists.txt中有定义,都会在输出文件中定义一个宏。#cmakedfine
则会根据变量在CMakeLists.txt
中的定义情况来确定是否会在输出文件中定义宏。
- 如果变量在
CMakeLists.txt
中没有定义或已定义但是一个判断为假的布尔值,则不会在输出文件中定义对应的宏。- 如果变量在
CMakeLists.txt
中有定义且不为布尔值、或者为布尔值但判断为真,则会在输出文件中定义对应的宏。
扩展2:
configure_file
中有很多参数,这些参数这里只进行简单介绍,不在演示
NO_SOURCE_PERMISSIONS
生成文件时,不拷贝源文件的权限,对于新生成的文件,采用默认的普通文本权限。USE_SOURCE_PERMISSIONS
生成文件时,拷贝源文件的权限(默认)。COPYONLY
只拷贝文本,不进行任何替换ESCAPE_QUOTES
对于”
使用\
进行转义,防止出现问题@ONLY
只替换@变量名@
形式的变量NEWLINE_STYLE <style>
指定文件的换行符
7、添加子目录
为当前项目添加子目录。子目录当中必须包含一个CMakeLists.txt
文件,其中可以不写cmake_minimum_required
与project
。
# 用法
add_subdirectory(<source_dir>)
# 示例
add_subdirectory(MathFunctions)
8、添加头文件的搜索路径
include_directories
这是一个全局命令,它将路径添加到所有目标的头文件查找路径中。这意味着在这个命令后定义的所有目标(不论是库还是可执行目标)都能找到这个路径下的头文件。同样,子目录下定义的所有目标也都可以。尽管这个命令很方便,但在大型项目中,它可能会导致不必要的文件包含或者头文件命名冲突。
# 用法
include_directories(dir1 dir2 ...)
# 示例
include_directories(include)
target_include_directories
是一个目标级命令,它只影响一个特定的目标。这个命令允许你为一个特定的构建目标(例如一个库或一个可执行文件)指定头文件查找路径。
# 用法
target_include_directories(<target> <INTERFACE|PUBLIC|PRIVATE> <dir1 dir2 ...>)# 示例
target_include_directories(Tutorial PUBLIC ${PROJECT_BINARY_DIR})
PUBLIC
表示本目标需要用,依赖这个目标的其他目标也需要。INTERFACE
表示本目标不需要,依赖本目标的其他目标需要。PRIVATE
表示本目标需要,依赖这个目标的其他目标不需要。
9、添加要链接库的路径
link_directories
(directory1 directory2 …)
同理这也是一个全局命令,它将路径添加到所有目标的库文件查找路径中。
# 用法
link_directories(directory1 directory2 ...)
# 示例
link_directories(/path/to/my/library)
target_link_directories
是一个目标级命令,它只影响一个特定的目标。
# 用法
target_link_directories(target <PRIVATE|PUBLIC|INTERFACE> dir1 [dir2 ...])
# 示例
target_link_directories(MyExecutable PRIVATE ${CMAKE_SOURCE_DIR}/libs)
10、添加要连接的库
注意target_link_libraries
此命令必须在add_executable
,add_library
后面使用,因为此命令用于为特定目标添加链接库,所以在你使用它之前,你需要先定义这个目标,而add_executable
和 add_library
这两个命令就是用来定义目标的。
# 用法
target_link_libraries(<target><PRIVATE|PUBLIC|INTERFACE> <item>...[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)# 示例
target_link_libraries(Tutorial PUBLIC MathFunctions)
11、添加依赖
使用add_dependencies
可以给添加同级目标之间的相互依赖,保证依赖先生成, 其基本格式如下:
# 用法
add_dependencies(<target> [<target-dependency>]...)
# 示例
add_executable(Tutorial2 tutorial.cxx)
add_dependencies(Tutorial2 Tutorial) # 加上这句可以保证Tutorial 先于Tutorial2 生成
add_executable(Tutorial tutorial.cxx)
12、添加自定义目标
add_custom_target(Name [ALL] [command1 [args1...]][COMMAND command2 [args2...] ...][DEPENDS depend depend depend ... ][WORKING_DIRECTORY dir])
使用add_custom_target
可以添加一个自定义目标,对于自定义目标在Linux
下可以使用make help
来查看可以执行的目标。
ALL
添加了ALL
以后,当执行ALL目标(默认目标)时,会自动执行该自定义目标。COMMAND
后面跟上要执行的命令以及参数DEPENDS
后面加上该目标的依赖WORKING_DIRECTORY
设置该目标的工作目录
# 示例
add_custom_target(my_target1 ALL COMMAND echo "hello world")
add_custom_target(my_target2 COMMAND dir WORKING_DIRECTORY ../../)
13、属性设置
(这里有些复杂,可以不看)
属性的设置可以使用下面的指令:
set_property(<GLOBAL | # 设置全局属性DIRECTORY [<dir>] | # 设置目录的属性TARGET [<target1> ...] | # 设置目标的属性SOURCE [<src1> ...] # 设置源文件的属性[DIRECTORY <dirs> ...][TARGET_DIRECTORY <targets> ...] |INSTALL [<file1> ...] | # 设置安装目录的属性TEST [<test1> ...] | # 设置测试的属性CACHE [<entry1> ...] > # 设置缓存的属性[APPEND] [APPEND_STRING] # 添加属性PROPERTY <name> [<value1> ...])
读取属性:
get_property(<variable><GLOBAL |DIRECTORY [<dir>] |TARGET <target> |SOURCE <source>[DIRECTORY <dir> | TARGET_DIRECTORY <target>] |INSTALL <file> |TEST <test> |CACHE <entry> |VARIABLE >PROPERTY <name>[SET | DEFINED | BRIEF_DOCS | FULL_DOCS])
-
SET
:将<variable>
设为该属性是否已设置 -
DEFINED
:将<variable>
设为该属性是否已定义
全局属性
set_property(GLOBAL PROPERTY <name> [<value1> ...])get_property(<variable> GLOBAL PROPERTY <name>)
get_cmake_property(<var> <property>)
目录属性
set_property(DIRECTORY [<dir>] PROPERTY <name> [<value1> ...])
set_directory_properties(PROPERTIES prop1 value1 [prop2 value2] ...)get_property(<variable> DIRECTORY [<dir>] PROPERTY <name>)
get_directory_property(<variable> [DIRECTORY <dir>] <prop-name>)
目标属性
set_property(TARGET <target1> PROPERTY <name> [<value1> ...])
set_target_properties(target1... PROPERTIES prop1 value1 prop2 value2 ...)get_property(<variable> TARGET <target> PROPERTY <name>)
get_target_property(<VAR> target property)
源文件属性
set_property(SOURCE [<src1> ...] [DIRECTORY <dirs> ...] [TARGET_DIRECTORY <targets> ...] PROPERTY <name> [<value1> ...])
set_source_files_properties(<files> ...[DIRECTORY <dirs> ...][TARGET_DIRECTORY <targets> ...]PROPERTIES <prop1> <value1>[<prop2> <value2>] ...)get_property(<variable> SOURCE <source> PROPERTY <name>)
get_source_file_property(<variable> <file>[DIRECTORY <dir> | TARGET_DIRECTORY <target>]<property>)
测试属性
set_tests_properties(test1 [test2...] PROPERTIES prop1 value1 prop2 value2)get_test_property(test property VAR)
14、为目标追加源文件
target_sources
的基本格式如下:
# 用法
target_sources(<target><INTERFACE|PUBLIC|PRIVATE> [items1...][<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
# 示例
# add the executable
add_executable(Tutorial )target_sources(Tutorial PUBLIC tutorial.cxx)
15、将 cmake 缓存变量标记为高级
使用的命令如下:
mark_as_advanced([CLEAR|FORCE] <var1> ...)
设置完毕以后,除非cmake GUI 将show advanced
选项处于打开状态,否则高级变量不会显示在gui中。在脚本模式下,“高级/非高级”状态不起作用。
二、日志输出
在CMake中可以用用户显示一条日志消息,该命令的名字为message
:
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
- (无) :重要消息
STATUS
:非重要消息WARNING
:CMake 警告, 会继续执行AUTHOR_WARNING
:CMake 警告 (dev), 会继续执行SEND_ERROR
:CMake 错误, 继续执行,但是会跳过生成的步骤FATAL_ERROR
:CMake 错误, 终止所有处理过程
CMake的命令行工具会在stdout
上显示STATUS消息,在stderr
上显示其他所有消息。CMake的GUI会在它的log区域显示所有消息。
CMake警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符。
# 示例
# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")
三、CMake内置的变量
cmake
内置的变量有很多,详细介绍可以查看官方文档
这里只介绍一些关于编译器和系统相关的内置变量,通过这些变量我们能够根据编译器和系统进行条件编译。
ANDROID
安卓平台APPLE
苹果平台(macOS, iOS, tvOS, visionOS or watchOS)IOS
ios平台LINUX
Linux平台WIN32
windows平台MINGW
MINGW编译器MSVC
MSVC编译器
对于gcc, clang
由于没有内置的变量,如果想要判断需要采用下面的方式:
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")message(STATUS "当前编译器是 G++ 或 MinGW")
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")message(STATUS "当前编译器是 Clang")