CMake:文件解析的顺序是什么(缓存、工具链等)?

40
这似乎是一个琐碎的问题,因为CMake是一种脚本语言,通常的答案是:严格按照顺序进行。但我遇到了几种情况,在这些情况下,CMake解析某些文件的顺序很重要。所以我想知道:
  1. 是否有可用的文档描述文件(包括内部CMake文件)解析的顺序?
  2. 文件顺序是否取决于CMake版本或某些CMake选项/设置/环境,包括所选生成器或主机环境?

迄今为止,我遇到过以下情况,其中上述信息很重要:

也许你知道更多。

为了找到答案,我尝试了以下方法:我设置了一个简单的主要CMakeLists.txt,如下所示,并运行cmake --trace …来分析解析顺序。

cmake_minimum_required(VERSION 2.8)

include(BeforeProjectCmd.cmake)

project(ParserTest CXX)

add_subdirectory(LibTarget1)
add_subdirectory(LibTarget2)

add_executable(ExeTarget Test.cpp)

variable_watch(CMAKE_BACKWARDS_COMPATIBILITY)

当我运行例如cmake --debug-output --trace -G"Visual Studio 12 2013" -DCMAKE_TOOLCHAIN_FILE:FILE_PATH=Toolchain.txt时,我得到了一个很长的跟踪信息,我试图总结一下:
# Begin try to read
CMakeCache.txt
${CMAKE_BINARY_DIR}/CMakeCache.txt
PreLoad.cmake
${CMAKE_BINARY_DIR}/PreLoad.cmake
# End try to read

┌ CMakeLists.txt(1):  cmake_minimum_required(VERSION 2.8 )
│ CMakeLists.txt(3):  include(BeforeProjectCmd.cmake )
│
├─ BeforeProjectCmd.cmake
│
│ CMakeLists.txt(5):  project(ParserTest CXX )
├┬ share/cmake-3.2/Modules/CMakeDetermineSystem.cmake
││
│└─ Toolchain.txt
│
├┬ ${CMAKE_PLATFORM_INFO_DIR}/CMakeSystem.cmake
││
│└─ Toolchain.txt
│
├─ share/cmake-3.2/Modules/CMakeSystemSpecificInitialize.cmake
├┬ share/cmake-3.2/Modules/CMakeDetermineCXXCompiler.cmake
│├┬ share/cmake-3.2/Modules/CMakeDetermineCompiler.cmake
││├ share/cmake-3.2/Modules/Platform/Windows-CXX.cmake
…
││├ share/cmake-3.2/Modules/CMakeDetermineCompilerId.cmake
││├─ share/cmake-3.2/Modules/CMakeCompilerIdDetection.cmake
…
││├ share/cmake-3.2/Modules/Compiler/MSVC-DetermineCompiler.cmake
…
│├ ${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/3.2.2/CMakeCXXCompiler.cmake
│├ share/cmake-3.2/Modules/CMakeSystemSpecificInformation.cmake
│├┬ share/cmake-3.2/Modules/CMakeGenericSystem.cmake
││├ share/cmake-3.2/Modules/Platform/Windows.cmake
││└─ share/cmake-3.2/Modules/Platform/WindowsPaths.cmake
│├ share/cmake-3.2/Modules/CMakeCXXInformation.cmake
│├┬ share/cmake-3.2/Modules/Compiler/MSVC-CXX.cmake
││├ share/cmake-3.2/Modules/Platform/Windows-MSVC-CXX.cmake
││├┬ share/cmake-3.2/Modules/Platform/Windows-MSVC.cmake
│││└─ share/cmake-3.2/Modules/CMakeRCInformation.cmake
││└ share/cmake-3.2/Modules/CMakeCommonLanguageInclude.cmake
│├ share/cmake-3.2/Modules/CMakeTestCXXCompiler.cmake
│├┬ share/cmake-3.2/Modules/CMakeTestCompilerCommon.cmake
││├ share/cmake-3.2/Modules/CMakeDetermineCompilerABI.cmake
││├ share/cmake-3.2/Modules/CMakeDetermineCompileFeatures.cmake
││├ share/cmake-3.2/Modules/Internal/FeatureTesting.cmake
││└ share/cmake-3.2/Modules/Compiler/MSVC-CXX-FeatureTests.cmake
│└ ${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/3.2.2/CMakeCXXCompiler.cmake
│
│ CMakeLists.txt(7):  add_subdirectory(LibTarget1 )
│
├─ LibTarget1/CMakeLists.txt
│
│ CMakeLists.txt(8):  add_subdirectory(LibTarget2 )
│
├─ LibTarget2/CMakeLists.txt
│
│ CMakeLists.txt(10):  add_executable(ExeTarget Test.cpp )
│ CMakeLists.txt(12):  variable_watch(CMAKE_BACKWARDS_COMPATIBILITY )
│
│  CMake Debug Log in CMakeLists.txt:
│  Variable "CMAKE_BACKWARDS_COMPATIBILITY" was accessed using UNKNOWN_READ_ACCESS with value "".

-- Configuring done
-- Generating ${CMAKE_BINARY_DIR}
-- Generating ${CMAKE_BINARY_DIR}/LibTarget1
-- Generating ${CMAKE_BINARY_DIR}/LibTarget2
-- Generating done

# Writes
${CMAKE_BINARY_DIR}/CMakeCache.txt

看到上面的输出,我得出了以下结论(我希望这些结论是正确的并且有些通用):

  1. CMakeCache.txt 文件只在配置启动时读取一次,并在生成完成后写入。它仅保留“全局变量”缓存的状态。
  2. project() 命令触发了大部分 CMake 的检测魔法(包括从 Toolchain.txt 文件中读取)。
  3. 工具链文件被读取两次。一次是在检测到编译系统之前,另一次是在生成的 CMakeSystem.cmake 中。
  4. variable_watch() 钩子可以随时触发,因此调用最佳“执行命令”的范围未定义。

3
也许你可以与一些CMake核心开发者分享你的问题。自CMake3.0以来,他们开始大力改善文档,所以这对他们来说可能很有趣。cmake-developers邮件列表非常活跃。 - Dimitri Merejkowsky
我认为这个链接可能会对你有所帮助。 - thiagowfx
@thiagowfx 感谢提供的链接。它帮助我理解了CMake背后的根本和部分解析概念(也可以参见这里),但我必须承认,我一直在寻找更详细的东西。自从我发布了这个问题以来,我已经获得了很多更深入的见解,例如这里,我想在有时间编译必要信息时尝试回答自己的问题。 - Florian
@Florian:是的,这个问题非常好。如果您认为已经学到了足够的信息,随时可以发布一个完整的答案。 - thiagowfx
@thiagowfx 好的。我已经添加了我目前发现的内容。 - Florian
1个回答

37

目前没有关于CMake内部运作的官方文档,因此请看下面我对CMake的所学所知进行的摘要...

解析哪些文件取决于以下因素:

  1. 主机和目标操作系统
  2. 目标编译器
  3. 您主机计算机的环境(变量、注册表、已安装软件)
  4. 您项目的CMake脚本文件,包括:
    1. 您的工具链文件
    2. 您选择的编程语言
    3. 任何外部项目/库/文件/脚本

这些参数有很多可能的组合,但大多数情况下,CMake会自动检测正确的设置,您不需要费心了解其如何进行。好消息是 - 当您需要知道时,它遵循某些固定模式。

有趣的是,它仅在很小程度上依赖于您选择的CMake生成器

初始步骤:编译器检测和验证

这主要从project()命令开始。以CXX语言为例,编译器检测的主要文件为(还请参见问题跟踪输出中的根文件):

  • share/cmake-x.y/Modules/CMakeDetermineCXXCompiler.cmake

    这基本上尝试确定编译器可执行文件的位置,并调用它以获取更具体的编译器ID。

    此外,例如根据主机计算机环境和目标操作系统定义源/输出文件扩展名。

  • share/cmake-x.y/Modules/CMakeCXXCompiler.cmake.in

    这是存储编译器检测结果的模板,存储在${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/x.y.z/CMakeCXXCompiler.cmake中。

    主要变量包括:CMAKE_CXX_COMPILERCMAKE_CXX_SOURCE_FILE_EXTENSIONSCMAKE_CXX_IGNORE_EXTENSIONSCMAKE_CXX_COMPILER_ENV_VAR

  • share/cmake-x.y/Modules/CMakeCXXInformation.cmake

    此文件设置编译器的基本标志。它也是在其中进行调用以影响设置的编译器、主机和目标的位置:

    include(Platform/${CMAKE_SYSTEM_NAME}-${CMAKE_CXX_COMPILER_ID}-CXX-${CMAKE_SYSTEM_PROCESSOR} OPTIONAL)
    include(Platform/${CMAKE_SYSTEM_NAME}-${CMAKE_CXX_COMPILER_ID}-CXX OPTIONAL)
    include(Platform/${CMAKE_SYSTEM_NAME}-${CMAKE_BASE_NAME} OPTIONAL)
    include(Platform/${CMAKE_SYSTEM_NAME} OPTIONAL)        
    
  • share/cmake-x.y/Modules/CMakeTestCXXCompiler.cmake

    这会对所有内容进行测试,例如通过在简单生成的 CMake 项目中实际调用编译器来确定编译器功能。

这些步骤的结果存储在缓存变量中,这些文件在这种情况下是特殊的,因为它们由类似于 CMAKE_CXX_COMPILER_LOADEDCMAKE_CXX_INFORMATION_LOADEDCMAKE_CXX_COMPILER_WORKS 的变量保护,以便不会在每个连续的 CMake 配置步骤中再次运行。

项目配置文件:修改默认值

有几种方法可以在不必实际更改项目的 CMakeLists.txt 文件的情况下更改 CMake 默认值。

  • -C <initial-cache> 命令行选项

    如果您希望一遍又一遍地为多个项目提供一些预设值(您通常会通过 -D ... 选项提供这些值),则可以使用此选项。比如您的电脑上某些库搜索路径或公司中使用的一些预设值。

  • CMakeCache.txt,例如通过 cmake-gui

    cmake-gui 让您在最终生成构建环境之前手动修改项目选项(编辑 CMakeCache.txt 中的所有非内部变量)。

  • CMAKE_TOOLCHAIN_FILE

    主要用于交叉编译,但更普遍地描述为每个编译器工具链的预设值。

  • PreLoad.cmake

    与“initial cache”选项几乎相同(见上文),但它不是通过命令行选项给出的。它只需位于您的项目的 CMakeLists.txt 相同目录中即可。

    注意:它支持所有 CMake 脚本命令,如if()调用,但 PreLoad.cmake

    • 自己的变量作用域(一切非缓存内容在主CMakeLists.txt中不可见)
    • 已知限制(它在所有其他内容之前运行,因此大多数情况下可以根据CMAKE_GENERATOR进行检查)
    CMAKE_USER_MAKE_RULES_OVERRIDECMAKE_USER_MAKE_RULES_OVERRIDE_<LANG> 这允许在CMake自动检测后修改非缓存默认值。 示例:通过.c文件扩展有效的CXX源文件扩展名。 MakeRulesOverwrite.cmake
    list(APPEND CMAKE_CXX_SOURCE_FILE_EXTENSIONS c)
    

    然后您可以使用类似以下方式调用cmake

    > cmake -D CMAKE_USER_MAKE_RULES_OVERRIDE:PATH=..\MakeRulesOverwrite.cmake ..
    
  • CMAKE_PROJECT_ParserTest_INCLUDE

    它的作用是在处理project()命令后(并检测到构建环境后)直接向项目构建中注入自定义代码,而无需修改其源代码。

Toolchain.cmake:多次解析

在确定系统、编译器等信息时,会多次读取工具链文件

需要知道的重要信息如下:

  • 每次使用try_compile()调用时都会读取它。因为try compile必须生成有效的可执行文件,所以如果您是交叉编译,则可能需要:

  • 如果更改工具链文件,CMake将重新触发编译器检测(如上面的跟踪所示)。这对于调整编译器设置非常有帮助。

CMake重新配置:所有内容都来自缓存

最后但并非最不重要的是,需要知道上述跟踪仅显示初始步骤。所有后续项目配置将几乎全部从缓存变量中获取信息,因此在重新配置时读取的文件要少得多。

参考文献


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接