目录

CMake模块

模块是预先写好的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>_FLAGSCMAKE_<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