add_custom_command没有生成目标。

28

也许这是不可能的,我可能误读了cmake 3.2文档,但是我认为创建一个自定义命令会在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.

果然,在cmake二进制输出目录中的任何文件中都不存在"foo_out"这个词。如果我将其更改为:

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


1
我认为你需要将OUTPUT foo指定为任何其他目标的输入(不一定是自定义目标)。add_custom_command()文档中说:“这定义了一个命令以生成指定的OUTPUT文件。在同一目录(CMakeLists.txt文件)中创建的目标,只要将自定义命令的任何输出指定为源文件,就会得到一个规则来使用构建时的命令生成该文件。” - Florian
@Florian - 在我的示例“bar”目标中,即使“bar”依赖于“foo”,我仍然无法直接构建“foo”(如果我执行“make foo”,什么也不会发生)。 - Mark Lakata
感谢提供的额外细节。在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 > foo_out)set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES foo_out)。虽然不太好看,但名称是匹配的... - Florian
@Florian 我刚试了一下,使用Unix Makefile生成器,目标文件在我添加add_costum_command的目录和构建根目录中都能找到。但是文档缺乏细节说明。 - usr1234567
3个回答

16

add_custom_command 并不会创建新的目标。你需要通过 add_executableadd_library 或者 add_custom_target 显式地定义目标,以便它们被传递给 make。

如果您需要调整部署细节,您可以:

1. 在您的 CMakeLists.txt 中使用 install 命令,例如:

install(SCRIPT <dir>/post_install.cmake)

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

2. 手动定义一个deploy目标。一旦你创建了这个目标,就可以创建一个自定义的后置构建命令,只有在显式运行deploy目标时才会执行。这允许你通过一个单独的目标来执行命令。

在你的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,因为它更加清晰。

希望这可以帮到你!


好的,但我仍然不明白为什么你不想在这里使用安装。它正是你想要的:为产品提供一个目标,使其准备好部署(修复lib/resource路径,复制文件),同时将开发和部署构建的配置分离。 - qCring
1
"make install" 实际上和 "make bar" 是一样的。但我想要做的是 "make foo_out"。如果我有100个主文件,想生成100个次要目标,我不想创建100个虚拟目标(如bar或install)来完成这个任务。 - Mark Lakata
如果我想使用if/else来决定是否在add_custom_targetadd_custom_command中执行命令,该怎么办? - Lewis Chan

11

文档不清晰

CMake的文档在这里并不清晰。CMake的Makefiles生成器会在子Makefiles中创建源文件的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)

由于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() 中不能声明 OUTPUT,但在这种情况下您不需要这样做(因为您不想有任何命名混淆)。但如果您没有声明任何输出:

  • 目标将始终被视为过时
  • 您需要在 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 的提示,我之前不知道。 - Mark Lakata
感谢您的反馈。我再次测试了我的 my_add_elf_we() 宏版本(在我的答案的下半部分),添加了一些修复,并且我认为它现在完全符合您的要求。输出名称和制作规则名称相同。在我的示例中,如果您在 CMakeLists.txt 中有 my_add_elf_we(foo foo.c),则可以调用 make foo_outmake foo_in。您可以更改目标/输出的命名,或者还可以为宏添加一个 PHONY 目标,仅针对 foo - Florian
我有信心找到解决方案。因此,我添加了另一个版本的宏 my_add_elf_in_out(),其中目标和输出名称被强制设置为给定的值。 - Florian
是的,我相信正确的解决方案是改进/修复CMake本身,并在CMake的错误跟踪器中提交了一个问题:0015614:add_custom_target/command:无法将目标命名为其中一个输出 - Florian
增加了CMake团队的回应和对象库版本。 - Florian
显示剩余4条评论

2

通过改变依赖关系,使用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。涉及此目标的构建将不正确;但仍会继续。


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