CMakePrintHelpers模块提供了两个宏,可以在开发过程中更方便地打印属性和变量的值。它们不是为了永久使用,而是为了帮助开发人员快速、轻松地临时记录信息,以帮助调查项目中的问题。
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()组合成一个单独的调用。必须指定一种属性类型,并为列出的每个实体打印每个命名的属性。当记录多个实体和/或属性的信息时,它特别方便。例如:
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)
输出:
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)
该模块还提供了一个类似的函数来记录一个或多个变量的值:
cmake_print_variables(var1 [var2...])
这适用于所有的变量,无论它们是否被项目显式设置,是否被CMake自动设置或根本没有设置。
set(foo "My variable")
unset(bar)
include(CMakePrintHelpers)
cmake_print_variables(foo bar CMAKE_VERSION)
上面的输出类似如下:
foo="My variable" ; bar="" ; CMAKE_VERSION="3.8.2"
10.2 字节顺序
在处理针对各种体系结构的嵌入式平台或项目时,希望项目能够了解目标系统的端序。TestBigEndian模块提供了test_big_endian()宏,该宏编译一个小的测试程序来确定目标的字节顺序。这个结果会被缓存,这样以后运行CMake就不必重做测试了。该宏只接受一个参数,即存储布尔结果的变量名(true表示系统是大端序):
include(TestBigEndian)
test_big_endian(isBigEndian)
message("Is target system big endian: ${isBigEndian}")
10.3 检查存在和支持
CMake模块涵盖的一个更全面的领域是检查是否存在或支持各种各样的东西。这一系列模块基本上都以相同的方式工作,编写少量的测试代码,然后尝试编译并可能链接并运行生成的可执行文件,以确认是否支持代码中正在测试的内容。所有这些模块都有一个以Check开头的名称。
一些更基础的Check…模块将一个简短的测试文件编译并链接到一个可执行文件中,并返回一个成功/失败的结果。这些模块的名字形式为Check<LANG>SourceCompiles,每个模块都提供了一个相关的宏来执行测试:
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表示成功。根据情况,False值可以是空字符串、错误消息等。测试执行一次后,随后的CMake运行将使用缓存的结果,而不是再次执行测试。即使正在测试的代码发生了更改,也会出现这种情况,因此为了强制重新求值,必须手动从缓存中删除变量。如果指定了FAIL_REGEX选项,则应用一个额外的条件。如果测试编译和链接的输出与regex正则表达式匹配,则将认为检查失败,即使代码成功编译和链接。
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()
对于Fortran,文件扩展名可以影响编译器如何处理源文件,因此可以使用SRC_EXT选项显式指定文件扩展名,以获得预期的行为。对于C或C++的情况,没有等效的选项。
在调用任何编译测试宏之前,可以设置许多CMAKE_REQUIRED_…形式的变量,以影响它们如何编译代码:
-
CMAKE_REQUIRED_FLAGS
在相关CMAKE_<LANG>_FLAGS和CMAKE_<LANG>_FLAGS变量的内容之后传递给编译器命令行的额外标志。这必须是一个单独的字符串,多个标志用空格分隔,不像下面所有其他的CMake列表变量。
-
CMAKE_REQUIRED_DEFINITIONS
一个编译器定义的CMake列表,每一个都以-DFOO或-DFOO=bar的形式指定。
-
CMAKE_REQUIRED_INCLUDES
指定搜索头文件的目录。多个路径必须指定为一个CMake列表,空格被视为路径的一部分。
-
CMAKE_REQUIRED_LIBRARIES
要添加到链接阶段的库的CMake列表。不要在库名前加上任何-l选项或类似的前缀,只提供库名或CMake导入目标的名称。
-
CMAKE_REQUIRED_QUIET
如果存在此选项,则宏将不打印任何状态消息。
这些变量用于构造内部执行检查的try_compile()调用的参数。
除了检查代码是否可以构建,CMake还提供了测试C或c++代码是否可以成功执行的模块。成功是通过从提供的源文件创建的可执行文件的退出代码来衡量的,0被视为成功,其他值则表示失败。模块遵循与编译案例相似的结构,每个模块都提供了一个实现check的宏:
include(CheckCSourceRuns)
check_c_source_runs(code resultVar)
include(CheckCXXSourceRuns)
check_cxx_source_runs(code resultVar)
这些宏没有FAIL_REGEX选项,因为成功或失败完全由测试进程的退出代码决定。如果不能构建代码,这也会被视为失败。所有影响check_c_source_compiles()和check_cxx_source_compiles()代码构建方式的相同变量,对这两个模块的宏也具有相同的效果。
对于交叉编译到不同目标平台的构建,check_c_source_runs()和check_cxx_source_runs()宏的行为非常不同。如果提供了必要的细节,他们可能会在模拟器下运行代码,这可能会大大降低CMake阶段的速度。如果模拟器的细节没有提供,宏将期望通过一组变量提供一个预定的结果,而不会尝试运行任何东西。这个相当高级的主题在CMake的文档中介绍了try_run()命令,这是宏内部用来执行检查的命令。
某些类型的检查非常常见,所以CMake为它们提供了专门的模块。这就删除了许多定义测试代码的样板,并允许项目为检查指定最小的信息集。这些通常只是Check<LANG>SourceCompiles提供的宏的包装器,因此用于自定义如何构建测试代码的同一组变量仍然适用。这些更专业的模块检查编译器标志、预处理器符号、函数、变量、头文件等等。
可以使用CheckCompilerFlag模块来检查特定编译器标志的支持,每个模块都提供了一个宏,其名称遵循一个可预测的模式:
include(CheckCCompilerFlag)
check_c_compiler_flag(flag resultVar)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(flag resultVar)
include(CheckFortranCompilerFlag)
check_fortran_compiler_flag(flag resultVar)
标记检查宏在内部更新CMAKE_REQUIRED_DEFINITIONS变量,在使用一个简单的测试文件调用适当的check_source_compiles()宏时包含标记。失败正则表达式的内部集合也作为FAIL_REGEX选项传递,测试该标志是否导致发出诊断消息。如果没有发出匹配的诊断消息,则调用的结果将为真值。请注意,这意味着任何导致编译器警告但成功编译的标志仍将被视为检查失败。还要注意,这些宏假定在相关CMAKE_<LANG>_FLAGS变量中已经存在的任何标志。本身不会生成任何编译器警告。如果它们这样做,那么每个标记测试宏的逻辑都将失败,所有这些检查的结果都将失败。
另外两个值得注意的模块是CheckSymbolExists和CheckCXXSymbolExists。前者提供了一个构建测试C可执行文件的宏,后者的功能与c++可执行文件相同。两者都检查特定的符号是否作为预处理符号(即可以通过#ifdef语句测试的符号)、函数或变量存在。
include(CheckSymbolExists)
check_symbol_exists(symbol headers resultVar)
include(CheckCXXSymbolExists)
check_cxx_symbol_exists(symbol headers resultVar)
对于头文件中指定的每一项(如果需要给出多个头文件,则为CMake列表),相应的#include将被添加到测试源代码中。在大多数情况下,被检查的符号将由这些头文件中的一个定义。测试的结果以通常的方式存储在resultVar缓存变量中。
在函数和变量的情况下,符号需要解析为测试可执行文件的一部分。如果函数或变量是由一个库提供的,那么这个库必须作为测试的一部分被链接,这可以使用CMAKE_REQUIRED_LIBRARIES变量来完成。
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)
对于这些宏可以检查的函数和变量的种类有一些限制。只有那些满足预处理器符号命名要求的符号才能被使用。check_cxx_symbol_exists()的含义更强,因为它意味着只能检查全局命名空间中的非模板函数或变量,因为任何作用域(::)或模板标记(<>)对于预处理器符号都无效。也不可能区分同一个函数的不同重载,所以也不能检查这些重载。
还有其他模块旨在提供类似于CheckSymbolExists所覆盖的功能或其子集。这些其他模块要么来自CMake的早期版本,要么是针对C或C++以外的语言。CheckFunctionExists模块已经被记录为已弃用,CheckVariableExists模块没有提供任何CheckSymbolExists没有提供的东西。CheckFortranFunctionExists模块对于那些使用Fortran的项目可能是有用的,但是请注意没有CheckFortranVariableExists模块。Fortran项目可能希望使用checkfortransourcecompile来保持一致性。
其他模块提供了更详细的检查。结构成员可以用CheckStructHasMember来测试,特定的C或C++函数原型可以用CheckPrototypeDefinition来测试,非用户类型的大小可以用CheckTypeSize来测试。其他更高级别的检查也是可能的,如CheckLanguage、CheckLibraryExists和各种CheckIncludeFile…模块提供的。随着CMake的发展,进一步的检查模块会继续添加到CMake中,所以请参阅CMake模块文档来查看完整的可用功能集。
在进行多个检查的情况下,或者执行检查的效果需要彼此隔离,或者需要与当前范围的其他部分隔离,手动保存和恢复检查前后的状态可能很麻烦。特别是,各种CMAKE_REQUIRED_…变量经常需要保存和恢复。为了帮助解决这个问题,CMake提供了CMakePushCheckState模块,它定义了以下三个宏:
cmake_push_check_state([RESET])
cmake_pop_check_state()
cmake_reset_check_state()
这些宏允许将各种CMAKE_REQUIRED_…变量作为一个集合来处理,并将它们的状态推送到/从一个虚拟堆栈中。每次调用cmake_push_check_state()时,它有效地为CMAKE_REQUIRED_…变量(以及仅由CheckTypeSize模块使用的CMAKE_EXTRA_INCLUDE_FILES变量)开始一个新的虚拟变量作用域。cmake_pop_check_state()是相反的,它丢弃CMAKE_REQUIRED_…变量的当前值,并将它们恢复为先前堆栈级别的值。cmake_reset_check_state()宏可以方便地清除所有CMAKE_REQUIRED_…变量,cmake_push_check_state()的RESET选项也可以方便地清除作为push一部分的变量。注意,然而,在CMake 3.10之前存在一个bug,它导致RESET选项被忽略,所以对于需要使用3.10之前版本的项目,最好使用单独的cmake_reset_check_state()来代替。
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()
10.4 其他模块
CMake对一些语言有很好的内置支持,特别是C和C++。它还包括许多模块,以更可扩展和可配置的方式提供对语言的支持。通过定义相关的函数、宏、变量和属性,这些模块允许项目使用某些语言或与语言相关的包的某些方面。其中许多模块是作为find_package()调用支持的一部分而提供的(请参阅第23.5节“查找包”),而其他模块则打算通过include()更直接地使用,将内容引入当前作用域。下面的模块列表应该给出了一种语言支持的风格:
- CSharpUtilities
- FindCUDA (但请注意,在最近的CMake版本中,CUDA作为第一类语言的支持已经取代了这一点)
- FindJava, FindJNI, UseJava
- FindLua
- FindMatlab
- FindMatlab
- FindPython, FindPythonInterp
- FindPHP4
- FindRuby
- FindRuby
- FindTCL
- FortranCInterface
此外,还提供了与外部数据和项目交互的模。还提供了许多模块,以方便测试和封装的各个方面。这些与CTest和CPack工具有密切的关系,它们作为CMake套件的一部分发布。
相关代码:https://gitee.com/jiangli01/cmake-learning
更多请关注微信公众号【Hope Hut】: