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

CMake: In which order are files parsed (cache, toolchain, etc.)?

这似乎是一个微不足道的问题,因为 CMake 是一种脚本语言,一般答案是:严格顺序。但我遇到过几个案例,其中 CMake 何时或以何种顺序解析某些文件很重要。所以我想知道:

  1. 是否有可用的文档来描述其中的顺序 文件(包括内部 CMake 文件)是否已解析?
  2. 文件顺序是取决于 CMake 版本还是某些 CMake options/settings/environment incl。选择的生成器或主机 环境?

到目前为止我遇到的案例,其中上述信息很重要:

也许你知道的更多。

为了找到答案,我尝试了以下方法:我已经设置了一个简单的 main 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文件只在配置开始时读取一次,生成完成后写入。它只是保留 "global variables" 缓存的状态。
  2. project() 命令触发了 CMake 的大部分检测魔法(包括从 Toolchain.txt 文件中读取)。
  3. 工具链文件被读取两次。一旦检测到 make/compile 系统,一旦进入然后生成的 CMakeSystem.cmake.
  4. variable_watch()钩子可以随时触发,所以最优"command to execute"的调用范围未定义

没有关于 CMake 的这个特定内部工作原理的官方文档,所以请在下面找到我到目前为止对 CMake 的了解的摘要...

解析哪些文件取决于

  1. 主机和目标操作系统
  2. 目标编译器
  3. 您的主机环境(变量、注册表、安装的软件)
  4. 您项目的 CMake 脚本文件,其中可能包括
    1. 你的工具链文件
    2. 您选择的编程语言
    3. 任何外部 projects/libraries/files/scripts

这些参数有很多可能的组合,但大多数时候 CMake 会自动为您检测正确的设置,而您无需理会它是如何完成的。好消息是 - 当您需要知道时 - 它遵循某些内在模式。

有趣的是,它仅略微取决于您选择的CMake generator

第一步:编译器检测和验证

这个主要是从project()命令开始的。以CXX语言为例,编译器检测的主要文件有(另见问题trace输出的根文件):

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

    这主要是尝试确定编译器可执行文件的位置并调用它以获得更具体的编译器 ID。

    此外,例如根据主机环境和目标操作系统定义 source/output 文件扩展名。

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

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

    主要是那些变量:CMAKE_CXX_COMPILER, CMAKE_CXX_SOURCE_FILE_EXTENSIONS, CMAKE_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 配置步骤。

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

您可以通过多种方式更改 CMake 默认值,而无需实际触及项目的 CMakeLists.txt 文件。

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

    如果你想通过几个项目一遍又一遍地给出一些预设值(你通常会通过 -D ... 选项给出),可以使用这个。比如你电脑上的一些库搜索路径或者你公司使用的一些预设。

  • CMakeCache.txt 通过例如cmake-gui

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

  • CMAKE_TOOLCHAIN_FILE

    主要用于 cross-compiling,但它可以更一般地描述为每个使用的编译器工具链的预设值。

  • PreLoad.cmake

    与 "initial cache" 选项(见上文)大致相同,但它不是通过命令行选项给出的。它必须与项目的 CMakeLists.txt.

    位于同一目录中

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

    • 自己的变量范围(此处未缓存的所有内容在您的主 CMakeLists.txt 中不可见)
    • 已知的限制(它 运行 比其他任何东西都早,所以大多数情况下你可以检查 CMAKE_GENERATOR
  • CMAKE_USER_MAKE_RULES_OVERRIDE, CMAKE_USER_MAKE_RULES_OVERRIDE_<LANG>

    这允许在 CMake 自动检测后修改非缓存的默认值。

    :将有效的 CXX 源文件扩展名扩展 .c 个文件

    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

    这意味着 "inject custom code into project builds without modifying their source" 在您的 project() 命令被处理后(并且检测到构建环境)。

Toolchain.cmake: 解析多次

在确定系统、编译器等时多次读取 toolchain file

重要的是要知道:

  • 每次 try_compile() 调用都会读取它。由于 try compile 必须生成有效的可执行文件,因此您可能需要 - 如果您是交叉编译 - 到

  • 如果您更改工具链文件,CMake 将重新触发编译器检测(如上图所示)。这对您的编译器设置有很大帮助。

CMake 重新配置:一切都来自缓存

最后但同样重要的是,重要的是要知道上面的跟踪只显示了初始步骤。所有连续的项目配置都将从缓存变量中获取几乎所有内容,因此在重新配置中读取的文件要少得多运行s。

参考资料