【问题标题】:Why does CMake make a distinction between a "target" and a "command"?为什么 CMake 区分“目标”和“命令”?
【发布时间】:2012-08-11 21:39:45
【问题描述】:

在 CMake 语义中,“目标”和“命令”之间存在某种区别,这让我感到困惑。在 Makefiles 中,没有这样的区别:

targetname:dependency
    command

即目标对应生成的同名文件。

在 CMake 中,您有像“add_custom_command”和“add_custom_target”这样具有重叠功能的命令,甚至在官方文档中,语义也很混乱,即在“Mastering CMake,第 5 版”中,第 110 页的“添加自定义目标”下":

DEPENDS 参数设置自定义目标之间的依赖关系 和自定义命令。

我的理解是目标(生成的文件)具有依赖关系(其他文件,生成或不生成),以及实际执行生成的命令。说目标取决于命令是荒谬的。更糟糕的是,“add_custom_command”有两种风格,要么将附加命令附加到现有目标,要么将命令吐出到以太中。

有人可以解释为什么这种区别存在吗?

【问题讨论】:

    标签: cmake


    【解决方案1】:

    目标

    通常,目标包括通过调用add_executableadd_library 定义的可执行文件或库,并且可以设置许多properties

    它们可以相互依赖,对于像这样的目标,这意味着依赖的目标将在它们的依赖之后构建。

    不过,您也可以通过add_custom_target 定义“自定义目标”。来自文档:

    添加一个具有给定名称的目标来执行给定的命令。目标没有输出文件,并且即使命令尝试使用目标名称创建文件,也总是被认为已过期。使用 ADD_CUSTOM_COMMAND 生成具有依赖关系的文件。默认情况下,没有任何东西取决于自定义目标。使用 ADD_DEPENDENCIES 将依赖项添加到其他目标或从其他目标添加。

    因此,这些与“普通”目标不同,因为它们不代表会产生 exe 或 lib 的东西,但它们仍然受益于目标可以拥有的所有属性,包括拥有或成为依赖项。它们显示为可以构建的目标(例如make MyCustomTargetmsbuild MyCustomTarget.vcxproj)。构建它们时,您只是调用为它们设置的命令。如果它们依赖于其他目标(普通或自定义),则将首先构建它们。


    自定义命令

    通过add_custom_command 定义的自定义命令完全不同,因为它不是“可构建”对象,并且不像目标那样具有可设置的属性 - 它不是可以显式引用的命名对象将其添加到 CMakeLists.txt 中后再次进行。

    它基本上是一个命令(或一组命令),在构建依赖目标之前将被调用。这就是“依赖”的真正含义(至少我是这么看的)——它只是说如果 A 依赖于 B,那么 B 将在 A 构建之前构建/执行。

    自定义命令的依赖项可以使用add_custom_command(TARGET target ... 表单显式设置,也可以通过创建包含通过add_custom_command(OUTPUT output1 ... 表单生成的文件的目标来隐式设置。

    第一种情况,每次构建target,都会先执行自定义命令。

    在第二种情况下,它有点复杂。如果自定义命令具有依赖于其输出文件的目标(并且输出文件不存在),则在构建这些依赖对象之前调用它。依赖项是在您执行时隐式创建的,例如add_library(MyLib output1.h ... ) 其中output1.h 是通过add_custom_command(OUTPUT output1.h ... ) 生成的文件。

    【讨论】:

    • 感谢您尝试解释 Fraser 的混乱情况,但我试图了解 CMake 开发人员到底为什么首先制造了这种混乱。我希望有一个很好的理由,但我怀疑这只是另一个糟糕的设计决策,比如全大写惨败。
    • @DrewWagner 好吧,我同意全大写的问题,但我不能说我对命令与目标的事情有问题。也许我只是习惯了 :-) 我知道 Kitware 的人非常小心地保持向后兼容性,因此他们必须支持他们可能不愿意的东西。它也创建了灰色区域,我猜新命令与现有命令的职责重叠。关于命令/目标设置的原始理由,您最好在mailing list? 上提问
    • 自定义目标用于在完全虚构的工件上挂起自定义操作,而自定义命令用于在实际存在的工件上挂起一些自定义处理。一些示例:使用自定义目标在您的项目中创建一个目标,该目标处理一些资源或 i18n 文件。使用自定义命令将代码签名应用于可执行工件。使用自定义目标执行第二个目标更加困难且不灵活,因为它是一个单独的目标,并且本质上对可执行目标的属性一无所知,因此您最终将硬编码路径等......
    • 否 - add_custom_command (cmake.org/cmake/help/v3.5/command/…) 的第二个签名将 TARGET 作为参数。
    • 我可能会说,所有这些混乱最终导致完全无法构建一个公平的便携式 cmake 列表。并且像循环重建问题(或根本没有建立依赖关系)这样的问题仍然存在,没有解决方案来永久修复它。我试图自己修复它,但没有运气,它可以解决 Linux 或 Windows 中的问题。两者结合在一起,它永远不会没有问题。
    【解决方案2】:

    add_custom_command 添加一个可以定义输出的可调用函数(使用 OUTPUT 和 BYPRODUCTS 参数)。它还可以具有将在调用函数之前运行的依赖项。

    请注意,由于奇怪的文档(makefile 示例非常具有误导性),它不会做您可能认为它会做的事情。特别是,它对执行的次数没有任何保证。例如,想象一下:

    add_custom_command(OUTPUT /tmp/touched COMMAND echo touch COMMAND touch /tmp/touched)
    add_custom_target(touched-one ALL DEPENDS /tmp/touched)
    add_custom_target(touched-two ALL DEPENDS /tmp/touched)
    

    “触摸”会打印多少次?你不知道,因为它没有在任何地方指定; make -j2 可能会打印两次,但这取决于时间:

    Scanning dependencies of target touched-two
    Scanning dependencies of target touched-one
    [ 50%] Generating touched
    touch
    [100%] Generating touched
    touch
    [100%] Built target touched-two
    [100%] Built target touched-one
    

    但 Ninja 只会打印一次,可能:

    # rm -rf * && cmake -GNinja ../c ; cmake --build . -- -j 5
    [1/1] Generating touched
    touch
    

    通常,您将执行 add_custom_command 来完成一些工作并定义一个 OUTPUT,然后您将拥有一个依赖于自定义命令输出的 add_custom_target。任何想要输出的人都取决于目标,这确实给了你想要的保证。

    警告:请参阅 this bug,了解为什么构建跨平台元构建工具真的很难

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-17
      • 2011-03-16
      • 2023-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多