Add_custom_command не создает цель

Возможно, это невозможно, и я неправильно читаю документацию cmake 3.2, но, хотя создание пользовательской команды создало бы пользовательскую "целевую" в Makefile, чтобы я мог создать цель, вызывая имя выходного файла. В документах CMake говорится:

В make файлах создается новая цель в следующей форме:

 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 not bar.

Как мне это сделать?

В моем случае мне нужно запустить специальный шаг обработки к стандартной исполняемой цели, которая вставляет дополнительные файлы в файл ELF. Я хотел бы иметь возможность иметь оба исполняемых файла в качестве целевых файлов Makefile, поэтому я могу создать готовый исполняемый файл ELF, а также исполняемый файл ELF с вложением ресурсов.

Если бы я писал пользовательский Makefile, это тривиально!

foo_in: foo.c
    $(CC) $< -o [email protected]

foo_out: foo_in
    post_process $< > [email protected]   

И я могу сделать make foo_in и make foo_out.

Ответ 1

add_custom_command создает не новую цель. Вы должны явно определить цели с помощью add_executable, add_library или add_custom_target, чтобы сделать их видимыми.

Если вам нужно исправить ситуацию для развертывания, вы можете

1. используйте команду install (где-то в вашем CMakeLists.txt) следующим образом:

install(SCRIPT <dir>/post_install.cmake)

для хранения команд, которые выполняются только при запуске make install в отдельном файле .cmake. Или, если цель установки уже зарезервирована для других вещей или у вас есть более сложные вещи:

2. вручную определить цель развертывания. После этого вы можете создать пользовательскую команду post-build, которая будет выполняться только при явном запуске 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>)

Оба подхода позволяют вам разделить сборки dev от дорогостоящих готовых к развертыванию сборок (если я правильно понимаю, что цель здесь). Я бы порекомендовал вариант 1, поскольку он просто чище.

Надеюсь, это поможет!

Ответ 2

Документация Нечетко

Документация CMake здесь неясна. Генераторы Makefile от CMake создают правила создания исходного файла в субфайловых файлах, которые не видны в основном Makefile. В главном Makefile вы найдете только PHONY правила для ваших целей CMake. Единственное исключение, о котором я знаю, это Ninja Генератор Makefiles, который ставит все правила сборки в один файл.

Перевод шагов после обработки в CMake

Из моего опыта - если post_process является script - вам, вероятно, следует подумать о переписывании шагов вашей последующей обработки с помощью/внутри сценариев 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_in или make 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. Я поднял вопрос об этом в надежде, что может быть возможное решение, расширив сам 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)

Вы не можете объявить OUTPUT в самой add_custom_target(), но в этом случае вы не хотите (потому что вы не хотите иметь путаницы в именах). Но если вы не объявляете какие-либо выходы:

  • Цель всегда будет считаться устаревшей.
  • Вам нужно добавить "невидимые" выходы в правило построения 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 bug tracker:

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)

Некоторые ссылки

Ответ 3

Оборачивая зависимости и используя вторую подпись 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. сборки, связанные с этой целью, будут неверными; продолжая в любом случае