CMake变量和属性有什么区别?

18

CMake 变量和属性似乎都能实现非常类似的功能,让我无法理解它们之间的不同。

虽然它们各自都有自己的文档部分,但两者都可以影响构建系统,都是“预存在”的,都可以根据其他 CMake 命令动态生成。它们似乎应该有单独的目的。它们是什么?


在属性中,您可以从复合标记构建名称,例如 <name>:<os_name>:<config_name>:<arch_name> 等,并使用正则表达式或其他任何方法将其解析为独立变量。在变量名称中,您不能使用此技术,因为变量名称不得包含无效字符,如 :。因此,属性可以与彼此结合使用,以形成某种结构变量值。 - Andry
4个回答

13

一个非常简短且简单的方法来思考它,就是属性是针对目标作用域的变量。例如:

add_executable(foo foo.cpp)
set_target_properties(foo PROPERTIES
    CXX_STANDARD 14
    CXX_EXTENSIONS OFF
)
# Build foo with c++11 for some reason
add_executable(foo11 foo.cpp)
set_target_properties(foo11 PROPERTIES
    CXX_STANDARD 11
    CXX_EXTENSIONS OFF
)

如果CMakeLists.txt是用C ++编写的,可能看起来像这样:

const char * src_files[] = { "foo.cpp" };
executable foo{src_files};
foo.setCxxStandard(14);
foo.setCxxExtensions(false);

executable foo11{src_files};
foo.setCxxStandard(11);
foo.setCxxExtensions(false);

如果我们使用变量来表示这些东西,代码会更像这样:

// globals
int CMAKE_CXX_STANDARD = 14;
bool CMAKE_CXX_EXTENSIONS = false;

// later, in a function
const char * src_files[] = { "foo.cpp" };
executable foo{src_files}; // foo copies global settings

CMAKE_CXX_STANDARD = 11;
executable foo11{src_files};

因为属性是目标的一部分而不是全局的,这也意味着它们可以被导出。以下是我其中一个项目中的样例:

set_target_properties(Foo::bar PROPERTIES
    INTERFACE_COMPILE_FEATURES "cxx_std_14"
    INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include/"
    INTERFACE_SOURCES "${_IMPORT_PREFIX}/include/foo/bar.hpp"
)

这意味着如果你导入 Foo::bar(可能是通过类似于 find_package(Foo) 的方式),你的项目已经知道链接到 Foo::bar 的内容需要使用 C++14(INTERFACE_COMPILE_FEATURES),它需要将一些东西添加到 include 路径中(INTERFACE_INCLUDE_DIRECTORIES),并且有一些源文件是必要的(我的头文件,INTERFACE_SOURCES)。


4
哦,我想我可能理解了属性和变量之间的区别,因为属性可能具有作用域,而变量始终是全局的。但是根据此处的属性文档https://cmake.org/cmake/help/v3.11/manual/cmake-properties.7.html,属性也可能是全局的,因此在这种情况下,仍然让我感到困惑,区分似乎是任意的。 - user1489829
1
@user1489829 - 我承认我不知道全局属性,所以我得想出来。我的答案是针对目标上的属性,但比较可以扩展到任何非全局属性。 - Stephen Newell
2
正如用户1489829所指出的那样,全局作用域也有其属性,这使得此答案不正确。因此,我仍然希望得到一个正确且更全面的答案。截至2018年5月,谷歌搜索此问题没有给出任何有用的结果。 - Fedorov7890
CMake 在我看来不是很连贯...但还是得应对它。 - smwikipedia

5
我认为Stephen Newell的答案捕捉了属性的主要动机(而成员变量与非成员变量的c++类比非常有帮助)。
但是,除了作用于目标的目标属性之外,还有其他各种类型的属性,可以作用于例如源、安装、目录、全局等。
可能存在同名的(全局)变量和(全局)属性同时存在的情况(但这样做的有用性不明显)。
总之,属性主要有用的是它们被“作用域”到像目标这样的东西上。全局属性也被作用域到单个“全局对象”上。(在Stephen的类比中,它们可能是单例的成员变量)。属性使用不同于变量的语法进行设置/获取。给定范围的属性可以与任何其他范围(包括全局)的同名属性同时存在,以及同名的常规变量。
结合全局属性和变量的一个可能有用性在这里给出:
全局属性可以是一个有用的未缓存的全局变量。许多目标属性都是从具有CMAKE_前缀的匹配变量初始化的。因此,例如设置CMAKE_CXX_STANDARD将意味着创建所有新目标时都将其设置为CXX_STANDARD。

对我来说,这是一个更完整的解释,因为它描述了所有的属性类型,然后概括了这个概念。 - mark sabido

4

我对其他答案并不完全满意,所以必须去找到自己的答案。经过一些阅读和咨询,我相信 CMake 为变量和属性指定的角色确实是不同的,并且实际上更或多少是直接的。

变量是任意命名的内存单元,您可以在计算过程中使用它们并传递值。也可以在命令行中分配它们并公开为GUI选项,以便从脚本外接受输入。

属性是预定义的槽位,共同描述了环境、状态和正在配置的项目的所有方面。请记住,运行 CMake 的目的是让所选的生成器输出一组 makefile (或IDE项目文件)。反过来,配置阶段的目的是评估和记录生成器制定决策时可能想要知道的所有事实。

因此,通常情况下,值会通过 CMake 流动如下:

命令行/选项 > 变量 > 某些处理 > 属性 > 生成器 > makefile

例如,可以通过 set_property() 设置 CXX_STANDARD 属性,但如果给定了一个值,则还将从 CMAKE_CXX_STANDARD 变量 初始化。

在“现代” CMake 中,我们被鼓励以目标及其属性为中心思考,而不是将值设置为一个无组织的变量集合,但似乎最初并没有属性,因此某些方面仍然可以使用变量进行配置。例如,可以将 CMAKE_CXX_FLAGS 变量 设置为“-std=c++11”,然后将直接影响生成。现代方法是设置 CXX_STANDARD 属性


换句话说,属性旨在影响构建系统,任何能够实现此目的的变量都是遗留的,可能仍然保留以实现向后兼容性。我可以接受这一点。暂时将其作为答案接受,因为这是第一个真正完整回答了我的问题意图的答案。 - user1489829

-1

首先,编程语言如何处理变量?编程语言中的变量可以分为两组。

  • 基于栈的变量是存储在内存中每个函数所保留的,存储在堆栈上的内存。当调用函数时,堆栈上的保留区域一直在增长和缩小。当进入新的函数时,该函数会在堆栈顶部为变量保留内存。离开函数时,将销毁该区域并清除变量。
  • 基于对象的变量存储在为对象保留的内存区域中。当对象被销毁时,基于对象的变量也被销毁。它们通常由某种类型的操作/命令销毁。这些类型的对象变量有时称为成员变量。

脚本语言也是这样工作的,但它们有模拟堆栈的技巧。当一个函数在脚本语言中被输入时,堆栈通常也是一个对象。该对象知道它的调用者堆栈。 它的工作方式类似于一棵树,变量分支会增长和缩小,每个分支都知道它的父级,并且具有变量叶子。 运行脚本程序的启动时间不能太长,因此在分析代码以进行优化时有限的时间。脚本语言中存在的这个堆栈树是一种权衡,使它们比将变量存储在堆栈上的语言更简单解析和分析,因为计算机处理器可以处理它。

那么在CMake中变量和属性如何工作呢?我还没有检查过代码,但我可以猜测。变量是基于堆栈的,属性是基于对象的。使用message打印变量值需要一个基于堆栈的变量,message命令可以找到存储在堆栈上的变量。它无法找到属性,因为它们是CMake中一个对象的成员。要从属性中获取值,您还需要该对象,这就是为什么需要另一个命令来获取它,例如get_property。

这里有一个CMake教程


我还没有检查过代码,但是我可以猜测。 - johnwbyrd

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