【问题标题】:Hierarchical CMake project that also works when building an "inner" (non-root) project在构建“内部”(非根)项目时也适用的分层 CMake 项目
【发布时间】:2021-07-02 19:39:43
【问题描述】:

假设我有一个分层的 CMake 项目,由 n 不同的项目组成:

CMakeLists.txt
proj-1/CMakeLists.txt
proj-2/CMakeLists.txt
(...)
proj-n/CMakeLists.txt

显然每个项目也会有源文件。

我会确保将所有感兴趣的命令添加到根 CMakeLists.txt 文件中——比如 CMAKE_CXX_STANDARDenable_testing()add_compile_options() 等。如果我理解正确,无论哪个选项都包含在根文件中CMakeLists.txt 文件也适用于所有儿童 CMakeLists.txt 文件——如果我错了,请纠正我,因为我指望这种行为。对于每个X = 1, ..., n,根CMakeLists.txt 还包含一个add_subdirectory(proj-X) 语句。

无论如何。假设出于某种原因,我只想构建proj-X 文件夹之一,例如proj-1。也许构建在其他项目之一中被破坏,或者我需要修复proj-1 上的错误,它不依赖于其他项目,并且构建所有项目需要很长时间。

重点是:我想在proj-1/CMakeLists.txt 上运行cmake 而不是在根CMakeLists.txt 文件上运行,但我想确保proj-1 的构建方式完全相同构建,如果我在根 CMakeLists.txt 文件上运行 cmake。这是一个问题,因为根 CMakeLists.txt 包含子 CMakeLists.txt 应该在从根构建它的常规情况下“继承”的语句,但在这种情况下,我直接从 proj-1/CMakeLists.txt 构建(在这种情况下,root CMakeLists.txt 文件不会出现。)

据我了解,一种可能性是将所有选项从根 CMakeLists.txt 文件复制到每个其他 proj-X/CMakeLists.txt 文件。当然,这是一个黑客和维护的噩梦,但我想它会起作用。

还有其他可能的解决方案吗?例如,我可以创建一个包含所有常用选项的文件并将其保存到根目录,然后在每个 proj-X/CMakeLists.txt 文件中执行与 #include 等效的 CMake 吗?是否会因为两次运行相同的命令而出现问题(一次在根 CMakeLists.txt 上,另一次在 proj-X/CMakeLists.txt 文件上,从根开始构建时)?

【问题讨论】:

    标签: cmake build


    【解决方案1】:

    您可能需要修改一些 CMakeLists.txt 文件。

    我会推荐观看Daniel Pfeifer's Effective CMake talk at CPPcon(幻灯片可用here)。

    其要点是,您的所有项目都应提供构建或编译所需的一切,本质上是构建要求和使用要求。为了以可维护和可扩展的方式实现这一点,您必须摆脱变量并设置全局选项(add_compile_optionsinclude_directories 等),而是专注于目标(target_compile_optionstarget_include_directories 等)。

    因此,在您的情况下,proj-1/CMakeLists.txt 将提供一个目标(我们称之为proj::proj1),它设置正确的PUBLICINTERFACE 选项(选项是指所需的编译器功能、依赖项、包含目录等)。

    一个抽象的例子:

    project(proj1)
    
    add_library(proj1 src.cpp)
    # This are private include files, whoever uses this library does not need them
    target_include_directories(proj1 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
    # These are public, needed both by this target and by whoever uses it.
    target_include_directories(proj1 PUBLIC
        # This is used when building the target
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/public/include>
        # This is used when the target is installed
        $<INSTALL_INTERFACE:include>)
    # Instead of asking directly for a language standard we ask for a compiler feature. We make this public so whoever depends on this target knows they also need this feature.
    target_compile_features(proj1 PUBLIC cxx_strong_enums)
    # As above, but this is needed only by this target during the build.
    target_compile_features(proe1 PRIVATE cxx_lambdas)
    
    # Add an alias, users can use target_link_libraries(target PRIVATE|PUBLIC proj::proj1) to add this target as a dependency (this will propagate all the PUBLIC include paths, compile options, compile features, dependencies, etc.
    add_library(proj::proj1 ALIAS proj1)
    

    这是高度抽象的,这取决于您在构建脚本中实际执行的操作,很难给出比 Daniel Pfeifer 更好的解释,所以我建议观看他的演讲或至少阅读幻灯片。它将使您的构建脚本更易于编写、阅读和使用。

    另一个很棒的资源是this site

    【讨论】:

    • 非常感谢,我会审查幻灯片。仍然:这是否意味着每个选项都应该进入自己的proj-X/CMakeLists.txt 文件?比如说,如果我有 10 个不同的选项,这些选项对所有人来说都是一样的呢?在每个文件中重复这 10 个不同的选项似乎并不正确。
    • 这确实有点问题。想到的第一种方法是在根CMakeLists.txt 的变量中添加常用选项,然后使用target_* 将它们添加到特定目标。但是,如果您想将一个项目编译为独立项目,您将没有选择。你可以有一个接口库来捆绑这些选项并链接它,但问题是你把那个库放在哪里以及如何找到它?我不知道有什么好的通用方法来处理这个问题。
    • 另一种选择是始终从根项目构建,但可以选择禁用您不需要的目标。但是,如果您只想提供一个包含其中一个子项目的包,这将不起作用。
    猜你喜欢
    • 2022-08-14
    • 2017-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-27
    • 1970-01-01
    相关资源
    最近更新 更多