文章目录
Modern CMake 2:基本介绍
最低版本
CMakeLists.txt的第一行通常都是:
cmake_minimum_required(VERSION 3.1)
请注意,cmake_minimun_required
是不区分大小写的,但根据Modern CMake 1:Modern CMake简介中的正确模式所言,函数名需要使用小写。在CMake3.12中,对最低版本的要求可以是一个区间,比如VERSION 3.1…3.12,因此,在新工程中我们应该这写:
cmake_minimun_requeired(VERSION 3.1...3.14)
if(${CMAKE_VERSION} VERSION_LESS 3.12)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
endif()
设置project
设置project通常看起来像这样:
project(MyProject VERSION 1.0
DESCRIPTION "Very nice project"
LANGUAGES CXX
)
可选语言有C,CXX,Fortran,CUDA(CMAKE3.7+)
。默认的是C CXX
。DESCRIPTION
是在CMake3.9引入的,可以对project进行描述。
生成可执行程序
add_executable(one two.cpp three.h)
one即时exe的名字,也是CMake target的名字。紧接着是源代码文件列表。
生成library
生成library,使用函数add_library,如:
add_library(one STATIC two.cpp three.h)
我们可以选择生成库的类型,如STATIC
,SHARED
或MODULE
。默认选项是BUILD_SHARED_LIBS
,将根据其值是否为真决定是STATIC
还是SHARED
。
给target添加包含目录
target_include_directories(one PUBLIC include)
target_include_directories
把include目录添加给target。PUBLIC
关键字对exe来说没有太大的意义;对于library,可以使CMake知道任何target链接此target的时候也同样需要include目录。另一关键字是PRIVATE
(仅影响到当前target,不传递给依赖项),INERFACE
(仅影响依赖项)。
链接目标,我们可以这么写:
add_library(anthor STATIC anthor.cpp anthor.h)
target_link_librayies(anthor PUBLIC one)
target可以包含目录,链接库(或链接目标),编译选项,编译定义,编译特性等。
变量和缓存
局部变量
set(MY_VARIABLE "value")
set(MY_LIST1 "one" "two")
set(MY_LIST2 "one;two")
变量通常用大写字母表示,变量值紧跟变量后面。访问变量使用${}
,比如${MY_VARIABLE}
。
CMake有作用域的概念,在出作用域(比如子目录下)后,变量将不再存在。当使用${}
引用变量时,要小心空格。尤其是含有空格的paths,引用时一定要用"${PATH}"
而不是${PATH}
.
Cache Variables
CMake提供Cache Variables用于从命令行设置变量。CMake提供了一些内置的Cache Variables,比如CMAKE_BUILD_TYPE
,自定义格式:
set(MY_CACHE_VARIABLE "value" CACHE STRING "Description")
Bool变量
option(MY_OPTION "This is settable from the command line" OFF)
Bool变量值可设置为ON
或者OFF
。
更多CMake变量参见:cmake-variables
环境变量
set(ENV{variable_name} value)
$ENV{variable_name}
注意,Modern CMake建议应尽可能的避免使用环境变量
缓存文件
CMake以纯文本的形式把设置存储在缓存文件CMakeCache.txt
中,使用缓存文件可避免每次运行cmake的时候从新输入你的设置。
属性
设置属性有两种方式:
set_property(TARGET TargetName
PROPERTY CXX_STANDARD 11)
set_target_property(TargetName PROPERTIES
CXX_STANDARD 11)
第一种方式更常用一些,你可以一次性的设置完成targets/files/tests
等。第二种用于单独给一个target设置多种属性。获取属性的方式为:
get_property(ResultVariable TARGET TargetName PROPERTY CXX_STANDARD)
更多CMake属性参见cmake-property
CMake 编程
控制流
if语句例子:
if(variable)
# 'ON','YES','TRUE','Y',或者非0数字
else()
# `0`, `OFF`, `NO`, `FALSE`, `N`, `IGNORE`, `NOTFOUND`, `""`, 或者以 `-NOTFOUND结尾
endif()
# 如果变量没有匹配上面任何一个,CMake将重新尝试
在CMake3.1+,你还可以这么写:
if("${variable}")
# True
else()
# False
endif()
生成表达式
生成表达式的功能非常强大。大部分的CMake命令发生在configure阶段,包括if控制流,但是生成表达式却可以在build阶段执行一些逻辑操作!
最简单的生成表达式是信息表达式,通常像这样$<KEYWORD>
,另外一种像这样$<KEYWORD:value>
,其中KEYWORD
是控制流关键字,值是0或者1。
比如,你只想在DEBUG
配置中加入一个编译选项,可以这么做:
target_compile_option(MyTarget PRIVATE "$<$<CONFIG:DEBUG>:--my-flag>")
这是一种比使用专有的*_DEBUG
变量更新的,更好的方式。
还有一种比较常用的生成式表达式:
target_include_directories(
MyTarget
PUBLIC
$<BUILD_INTERFACE:"${CMAKE_CURRENT_SOURCE_DIR}/include">
$<INSTALL_INTERFACE:include>
)
宏和函数
在CMake中定义宏和函数是非常简单的。宏和函数唯一的区别在于作用域,宏是没有作用域的。如果你希望函数中设置的变量可以给外部使用,那么你需要PARENT_SCOPE
标记。一个函数的简单例子如下:
function(SIMPLE_REQUIRED_ARG)
message(STATUS "Simple arguments:${REQUIRED_ARG}, follow by ${ARGV}")
set(${REQUIRED_ARG} "From SIMPLE" PARENT_SCOPE)
endfunction()
simple(This)
message("Output:${This}")
请注意,CMake中函数并没有返回值。所有参数都在ARGN
中.
参数
我们可以通过内置函数cmake_parse_arguments
使用CMake的命名变量系统。一个简单例子:
function(COMPLEX)
cmake_parse_arguments(
COMPLEX PREFIX
"SINGLE;ANOTHER"
"ONE_VALUE;ALSO_ONE_VALUE"
"MULTI_VALUES"
${ARGN}
)
endfunction()
complex(SIGNLE ONE_VALUE value MULTI_VALUES some other values)
此函数调用后,各个变量如下:
COMPLEX_PREFIX_SINGLE = TRUE
COMPLEX_PREFIX_ANOTHER = FALSE
COMPLEX_PREFIX_ONE_VALUE = "value"
COMPLEX_PREFIX_ALSO_ONE_VALUE = <UNDEFINED>
COMPLEX_PREFIX_MULTI_VALUES = "some;other;values"
代码交互
CMake允许你在代码中访问参数,这是通过configure_file
实现的。她是通过把.in
结尾的文件的内容替换到代码中实现的。这种方法被广泛应用,一个简单例子:
#pragma once
#define MY_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define MY_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define MY_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define MY_VERSION_TWEAK @PROJECT_VERSION_TWEAK@
#define MY_VERSION "@PROJECT_VERSION@"
CMake lines:
configure_file(
"${PROJECT_SOURCE_DIR}/include/Version.h.in"
"${PROJECT_SOURCE_DIR}/include/Version.h")
当然,构建的时候你需要把此包含目录包含在你的工程中。