模块是预先写好的CMake代码,通常放在一个目录中作为CMake发布的一部分。include命令可以将模块代码引入到当前作用域,语法如下:
1
|
include(module [OPTIONAL] [RESULT_VARIABLE myVar] [NO_POLICY_SCOPE])
|
CMake首先在变量CMAKE_MODULE_PATH定义的目录列表中按顺序搜索每个目录来查找模块文件,第一个匹配的文件将被使用。如果没有找到匹配的文件,CMake将在它自己的内部模块目录中搜索。一个常见的做法是将自己写的模块加到CMAKE_MODULE_PATH变量中,然后使用模块代码:
1
2
3
4
5
6
7
8
|
cmake_minimum_required(VERSION 3.0)
project(Example)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# Inject code from project-provided modules
include(CoolThings)
include(MyModule)
|
CMake查找模块的搜索顺序有一个例外,如果模块文件在CMake内部模块目录内,那么CMake先查找内部模块,然后查找CMAKE_MODULE_PATH指定的目录。
使用模块的另一个方法是使用find_package命令,其基本形式如下:
1
|
find_package(PackageName)
|
find_package命令与include命令非常相似,只是它查找一个名为FindPackageName.cmake的文件,而不是PackageName.cmake。接下来介绍一些有趣的CMake模块。
有用的开发辅助工具
CMakePrintHelpers模块提供了两个宏,可以方便地打印属性和变量的值。第一个宏如下:
1
2
3
4
5
6
7
|
cmake_print_properties([TARGETS target1 [target2...]]
[SOURCES source1 [source2...]]
[DIRECTORIES dir1 [dir2...]]
[TESTS test1 [test2...]]
[CACHE_ENTRIES var1 [var2...]]
PROPERTIES property1 [property2...]
)
|
这个宏实质上是将get_property和message合并为一个调用。
1
2
3
4
5
6
7
|
add_executable(myApp main.c)
add_executable(myAlias ALIAS myApp)
add_library(myLib STATIC src.cpp)
include(CMakePrintHelpers)
cmake_print_properties(TARGETS myApp myLib myAlias
PROPERTIES TYPE ALIASED_TARGET)
|
输出如下:
1
2
3
4
5
6
7
8
9
|
Properties for TARGET myApp:
myApp.TYPE = "EXECUTABLE"
myApp.ALIASED_TARGET = <NOTFOUND>
Properties for TARGET myLib:
myLib.TYPE = "STATIC_LIBRARY"
myLib.ALIASED_TARGET = <NOTFOUND>
Properties for TARGET myAlias:
myAlias.TYPE = "EXECUTABLE"
myAlias.ALIASED_TARGET = "myApp"
|
另一个宏用来打印一个或多个变量的值:
1
|
cmake_print_variables(var1 [var2...])
|
大小端
TestBigEndian模块中的test_big_endian宏,会编译一个小的测试程序来确定目标系统是大端还是小端,然后将测试结果缓存起来。语法如下:
1
2
3
|
include(TestBigEndian)
test_big_endian(isBigEndian)
message("Is target system big endian: ${isBigEndian}")
|
检查系统支持情况
CMake包含一些以Check开头的模块,用于检查系统对某个东西的支持情况。其工作方式是,编写少量的测试代码,然后尝试编译链接和运行,以确认代码中被测试的东西是否被支持。检查源码编译的模块形式为Check<LANG>SourceCompiles
,每个模块都提供一个相关的宏来执行测试:
1
2
3
4
5
6
7
8
|
include(CheckCSourceCompiles)
check_c_source_compiles(code resultVar [FAIL_REGEX regex])
include(CheckCXXSourceCompiles)
check_cxx_source_compiles(code resultVar [FAIL_REGEX regex])
include(CheckFortranSourceCompiles)
check_fortran_source_compiles(code resultVar [FAIL_REGEX regex] [SRC_EXT extension])
|
code参数是一个包含源代码的字符串。编译链接代码的结果保存在缓存变量resultVar中,true表示成功。在执行了一次测试后,后续的CMake运行将使用缓存的结果,即使被测试的代码改变了。如果指定了FAIL_REGEX选项,只要编译链接的输出与regex正则表达式相匹配,即使代码编译链接成功,检查也将被视为失败。
1
2
3
4
5
6
7
8
9
10
|
include(CheckCSourceCompiles)
check_c_source_compiles("
int main(int argc, char* argv[])
{
int myVar;
return 0;
}" noWarnUnused FAIL_REGEX "[Ww]arn")
if(noWarnUnused)
message("Unused variables do not generate warnings by default")
endif()
|
CMake包含一些形式为CMAKE_REQUIRED_...
的变量,它们能够影响代码的编译方式,可以在调用编译测试宏之前使用:
- CMAKE_REQUIRED_FLAGS:在
CMAKE_<LANG>_FLAGS
和CMAKE_<LANG>_FLAGS_<CONFIG>
变量之后,传递给编译器命令行的额外标志,它是一个单一的字符串,多个标志用空格隔开。
- CMAKE_REQUIRED_DEFINITIONS:一个编译选项定义的CMake列表,每个定义都以-DFOO或-DFOO=bar的形式指定。
- CMAKE_REQUIRED_INCLUDES:头文件查找路径,多个目录必须指定为CMake列表.
- CMAKE_REQUIRED_LIBRARIES:链接阶段使用的库列表,不要在库名前加上任何-l选项。
- CMAKE_REQUIRED_QUIET:如果这个选项存在,宏不会打印任何状态信息。
CMake还提供了测试C或C++代码是否能成功执行的模块。程序退出代码0为成功,所有其他值表示失败。
1
2
3
4
5
|
include(CheckCSourceRuns)
check_c_source_runs(code resultVar)
include(CheckCXXSourceRuns)
check_cxx_source_runs(code resultVar)
|
Check<LANG>CompilerFlag
模块用来检查对特定编译器标志的支持:
1
2
3
4
5
6
7
8
|
include(CheckCCompilerFlag)
check_c_compiler_flag(flag resultVar)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(flag resultVar)
include(CheckFortranCompilerFlag)
check_fortran_compiler_flag(flag resultVar)
|
CheckSymbolExists和CheckCXXSymbolExists模块都是用来检查一个特定的符号存在:
1
2
3
4
5
|
include(CheckSymbolExists)
check_symbol_exists(symbol headers resultVar)
include(CheckCXXSymbolExists)
check_cxx_symbol_exists(symbol headers resultVar)
|
如果一个符号是由一个库提供的,则该库必须作为测试的一部分被链接,这可以通过CMAKE_REQUIRED_LIBRARIES变量来完成:
1
2
3
4
5
6
|
include(CheckSymbolExists)
check_symbol_exists(sprintf stdio.h HAVE_SPRINTF)
include(CheckCXXSymbolExists)
set(CMAKE_REQUIRED_LIBRARIES SomeCxxSDK)
check_cxx_symbol_exists(SomeCxxInitFunc somecxxsdk.h HAVE_SOMECXXSDK)
|
以下这些模块可以查阅CMake相关文档了解它们的用法:CheckStructHasMember、CheckPrototypeDefinition、CheckTypeSize、CheckLanguage、CheckLibraryExists、CheckIncludeFile。
CMake提供了CMakePushCheckState模块用于快速保存和恢复各种CMAKE_REQUIRED_...
变量,它定义了以下三个宏:
1
2
3
|
cmake_push_check_state([RESET]) # RESET选项3.10版本开始支持
cmake_pop_check_state()
cmake_reset_check_state()
|
这些宏允许各种CMAKE_REQUIRED_…变量被视为一个集合,并将其状态推入/推出到虚拟堆栈中。例如:
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
|
include(CheckSymbolExists)
include(CMakePushCheckState)
# Start with a known state we can modify and undo later
cmake_push_check_state() # Could use RESET option, but needs CMake >= 3.10
cmake_reset_check_state() # Separate call, safe for all CMake versions
set(CMAKE_REQUIRED_FLAGS -Wall)
check_symbol_exists(FOO_VERSION foo/version.h HAVE_FOO)
if(HAVE_FOO)
# Preserve -Wall and add more things for extra checks
cmake_push_check_state()
set(CMAKE_REQUIRED_INCLUDES foo/inc.h foo/more.h)
set(CMAKE_REQUIRED_DEFINES -DFOOBXX=1)
check_symbol_exists(FOOBAR "" HAVE_FOOBAR)
check_symbol_exists(FOOBAZ "" HAVE_FOOBAZ)
check_symbol_exists(FOOBOO "" HAVE_FOOBOO)
cmake_pop_check_state()
# Now back to just -Wall
endif()
# Clear all the CMAKE_REQUIRED_... variables for this last check
cmake_reset_check_state()
check_symbol_exists(__TIME__ "" HAVE_PPTIME)
# Restore all CMAKE_REQUIRED_... variables to their original values
# from the top of this example
cmake_pop_check_state()
|
其他模块
以下是CMake支持的一些语言模块:
- CSharpUtilities
- FindCUDA
- FindJava, FindJNI, UseJava
- FindLua
- FindMatlab
- FindPerl, FindPerlLibs
- FindPython, FindPythonInterp
- FindPHP4
- FindRuby
- FindSWIG, UseSWIG
- FindTCL
- FortranCInterface