【问题标题】:Make CMake variable set in subproject (variable nesting) available to all other subprojects使子项目中的 CMake 变量集(变量嵌套)可用于所有其他子项目
【发布时间】:2021-10-11 14:38:56
【问题描述】:

如果我有类似如下结构的项目树(每个子项目通过add_subdirectory()添加到父项目中):

CMakeLists.txt (TOP-LEVEL)
    |
    + -- subproj1
    |       |
    |       + -- CMakeLists.txt (subproj1)
    |       |
    |       + -- subsubproj1
    |                 |
    |                 + -- CMakeLists.txt (subsubproj1)
    |
    + -- subproj2
            |
            + -- CMakeLists.txt (subproj2)

我想在subproj2 中公开subsubproj1 中的变量集。这个变量的性质无关紧要,但在我的例子中,它指向${CMAKE_CURRENT_SOURCE_DIR}/include,这是subsubproj1 的包含目录,(在我的例子中)是subproj2 使用的库。目前,我在每个中间 CMakeLists.txt 之间重新分配变量(此处为 subproj1),其中变量被分配了一个值到顶层并启用了 PARENT_SCOPE

CMakeLists.txt (subsubproj1)

# Expose MY_VAR to subproj1
set(MY_VAR
    "Hello"
    PARENT_SCOPE
)

CMakeLists.txt (subproj1)

# Expose MY_VAR to top level project thus making it visible to all
set(MY_VAR
    ${MY_VAR}
    PARENT_SCOPE
)

这可以应用于任意嵌套的项目树。

我的问题是做我上面描述的常见做法是什么?我可以将MY_VAR 声明为一个顶级变量,但如果出于某种原因我不想让它在那里可见(如书面文本)怎么办。在这种情况下,PARENT_SCOPE 不再是一个选项,而应该在顶层 CMakeLists.txt 中直接声明该变量?

【问题讨论】:

  • 你说I want to expose a variable set inside subsubproj1 in subproj2。所以:In which case is PARENT_SCOPE no longer an option and should be replaced with just a straight declaration of that variable in the top-level CMakeLists.txt? 我不明白,在这种情况下,subsubproj1 内部没有设置变量,所以违反了你的要求,所以在任何情况下都不应该替换它。 what is the common practice of doing您想在subsubproj1 中设置一个可供父母使用的变量时,通常的做法是使用PARENT_SCOPE
  • 您的用例可能不那么广泛吗?你打算如何使用你的变量?做什么的?该变量的用途是什么?subproj2 打算如何使用它?变量会影响什么以及它的值如何变化?
  • 我将添加有关变量性质的详细信息。
  • 通常你会使用缓存变量。这使得变量在任何地方都可见,但您不必担心通过目录层次结构传递变量。考虑到您对这是一个包含目录的描述,我看不出有理由不通过具有公共或接口可见性的target_include_directory 将目录添加到目标,这确保所有链接库都可以访问此包含目录,即使如果它们间接链接(只要除了最后一个链接之外的每个链接都具有公共或界面可见性)。

标签: cmake


【解决方案1】:

目标

这个变量的性质无关紧要,但在我的例子中,它指向 ${CMAKE_CURRENT_SOURCE_DIR}/include,这是 subsubproj1 的包含目录,(在我的例子中)是 subproj2 使用的库。

不,性质并非无关紧要

在 CMake 中使用变量来通信包含目录是 2.6.x 时代的一种可怕的反模式。你不应该用锤子敲螺丝。

IMPORTED 项目始终是全球性的,因此您可以安全地链接到它们。在subsubproj1 你会写:

add_library(myproj_subsubproj1 INTERFACE)
add_library(myproj::subsubproj1 ALIAS myproj_subsubproj1)

target_include_directories(
  myproj_subsubproj1
  INTERFACE
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
)

然后在subproj2,你会写:

target_link_libraries(myproj_subproj2 PRIVATE myproj::subsubproj1)

更糟糕的选择

以下选项更糟糕,因为它们放弃了 CMake 语言的声明性部分,并使您的脚本依赖于子项目包含顺序。这是一个显着的复杂性增加,(根据我的经验)在构建代码中是没有保证的。

尽管如此,以下是 CMake 提供的命令式工具:

1。使用CACHE 变量

缓存是全局变量的磁盘持久存储。在任何目录中设置 1 会使该值对 所有 目录可见。

请注意,这样做有一些潜在的缺点:

  1. 在 CMake 3.21 之前,创建新的缓存条目会删除同名的普通变量,从而导致构建变得非幂等(糟糕!)的棘手情况。见https://cmake.org/cmake/help/latest/policy/CMP0126.html
  2. 用户可以在命令行覆盖您的缓存变量,因此当您的 CMake 程序开始运行时,您不能依赖于变量的定义或值。

如果你能忍受这个,那么你可以写:

# On CMake <3.21, honor normal variables. Can remove
# check if on CMake >=3.21
if (NOT DEFINED MyProj_INADVISABLE_VARIABLE)
  set(MyProj_INADVISABLE_VARIABLE
      "${CMAKE_CURRENT_SOURCE_DIR}/include>"
      CACHE STRING "Doc string...") 
  # If you want to hint to your users that they should
  # not edit this variable, include the following line:
  mark_as_advanced(MyProj_WEIRD_VARIABLE)
endif ()

如果您不想让您的用户覆盖它,那么您可以始终使用INTERNAL 缓存变量:

set(MyProj_INADVISABLE_VARIABLE "..." CACHE INTERNAL "...")

只要您尽早将其初始化为已知值,那么它作为全局变量就可以正常工作,但可能会在写入时产生磁盘流量。

2。目录属性

更好的方法是使用自定义目录属性来传达值。在subsubproj1

set_property(DIRECTORY "." PROPERTY inadvisable_prop "foo")

然后在subproj2:

get_property(value DIRECTORY "../subproj1/subsubproj1"
             PROPERTY inadvisable_prop)

请注意,获取不存在的属性并不是错误,因此请注意类型。

可以也使用GLOBAL 属性而不是目录属性,但是全局变量通常是等待发生的令人头疼的事情。您不妨将其设置在目录中,以减少意外范围界定错误的机会。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-08
    • 1970-01-01
    • 2011-11-25
    • 2022-11-04
    • 2017-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多