内容:
姊妹篇:一步一步学CMake 之 必学的二十个指令(11-20)
1. add_libray
该指令的主要作用就是将指定的源文件生成链接文件,然后添加到工程中去
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
name:表示库文件的名字,该库文件会根据命令里列出的源文件来创建。
[STATIC | SHARED | MODULE]
STATIC:表示生成静态库,编译时生成
SHARED:表示生成共享(动态)库,运行时被加载
MODULE:一种不会被链接到其它目标中的插件,但是可能会在运行时动态加载。
OBJECT: 可以用来编译的源代码列表中给定的add_library到对象文件,但是既不将它们归档到静态库中,也不进行链接
它们变成一个共享库中。主要是使用在既需要创建静态库和又需要共享库时。
举个栗子:
add_library(math "")
target_sources(math
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.cpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.cpp
PUBLIC
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.hpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.hpp
)
首先声明一个库目标文件,默认不包含任何源文件,也可以使用target_sources来构成源文件。这里并未值是 STATIC 还是 SHARED 类型的库文件,如果不指定,则默认为 STATIC 类型的。
2. option
为用户提供一个可选项,该命令为用户提供了一个在ON和OFF中做出选择的选项。如果没有指定初始值,将会使用OFF作为初值。这个option命令和你本地是否存在编译缓存的关系很大。所以,如果你有关于 option 的改变,那么请你务必清理 CMakeCache.txt 和 CMakeFiles 文件夹。 请使用标准的 [initial value] 值 ON 或者 OFF。
option(<option_variable> "help string" [initial value])
<option_variable> 选项名.
"help string" 提示用户的信息.
[initial value] 变量出事值 ON or OFF .
举个栗子:
option(WITH_BLUEFS "libbluefs library" OFF)
构建过程中,会提示 libbluefs library 文字,等待用户输入(ON or OFF),输入的值保存到 WITH_BLUEFS 变量里。
3. execute_process
执行进程
这条命令可以执行一个或多个命令作为当前命令的子命令,将输出的结果保存到cmake变量或文件中,所有的进程使用单个的标准错误输出管道。
execute_process(COMMAND <cmd1> [args1...]]
[COMMAND <cmd2> [args2...] [...]]
[WORKING_DIRECTORY <directory>]
[TIMEOUT <seconds>]
[RESULT_VARIABLE <variable>]
[OUTPUT_VARIABLE <variable>]
[ERROR_VARIABLE <variable>]
[INPUT_FILE <file>]
[OUTPUT_FILE <file>]
[ERROR_FILE <file>]
[OUTPUT_QUIET]
[ERROR_QUIET]
[OUTPUT_STRIP_TRAILING_WHITESPACE]
[ERROR_STRIP_TRAILING_WHITESPACE])
如果指定了WORKING_DIRECTORY,则指定的目录将作为子进程当前的工作目录。
如果指定了TIMEOUT值,则如果在指定的时间内(以秒为单位计算,允许有小数位)子进程执行仍未完成,则将会被中断。
如果指定了RESULT_VARIABLE变量,则最后命令执行的结果将保存在该变量中,它是最后一个子进程执行完后的返回值或描述某种错误信息的字符串。
如果指定了OUTPUT_VARIABLE或ERROR_VARIABLE变量,则该变量会分别保存标准输出和标准错误输出的内容。
如果指定的变量是同一个,则输出会按产生的先后顺序保存在该变量中。
如果指定了INPUT_FILE, OUTPUT_FILE或ERROR_FILE等文件名,则它们会分别与第一个子进程的标准输入,最后一个子进程的标准输出以及所有子进程的标准错误输出相关联。
如果指定了OUTPUT_QUIET或ERROR_QUIET,则会忽略标准输出和错误输出。
如果在同一管道中同时指定了多个OUTPUT_*或ERROR_*选项,则优先级顺序是未知的(应避免这种情况)。
如果未指定任何OUTPUT_*或ERROR_*选项,则命令CMake所在进程共享输出管道。
举个栗子:
# Execute a tiny Python script
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "print('Hello, world!')"
RESULT_VARIABLE _status
OUTPUT_VARIABLE _hello_world
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
4. target_sources
往source文件中追加源文件, 比如:
add_executable(my_source "")
target_sources(my_source
PRIVATE
subSource.cpp)
当然如果你在根目录通过add_executable定义了一个输出目标,也可以可以在子目录中用target_sources命令往这个输出目标里面追加源文件。
举个栗子:
add_library(math "")
target_sources(math
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.cpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.cpp
PUBLIC
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.hpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.hpp
)
注意:C++的源文件指定为Private,是因为源文件只是在构建库文件是使用,头文件指定为Public是因为构建和编译时都会使用。
5. add_custom_command
为生成的构建系统添加一条自定义的构建规则。
两种使用方法,方法一:
增加一个自定义命令用来产生一个输出。
add_custom_command(OUTPUT output1 [output2 ...]
COMMAND command1[ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[MAIN_DEPENDENCYdepend]
[DEPENDS[depends...]]
[IMPLICIT_DEPENDS<lang1> depend1 ...]
[WORKING_DIRECTORYdir]
[COMMENT comment] [VERBATIM] [APPEND])
举个栗子:
set(wrap_BLAS_LAPACK_sources
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.hpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.cpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.hpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.cpp
)
add_custom_command(
OUTPUT
${wrap_BLAS_LAPACK_sources}
COMMAND
${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gz
COMMAND
${CMAKE_COMMAND} -E touch ${wrap_BLAS_LAPACK_sources}
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gz
COMMENT
"Unpacking C++ wrappers for BLAS/LAPACK"
VERBATIM
)
set命令是声明一个变量wrap_BLAS_LAPACK_sources来保存源文件的名字,此时这些文件还不存在,因为这些文件在压缩包里存着,那我们就需要将压缩包进行解压,将解压得到的文件赋值给wrap_BLAS_LAPACK_sources。
add_custom_command 命令就是自定义解压指令,将解压的文件赋值给wrap_BLAS_LAPACK_sources变量。
OUTPUT 输出,输出到wrap_BLAS_LAPACK_sources变量。
WORKING_DIRECTORY 指定在哪个目录下执行该命令。
DEPENDS 列出了自定义的这条命令的依赖项。
COMMENT 备注(提示信息),在构建时会打印出来。
VERBATIM 告诉CMake为指定的生成器和平台生成正确的命令,来确保其是与平台无关的,不依赖的平台的。
方法二:
add_custom_command(TARGET target
PRE_BUILD | PRE_LINK| POST_BUILD
COMMAND command1[ARGS] [args1...]
[COMMAND command2[ARGS] [args2...] ...]
[WORKING_DIRECTORYdir]
[COMMENT comment][VERBATIM])
add_custom_command的局限性:
1. It will only be valid if all of the targets depending on its output are specified
in the same CMakeLists.txt .
2. Using the same output as add_custom_command for different, independent targets
might re-execute the custom commands rule. This may cause conflicts and
should be avoided.
第二个局限性可以通过引入add_dependencies来避免,但是恰当的方法是使用add_custom_target
6. add_custom_target
7. function & macro
function(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endfunction(<name>)
macro(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endmacro(<name>)
相同点
CMake的 macro 和 function 都没有返回值。
和不同点:
CMake中函数和宏的主要区别是变量的作用域不同。通俗的讲:如果更改宏内的变量值,那么宏外相同名称的变量值也会跟着更改;如果更改函数体内的变量值,那么函数体外相同名称的变量值并不会跟着更改。当然,如果想搞更改函数内外的变量值,必须使用关键字PARENT_SCOPE说明。
还不懂?举个栗子:
macro(add_catch_test)
math(EXPR num_macro_calls "${num_macro_calls} + 1")
endmacro()
set(num_macro_calls 0)
add_catch_test()
add_catch_test()
message(STATUS "in total there were ${num_macro_calls} calls to add_catch_test")
两次调用add_catch_test()宏之后,num_macro_calls的值将会变成2。最终将输出:
-- in total there were 2 calls to add_catch_test
如果将宏add_catch_test改为函数之后,代码也能正常执行,但是最终返回的结果不是2,而是0。如果想搞更改函数内外的变量值,必须使用关键字PARENT_SCOPE说明:
set(variable_visible_outside "some value" PARENT_SCOPE)
8. check_cxx_compiler_flag
判断C++编译器是否支持 某flag,flag可以是 -g, -std=c++11等。
check_cxx_compiler_flag(<flag> <var>)
举个栗子:
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-std=c++11" COMPILER_SUPPORTS_CXX11)
unset(COMPILER_SUPPORTS_CXX11 CACHE)
if(COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
else()
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support")
endif()
使用check_cxx_compiler_flag宏之前需要包含CheckCXXCompilerFlag定义,检查的结果会存储在内部缓存里,并以COMPILER_SUPPORTS_CXX11命名,所以使用该变量之前最好unset一下。
check_c_compiler_flag使用方法类似,只是用于C语言。
9. configure_file
configure_file(<input> <output>
[COPYONLY] [ESCAPE_QUOTES] [@ONLY]
[NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
拷贝 input文件 到 其它地方output,并更改其中的内容。
把 input 文件中@VAR@ 或者 ${VAR} 引用的变量值 替换成 当前的变量值(注:CMake中的变量值)或者空串当变量未定义。input 默认是在 Source 文件夹里,也就是 ${CMAKE_CURRENT_SOURCE_DIR}/input,output 默认是在 Binary 文件夹里,也就是 ${CMAKE_CURRENT_BINARY_DIR}/output。
举个栗子:
configure_file(config.h.in config.h @ONLY)
输入文件为 config.h.in:
#pragma once
#define NUMBER_OF_LOGICAL_CORES @_NUMBER_OF_LOGICAL_CORES@
#define NUMBER_OF_PHYSICAL_CORES @_NUMBER_OF_PHYSICAL_CORES@
#define TOTAL_VIRTUAL_MEMORY @_TOTAL_VIRTUAL_MEMORY@
#define AVAILABLE_VIRTUAL_MEMORY @_AVAILABLE_VIRTUAL_MEMORY@
#define TOTAL_PHYSICAL_MEMORY @_TOTAL_PHYSICAL_MEMORY@
#define AVAILABLE_PHYSICAL_MEMORY @_AVAILABLE_PHYSICAL_MEMORY@
#define IS_64BIT @_IS_64BIT@
#define HAS_FPU @_HAS_FPU@
#define HAS_MMX @_HAS_MMX@
#define HAS_MMX_PLUS @_HAS_MMX_PLUS@
#define OS_NAME "@_OS_NAME@"
#define OS_RELEASE "@_OS_RELEASE@"
#define OS_VERSION "@_OS_VERSION@"
#define OS_PLATFORM "@_OS_PLATFORM@"
那么输出文件为config.h:
#pragma once
#define NUMBER_OF_LOGICAL_CORES 8
#define NUMBER_OF_PHYSICAL_CORES 4
#define TOTAL_VIRTUAL_MEMORY 16345
#define AVAILABLE_VIRTUAL_MEMORY 16345
#define TOTAL_PHYSICAL_MEMORY 16008
#define AVAILABLE_PHYSICAL_MEMORY 10178
#define IS_64BIT 1
#define HAS_FPU 1
#define HAS_MMX 1
#define HAS_MMX_PLUS 0
#define OS_NAME "Linux"
#define OS_RELEASE "4.8.0-36-generic"
#define OS_VERSION "#36~16.04.1-Ubuntu SMP Sun Feb 5 09:39:57 UTC 2017"
#define OS_PLATFORM "x86_64"
@ONLY 啥意思
限制变量的替换,因为 configure_file 命令会把输入文件中@或 $ 类型的变量都会替换成其变量值。@ONLY的意思是只替换@类型的变量,而不替换$类型的变量。这在配置 ${VAR} 语法的脚本时是非常有用的。
10.set_target_properties
为目标文件设置属性,语法如下:
set_target_properties(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
举个栗子:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe LANGUAGES CXX)
# generate an object library from sources
add_library(message-objs
OBJECT
Message.hpp
Message.cpp
)
# shared library
add_library(message-shared
SHARED
$<TARGET_OBJECTS:message-objs>
)
set_target_properties(message-shared
PROPERTIES
OUTPUT_NAME "message"
)
# static library
add_library(message-static
STATIC
$<TARGET_OBJECTS:message-objs>
)
set_target_properties(message-static
PROPERTIES
OUTPUT_NAME "message"
)
add_executable(hello-world hello-world.cpp)
target_link_libraries(hello-world message-static)
set_target_properties(message-shared PROPERTIES OUTPUT_NAME "message" )
set_target_properties(message-static PROPERTIES OUTPUT_NAME "message" )
把静态库 message-static 和 动态库 message-shared 的名字属性都改为"message"。