Немедленное создание инструмента для последующего его использования в том же цикле запуска CMake
У меня есть интересная проблема курицы и яйца и потенциальное решение (см. Мой опубликованный ответ), но это решение использует CMake необычным образом. Лучше альтернативы или комментарии будут приветствоваться.
ЭТА ПРОБЛЕМА:
Простая версия проблемы может быть описана как один проект CMake со следующими характеристиками:
- Одной из целей сборки является исполняемый файл командной строки, который я назову mycomp, источник которого находится в
mycompdir
и внесение каких-либо изменений в содержимое этого каталога невозможно. - Проект содержит текстовые файлы (я назову их
foo.my
а такжеbar.my
) которые должны запускаться на mycomp для создания набора исходных текстов и заголовков C++ и некоторыхCMakeLists.txt
файлы, определяющие библиотеки, созданные из этих источников. - Другие цели сборки в том же проекте должны быть связаны с библиотеками, определенными созданными
CMakeLists.txt
файлы. Эти другие цели также имеют источники, которые#include
некоторые из сгенерированных заголовков.
Вы можете думать о mycomp как о чем-то похожем на компилятор, а текстовые файлы на шаге 2 - как некие исходные файлы. Это представляет проблему, потому что CMake нуждается в CMakeLists.txt
файлы во время настройки, но mycomp недоступен до времени сборки и поэтому недоступен при первом запуске для создания CMakeLists.txt
файлы достаточно рано.
NON-ОТВЕТ:
Как правило, потенциальным решением этой проблемы может быть компоновка надстройки на основе ExternalProject, но вышеизложенное является значительным упрощением реального проекта, с которым я имею дело, и у меня нет свободы разбивать сборку на разные части или выполнять другие большие масштабная перестройка работы.
1 ответ
Суть проблемы в том, что mycomp должен быть доступен при запуске CMake, чтобы сгенерированный CMakeLists.txt
файлы могут быть созданы, а затем извлечены с add_subdirectory()
, Возможный способ достичь этого - использовать execute_process()
запустить вложенный cmake-and-build из основной сборки. Этот вложенный cmake-and-build будет использовать те же самые исходные и двоичные каталоги, что и CMake-запуск верхнего уровня (если только не происходит кросс-компиляция). Общая структура основного верхнего уровня CMakeLists.txt
будет что-то вроде этого:
# Usual CMakeLists.txt setup stuff goes here...
if(EARLY_BUILD)
# This is the nested build and we will only be asked to
# build the mycomp target (see (c) below)
add_subdirectory(mycompdir)
# End immediately, we don't want anything else in the nested build
return()
endif()
# This is the main build, setup and execute the nested build
# to ensure the mycomp executable exists before continuing
# (a) When cross compiling, we cannot re-use the same binary dir
# because the host and target are different architectures
if(CMAKE_CROSSCOMPILING)
set(workdir "${CMAKE_BINARY_DIR}/host")
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory "${workdir}")
else()
set(workdir "${CMAKE_BINARY_DIR}")
endif()
# (b) Nested CMake run. May need more -D... options than shown here.
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}"
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}
-DEARLY_BUILD=ON
${CMAKE_SOURCE_DIR}
WORKING_DIRECTORY "${workdir}")
# (c) Build just mycomp in the nested build. Don't specify a --config
# because we cannot know what config the developer will be using
# at this point. For non-multi-config generators, we've already
# specified CMAKE_BUILD_TYPE above in (b).
execute_process(COMMAND ${CMAKE_COMMAND} --build . --target mycomp
WORKING_DIRECTORY "${workdir}")
# (d) We want everything from mycompdir in our main build,
# not just the mycomp target
add_subdirectory(mycompdir)
# (e) Run mycomp on the sources to generate a CMakeLists.txt in the
# ${CMAKE_BINARY_DIR}/foobar directory. Note that because we want
# to support cross compiling, working out the location of the
# executable is a bit more tricky. We cannot know whether the user
# wants debug or release build types for multi-config generators
# so we have to choose one. We cannot query the target properties
# because they are only known at generate time, which is after here.
# Best we can do is hardcode some basic logic.
if(MSVC)
set(mycompsuffix "Debug/mycomp.exe")
elseif(CMAKE_GENERATOR STREQUAL "Xcode")
set(mycompsuffix "Debug/mycomp")
else()
set(mycompsuffix "mycomp")
endif()
set(mycomp_EXECUTABLE "${workdir}/mycompdir/${mycompsuffix}")
execute_process(COMMAND "${mycomp_EXECUTABLE}" -outdir foobar ${CMAKE_SOURCE_DIR}/foo.my ${CMAKE_SOURCE_DIR}/bar.my)
# (f) Now pull that generated CMakeLists.txt into the main build.
# It will create a CMake library target called foobar.
add_subdirectory(${CMAKE_BINARY_DIR}/foobar ${CMAKE_BINARY_DIR}/foobar-build)
# (g) Another target which links to the foobar library
# and includes headers from there
add_executable(gumby gumby.cpp)
target_link_libraries(gumby PUBLIC foobar)
target_include_directories(gumby PUBLIC foobar)
Если мы не будем повторно использовать тот же двоичный каталог в (b) и (c), который мы используем для основной сборки, мы закончим сборку mycomp
дважды, чего мы явно хотим избежать. Для кросс-компиляции мы не можем избежать этого, поэтому в таких случаях мы создаем mycomp
инструмент в сторону в отдельном двоичном каталоге.
Я экспериментировал с вышеупомянутым подходом, и, похоже, он действительно работает в реальном проекте, который вызвал оригинальный вопрос, по крайней мере, для генераторов файлов Unix Makefiles, Ninja, Xcode (OS X и iOS) и Visual Studio. Частью привлекательности этого подхода является то, что он требует лишь небольшого количества кода, добавляемого только на верхний уровень CMakeLists.txt
файл. Тем не менее, есть некоторые замечания, которые следует сделать:
- Если команды компилятора или компоновщика для
mycomp
и его источники в любом случае различаются между вложенной сборкой и основной сборкой,mycomp
цель заканчивает тем, что была восстановлена во второй раз в (d). Если нет различий,mycomp
создается только один раз, когда не выполняется кросс-компиляция, а это именно то, что нам нужно. - Я не вижу простого способа передать точно такие же аргументы для вложенного вызова CMake в (b), который был передан в запуск CMake верхнего уровня (в основном проблема, описанная здесь). чтение
CMakeCache.txt
не вариант, так как он не будет существовать при первом вызове, и он не даст вам никаких новых или измененных аргументов из текущего выполнения в любом случае. Лучшее, что я могу сделать, - это установить те переменные CMake, которые, я думаю, потенциально будут использоваться и которые могут повлиять на команды компилятора и компоновщика.mycomp
, Это можно обойти, добавляя все больше и больше переменных по мере того, как я сталкиваюсь с необходимыми, но это не идеально. - При повторном использовании одного и того же двоичного каталога мы полагаемся на то, что CMake не начнет записывать какие-либо свои файлы в двоичный каталог до этапа генерации (ну, по крайней мере, до завершения сборки в (c)). Похоже, что с протестированными генераторами у нас все в порядке, но я не знаю, все ли генераторы на всех платформах также следуют этому поведению (и я не могу проверить каждую комбинацию, чтобы узнать!). Это та часть, которая вызывает у меня наибольшее беспокойство. Если кто-либо может подтвердить с помощью рассуждений и / или доказательств того, что это безопасно для всех генераторов и платформ, это было бы полезно (и стоило бы возразить, если вы хотите рассмотреть это как отдельный ответ).
ОБНОВЛЕНИЕ: После использования вышеупомянутой стратегии в ряде реальных проектов с персоналом различного уровня знания CMake, можно сделать некоторые наблюдения.
Наличие во вложенной сборке повторного использования того же каталога сборки, что и основная сборка, может иногда приводить к проблемам. В частности, если пользователь завершает запуск CMake после завершения вложенной сборки, но до выполнения основной сборки,
CMakeCache.txt
файл оставлен сEARLY_BUILD
установлен вON
, Затем все последующие запуски CMake действуют как вложенная сборка, поэтому основная сборка по существу теряется доCMakeCache.txt
файл удален вручную. Возможно, что ошибка где-то в одном из проектовCMakeLists.txt
файл также может привести к аналогичной ситуации (неподтвержденные). Выполнение вложенной сборки в стороне в своем отдельном каталоге сборки работало очень хорошо, хотя без таких проблем.Вложенная сборка, вероятно, должна быть Release, а не Debug. Если мы не будем повторно использовать тот же каталог сборки, что и основная сборка (теперь то, что я бы порекомендовал), нам больше не нужно пытаться избегать компиляции одного и того же файла дважды, поэтому мы также можем сделать mycomp максимально быстрым.
Используйте ccache, чтобы минимизировать любые затраты, связанные с повторной сборкой нескольких файлов с разными настройками. На самом деле, мы обнаружили, что использование ccache обычно делает вложенную сборку очень быстрой, поскольку она редко изменяется по сравнению с основной сборкой.
Вложенная сборка, вероятно, должна иметь
CMAKE_BUILD_WITH_INSTALL_RPATH
установлен вFALSE
на некоторых платформах, так что любые библиотеки mycomp могут быть найдены без необходимости установки переменных окружения и т. д.