【问题标题】:add_custom_command is not generating a targetadd_custom_command 没有生成目标
【发布时间】:2015-06-08 21:17:52
【问题描述】:

也许这是不可能的,我误读了cmake 3.2 documentation,但我虽然创建一个自定义命令会在 Makefile 中创建一个自定义“目标”,以便我可以通过调用输出文件的名称来构建目标。 CMake 文档说:

在 makefile 术语中,这会以以下形式创建一个新目标:

 OUTPUT: MAIN_DEPENDENCY DEPENDS
    COMMAND

所以我想我可以运行make OUTPUT。也许文档将 CMake 目标与 Makefile 目标混淆了?

例如,

 add_custom_command(OUTPUT foo_out
    COMMAND post_process foo_in > foo_out
    DEPENDS foo_in
 )

我想做

 make foo_out

它会生成foo_out。但是,如果我这样做,我会得到

make: **** No rule to make target `foo_out`. Stop.

果然,“foo_out”这个词在 cmake 二进制输出目录的任何文件中都不存在。如果我把它改成这个

add_custom_target(bar DEPENDS foo_out)
add_custom_command(OUTPUT foo_out COMMAND post_process foo_in > foo_out)

那我就可以了

make bar

我能做到

make foo_in

但我还是做不到

make foo_out

make bar 的问题在于它不直观,因为实际的文件输出是foo_out 而不是bar

我该怎么做?

在我的例子中,我需要对标准可执行目标运行一个特殊的处理步骤,它将可选资源插入到 ELF 文件中。我希望能够将两个可执行文件都作为 Makefile 目标,这样我就可以构建裸 ELF 可执行文件以及注入资源的 ELF 可执行文件。

如果我正在编写自定义 Makefile,这很简单!

foo_in: foo.c
    $(CC) $< -o $@

foo_out: foo_in
    post_process $< > $@   

我可以做到make foo_inmake foo_out

【问题讨论】:

  • 我认为您需要将 OUTPUT foo 指定为任何其他目标(不一定是自定义目标)的输入。 add_custom_command() 文档说“这定义了一个命令来生成指定的 OUTPUT 文件。在同一目录(CMakeLists.txt 文件)中创建的目标将自定义命令的任何输出指定为源文件 在构建时使用命令生成文件的规则。”
  • @Florian - 在我的示例bar 目标中,我仍然无法直接构建foo(如果我这样做make foo 没有任何反应),即使bar 依赖于foo
  • 感谢您提供更多详细信息。您不能在 CMake 中执行类似 add_custom_target(foo_out DEPENDS foo_out) 的操作,因为它会尝试将依赖项与目标名称匹配。在使用 GNU Makefile 生成器进行一些测试后,我看到您的示例中 foo_out 的实际构建规则仅生成到“CMakeFiles\bar.dir\build.make”中,并且在主 Makefile 中不可见。所以我累了:add_custom_target(foo_out COMMAND post_process foo_in &gt; foo_out)set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES foo_out)。不好,但名字匹配...
  • @Florian 我刚刚尝试过,对我来说,使用 Unix Makefile 生成器可以在我添加 add_costum_command 的目录和构建根目录中找到目标。无处可去。据我所知,该文档缺乏任何细节。

标签: cmake cmake-custom-command


【解决方案1】:

add_custom_command创建新目标。您必须通过add_executableadd_libraryadd_custom_target 明确定义目标,以使它们可见。

如果您必须解决部署问题,您可以

1. 像这样使用install 命令(在您的 CMakeLists.txt 中的某处):

install(SCRIPT <dir>/post_install.cmake)

将仅在您运行make install 时执行的命令存储在单独的.cmake 文件中。或者,如果安装目标已经为其他事情保留,或者您有更复杂的事情要做:

2.手动定义一个部署目标。一旦你明白了,你可以创建一个自定义的构建后命令,该命令仅在你明确地在 deploy 目标上运行 make 时执行。这允许您通过单独的目标执行命令。

在您的 CMakeLists.txt 中可能如下所示:

cmake_minimum_required(VERSION 3.0)

add_executable("App" <sources>)

# option 1: do deployment stuff only when installing
install(SCRIPT <dir>/post_install.cmake)

# option 2: define a deploy target and add post-build commands
add_custom_target("deploy")
add_custom_command(TARGET "deploy" POST_BUILD <some command>)

这两种方法都允许您将开发构建与昂贵的准备部署构建分开(如果我理解正确,这就是这里的目标)。我会推荐选项 1,因为它更干净。

希望这会有所帮助!

【讨论】:

  • 好的,但我还是不明白为什么 install 不是你想要的。它完全符合您的要求:提供一个目标以使您的产品为部署做好准备(修复库/资源路径,复制内容),同时为开发和部署构建分离构建配置。
  • make install 实际上与make bar 相同。但我想做的是make foo_out。如果我有 100 个主文件并想生成 100 个辅助目标,我不想创建 100 个虚拟目标(如 bar 或 install)来执行此操作。
  • 如果我想使用 if/else 来决定是在add_custom_target 还是add_custom_command 中执行命令呢?
【解决方案2】:

文档不清楚

CMake 的文档在这里不清楚。 CMake 的 Makefile 生成器确实在子 Makefile 中创建源文件 make 规则,这些规则在主 Makefile 中不可见。在主 Makefile 中,您只会找到 CMake 目标的 PHONY 规则。我知道的唯一例外是Ninja 将所有构建规则放入单个文件的 Makefiles 生成器。

将后处理步骤转换为 CMake

根据我的经验 - 如果 post_process 是一个脚本 - 您可能应该考虑使用/在 CMake 脚本中重写您的后处理步骤,因为 CMake 应该知道用于后处理的所有文件依赖项和变量(然后它将为您处理所有必要的重建或清理步骤)。

这是我所做工作的简化/修改版本:

function(my_add_elf _target)

    set(_source_list ${ARGN})
    add_executable(${_target}_in ${_source_list})

    set_target_properties(
        ${_target}_in
        PROPERTIES
            POSITION_INDEPENDENT_CODE   0
            SUFFIX                      .elf
    )

    add_custom_command(
        OUTPUT ${_target}_step1.elf
        COMMAND some_conversion_cmd $<TARGET_FILE:${_target}_in> > ${_target}_step1.elf
        DEPENDS ${_target}_in
    )

    add_custom_target(
        ${_target}_step1 
        DEPENDS 
            ${_target}_step1.elf
    )

    add_custom_command(
        OUTPUT ${_target}_out.elf
        COMMAND final_post_process_cmd ${_target}_step1.elf > ${_target}_out.elf
        DEPENDS ${_target}_step1
    )

    add_custom_target(
        ${_target}_out 
        DEPENDS 
            ${_target}_out.elf
    )

    # alias / PHONY target
    add_custom_target(${_target} DEPENDS ${_target}_out)

endfunction(my_add_elf)

然后调用

my_add_elf(foo foo.c)

这只是一个示例,但我希望它给出了一个想法:您可以调用 make foo 来获取最终的 ELF 输出,调用 make foo_inmake foo_step1 来获取其他步骤之一。而且我认为所有步骤对用户和 CMake 都是透明的。

不能为您的目标提供与输出之一相同的名称

当您尝试为自定义目标指定与其输出之一相同的名称时,例如像这样:

add_executable(foo_in foo.c)
add_custom_command(
    OUTPUT foo_out
    COMMAND post_process foo_in > foo_out
    DEPENDS foo_in
)
add_custom_target(foo_out DEPENDS foo_out)

您最终会得到无效的 make 文件。我已经 raised an issue 对此进行了讨论,希望通过扩展 CMake 本身可以找到可能的解决方案并得到以下回复:

CMake 并非旨在在 Makefile 中生成特定内容。 add_custom_target 创建的顶级目标名称始终是合乎逻辑的 (即假名)名称。根本不允许有一个文件 同名。

可能的解决方法

所以有一些解决方法,但它们都有一个或另一个缺点。

1. 最短版本:

macro(my_add_elf_we _target)
    add_executable(${_target}_in ${ARGN})
    add_custom_target(
        ${_target}_out 
        COMMAND post_process $<TARGET_FILE:${_target}_in> > ${_target}_out
        DEPENDS ${_target}_in
    ) 
    set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${_target}_out)
endmacro(my_add_elf_we)

您不能在add_custom_target() 本身中声明OUTPUTs,但在这种情况下您不想这样做(因为您不想有任何命名混淆)。但是如果你不声明任何输出:

  • 目标将始终被视为已过期
  • 您需要在clean 构建规则中添加“不可见”输出

2.强制输出名称版本

这是上述宏的一个版本,它强制目标和输出名称为给定值:

macro(my_add_elf_in_out _target_in _target_out)
    add_executable(${_target_in} ${ARGN})
    set_target_properties(
        ${_target_in}
        PROPERTIES
            SUFFIX          ""
            OUTPUT_NAME     "${_target_in}"
    )
    add_custom_target(
        ${_target_out}
        COMMAND post_process ${_target_in} > ${_target_out}
        DEPENDS ${_target_in}
    ) 
    set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${_target_out})
endmacro(my_add_elf_in_out)

你调用它:

my_add_elf_in_out(foo_in.elf foo_out.elf foo.c)

3.对象库版本

以下版本使用对象库,但系统不会重用foo_in目标链接:

macro(my_add_elf_obj_in_out _target_in _target_out)

    add_library(${_target_in}_obj OBJECT ${ARGN})

    add_executable(${_target_in} $<TARGET_OBJECTS:${_target_in}_obj>)
    set_target_properties(
        ${_target_in}
        PROPERTIES
            SUFFIX          ""
            OUTPUT_NAME     "${_target_in}"
    )

    add_executable(${_target_out} $<TARGET_OBJECTS:${_target_in}_obj>)
    set_target_properties(
        ${_target_out}
        PROPERTIES
            SUFFIX              ""
            OUTPUT_NAME         "${_target_out}"
            EXCLUDE_FROM_ALL    1
    )
    add_custom_command(
        TARGET ${_target_out}
        POST_BUILD
        COMMAND post_process ${_target_in} > ${_target_out}
    )

endmacro(my_add_elf_obj_in_out)

4.最后和最终版本

还有一个绝对只适用于 Makefile 生成器的最终版本,这让我在 CMake 的错误跟踪器上发布了这个问题:

macro(my_add_elf_ext_in_out _target_in _target_out)

    add_executable(${_target_in} ${ARGN})
    set_target_properties(
        ${_target_in}
        PROPERTIES
            SUFFIX          ""
            OUTPUT_NAME     "${_target_in}"
    )
    add_executable(${_target_out} NotExisting.c)
    set_source_files_properties(
        NotExisting.c
        PROPERTIES
            GENERATED           1
            HEADER_FILE_ONLY    1
    )
    set_target_properties(
        ${_target_out}
        PROPERTIES
            SUFFIX              ""
            OUTPUT_NAME         "${_target_out}"
            RULE_LAUNCH_LINK    "# "
    )
    add_custom_command(
        TARGET ${_target_out}
        POST_BUILD
        COMMAND post_process ${_target_in} > ${_target_out}
    )
    add_dependencies(${_target_out} ${_target_in})

endmacro(my_add_elf_ext_in_out)

一些参考资料

【讨论】:

  • 谢谢,但这仍然不能解决问题。使用您的示例,我需要输入make foo 来生成foo.elf。我想做make foo.elf,也就是用实际的文件名作为目标。但是感谢 ADDITIONAL_MAKE_CLEAN_FILES 提示,我不知道这一点。
  • 感谢您的反馈。我再次测试了我的my_add_elf_we() 宏版本(在我的答案的下部),添加了一些修复程序,我认为它现在完全符合您的要求。输出名称和生成规则名称相同。在我的示例中,如果您的CMakeLists.txt 中有my_add_elf_we(foo foo.c),您可以调用make foo_outmake foo_in。您可以更改目标/输出的命名,也可以将 PHONY 目标添加到宏中,仅用于 foo
  • 感谢您的帮助,但这仍然不能满足我的要求。我想做make foo_out.elf,我想查看一个名为foo_out.elf 的文件。我还希望 make help 显示 foo_out.elf 作为可能的目标。
  • 我有信心找到解决方案。所以我添加了另一个版本的宏 my_add_elf_in_out(),其中目标和输出名称被强制为给定值。
  • edit #1 的问题是目标总是重新制作(因为它是自定义目标)。编辑#2 - 嗯,这很聪明,但对我来说可能太聪明了。我认为你给 cmake 开发者写信的想法很棒!
【解决方案3】:

绕过依赖关系,并使用second signature of add_custom_command,这应该可以工作:

add_custom_target(foo_out DEPENDS foo_in)
add_custom_command(TARGET foo_out POST_BUILD COMMAND post_process foo_in > foo_out)

注意:添加BYPRODUCTS foo_out 将导致(例如)ninja

多个规则生成 foo_out。涉及此目标的构建将不正确;还是继续

【讨论】:

    猜你喜欢
    • 2012-07-20
    • 1970-01-01
    • 1970-01-01
    • 2017-01-30
    • 1970-01-01
    • 1970-01-01
    • 2012-03-11
    • 1970-01-01
    相关资源
    最近更新 更多