Получение cmake для сборки из исходного кода без упаковки сценариев?
Я пытаюсь заставить cmake встроить в каталог 'build', как в project/build
где CMakeLists.txt находится в project/
,
Я знаю, что могу сделать:
mkdir build
cd build
cmake ../
но это громоздко Я мог бы поместить его в скрипт и вызвать его, но тогда было бы неприятно предоставлять разные аргументы для cmake (например, -G "MSYS Makefiles"), или мне нужно было бы редактировать этот файл на каждой платформе.
Желательно, чтобы я делал что-то вроде SET(сборка CMAKE_OUTPUT_DIR) в основном CMakeLists.txt. Пожалуйста, скажите мне, что это возможно, и если да, то как? Или какой-то другой метод сборки из исходного кода, который позволяет легко указывать другие аргументы?
4 ответа
CMake 3.13 или новее поддерживает параметры командной строки -S
а также -B
указать исходный и двоичный каталог соответственно.
cmake -S . -B build -G "MSYS Makefiles"
Это будет искать CMakeLists.txt
в текущей папке и создайте build
папка (если она еще не существует) в нем.
Для более старых версий CMake вы можете использовать недокументированные параметры CMake. -H
а также -B
указать исходный и двоичный каталог при вызове cmake
:
cmake -H. -Bbuild -G "MSYS Makefiles"
Обратите внимание, что между параметром и путем к каталогу не должно быть пробела.
Решение, которое я недавно нашел, состоит в том, чтобы объединить концепцию сборки из исходного кода с оболочкой Makefile.
В мой файл CMakeLists.txt верхнего уровня я включаю следующее, чтобы предотвратить сборку в исходном коде:
if ( ${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR} )
message( FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt." )
endif()
Затем я создаю Makefile верхнего уровня и включаю следующее:
# -----------------------------------------------------------------------------
# CMake project wrapper Makefile ----------------------------------------------
# -----------------------------------------------------------------------------
SHELL := /bin/bash
RM := rm -rf
MKDIR := mkdir -p
all: ./build/Makefile
@ $(MAKE) -C build
./build/Makefile:
@ ($(MKDIR) build > /dev/null)
@ (cd build > /dev/null 2>&1 && cmake ..)
distclean:
@ ($(MKDIR) build > /dev/null)
@ (cd build > /dev/null 2>&1 && cmake .. > /dev/null 2>&1)
@- $(MAKE) --silent -C build clean || true
@- $(RM) ./build/Makefile
@- $(RM) ./build/src
@- $(RM) ./build/test
@- $(RM) ./build/CMake*
@- $(RM) ./build/cmake.*
@- $(RM) ./build/*.cmake
@- $(RM) ./build/*.txt
ifeq ($(findstring distclean,$(MAKECMDGOALS)),)
$(MAKECMDGOALS): ./build/Makefile
@ $(MAKE) -C build $(MAKECMDGOALS)
endif
Цель по умолчанию all
вызывается набрав make
и вызывает цель ./build/Makefile
,
Первое, что цель ./build/Makefile
делает, чтобы создать build
использование каталога $(MKDIR)
, которая является переменной для mkdir -p
, Каталог build
Здесь мы выполним нашу сборку вне исходного кода. Мы предоставляем аргумент -p
чтобы убедиться, что mkdir
не кричит на нас за попытку создать каталог, который может уже существовать.
Второе, что цель ./build/Makefile
делает, чтобы изменить каталоги на build
каталог и вызвать cmake
,
Назад к all
цель, мы вызываем $(MAKE) -C build
, где $(MAKE)
переменная Makefile, автоматически сгенерированная для make
, make -C
меняет каталог, прежде чем делать что-либо. Поэтому, используя $(MAKE) -C build
эквивалентно делать cd build; make
,
Подводя итог, можно назвать эту оболочку Makefile с помощью make all
или же make
эквивалентно выполнению:
mkdir build
cd build
cmake ..
make
Цель distclean
Запускает cmake ..
, затем make -C build clean
и, наконец, удаляет все содержимое из build
каталог. Я считаю, что это именно то, что вы просили в своем вопросе.
Последний фрагмент Makefile оценивает, является ли предоставленная пользователем цель или нет distclean
, Если нет, он изменит каталоги на build
прежде чем вызывать его. Это очень мощно, потому что пользователь может напечатать, например, make clean
и Makefile преобразует это в эквивалент cd build; make clean
,
В заключение, эта оболочка Makefile в сочетании с обязательной конфигурацией CMake для сборки из источника делает ее такой, чтобы пользователю никогда не приходилось взаимодействовать с командой cmake
, Это решение также предоставляет элегантный метод для удаления всех выходных файлов CMake из build
каталог.
PS В Makefile мы используем префикс @
подавить вывод команды оболочки и префикс @-
игнорировать ошибки из команды оболочки. Когда используешь rm
как часть distclean
target, команда вернет ошибку, если файлы не существуют (возможно, они уже были удалены с помощью командной строки с rm -rf build
или они никогда не были созданы в первую очередь). Эта ошибка возврата заставит наш Makefile выйти. Мы используем префикс @-
чтобы предотвратить это. Это приемлемо, если файл уже был удален; мы хотим, чтобы наш Makefile продолжал работать и удалял остальные.
Еще одна вещь, которую стоит отметить: этот Makefile может не работать, если вы используете переменное число переменных CMake для построения вашего проекта, например, cmake .. -DSOMEBUILDSUSETHIS:STRING="foo" -DSOMEOTHERBUILDSUSETHISTOO:STRING="bar"
, Этот Makefile предполагает, что вы вызываете CMake последовательным способом, либо набирая cmake ..
или предоставляя cmake
постоянное количество аргументов (которые вы можете включить в ваш Makefile).
Наконец, кредит, где кредит должен. Эта оболочка Makefile была адаптирована из Makefile, предоставленного шаблоном проекта приложения C++.
Этот ответ был первоначально размещен здесь. Я думал, что это относится и к вашей ситуации.
Основываясь на предыдущих ответах, я написал следующий модуль, который вы можете включить для обеспечения сборки из исходного кода.
set(DEFAULT_OUT_OF_SOURCE_FOLDER "cmake_output")
if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
message(WARNING "In-source builds not allowed. CMake will now be run with arguments:
cmake -H. -B${DEFAULT_OUT_OF_SOURCE_FOLDER}
")
# Run CMake with out of source flag
execute_process(
COMMAND ${CMAKE_COMMAND} -H. -B${DEFAULT_OUT_OF_SOURCE_FOLDER}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
# Cause fatal error to stop the script from further execution
message(FATAL_ERROR "CMake has been ran to create an out of source build.
This error prevents CMake from running an in-source build.")
endif ()
Это работает, однако я уже заметил два недостатка:
- Когда пользователь ленится и просто запускает
cmake .
они всегда будут видетьFATAL_ERROR
, Я не мог найти другой способ предотвратить CMake от выполнения каких-либо других операций и досрочного выхода. - Все аргументы командной строки, переданные исходному вызову
cmake
не будет передано "вызову сборки из источника".
Предложения по улучшению этого модуля приветствуются.
Хитрость заключается в том, чтобы написать сценарий-обертку... в CMake!
Скрипт Bash или оболочка Makefile потребуют, чтобы в их системе были установлены Bash или Make, но сценарий, написанный на CMake, будет работать на всех платформах без введения каких-либо новых зависимостей.
Мы можем воспользоваться преимуществами CMake-P
вариант , который:
Обработайте данный файл cmake как сценарий, написанный на языке CMake. Никакие шаги настройки или генерации не выполняются, и кеш не изменяется.
Внутри скрипта мы будем использоватьfile()
команду для создания каталога сборки, затем используйтеexecute_process()
чтобы снова запустить CMake, на этот раз с параметрами, необходимыми для создания системы сборки. Поскольку параметры указаны в сценарии, вам не придется каждый раз вводить их вручную в командной строке.
Создайте новый текстовый файл с именемв папке проекта и вставьте следующий код:
#!/usr/bin/env -S cmake -P
# Set default values for project variables
set(SOURCE_PATH "${CMAKE_CURRENT_LIST_DIR}") # directory of this script
set(BUILD_PATH "${SOURCE_PATH}/build")
set(CPUS 16) # number of logical processor cores (for parallel builds)
# Set generator for each platform
if(WIN32)
set(GENERATOR "MSYS Makefiles")
elseif(APPLE)
set(GENERATOR "Xcode")
else()
set(GENERATOR "Ninja")
endif()
# Allow developers to override the above variables if they want
if(EXISTS "${SOURCE_PATH}/overrides.cmake")
include("${SOURCE_PATH}/overrides.cmake")
endif()
# Configure
if(NOT EXISTS "${BUILD_PATH}/CMakeCache.txt")
file(MAKE_DIRECTORY "${BUILD_PATH}")
execute_process(
# CMAKE_ARGS on next line must not be quoted
COMMAND "${CMAKE_COMMAND}" -S "${SOURCE_PATH}" -B . -G "${GENERATOR}" ${CMAKE_ARGS}
WORKING_DIRECTORY "${BUILD_PATH}"
RESULT_VARIABLE EXIT_STATUS
)
if(NOT "${EXIT_STATUS}" EQUAL "0")
file(REMOVE "${BUILD_PATH}/CMakeCache.txt") # force CMake to run again next time
message(FATAL_ERROR "CMake failed with status ${EXIT_STATUS}.")
endif()
endif()
# Build
execute_process(
COMMAND "${CMAKE_COMMAND}" --build . --parallel "${CPUS}"
WORKING_DIRECTORY "${BUILD_PATH}"
RESULT_VARIABLE EXIT_STATUS
)
if(NOT "${EXIT_STATUS}" EQUAL "0")
message(FATAL_ERROR "${GENERATOR} failed with status ${EXIT_STATUS}.")
endif()
Важно: файл должен использовать LF в стиле Unix(
\n
) терминаторы линии для работы линии shebang .
-S
вариант для
env
в некоторых системах требуется, чтобы можно было указать несколько аргументов в строке shebang.
Теперь вы можете собрать проект, просто запустив это в командной строке:
# From any Unix shell (Linux, macOS, WSL, Git Bash)
./build.cmake
# CMD or PowerShell
cmake -P build.cmake
Это здорово, но все переменные проекта были жестко запрограммированы, поэтому они будут одинаковыми для всех разработчиков. Что если я захочу построить проект с другим набором переменных?
Без проблем! Просто создайте еще один файл с именемв том же каталоге, что и . Укажите нужные вам значения, и они будут иметь приоритет над значениями в .
Например, у вас может быть:
set(CPUS 32) # override default CPUS set in build.cmake
set(GENERATOR "Visual Studio 17 2022") # use different generator
# Pass some options to CMake
set(CMAKE_ARGS
"-DFIRST_OPTION=Value 1"
"-DSECOND_OPTION=Value 2"
)
set(ENV{PATH} "C:/Tools;$ENV{PATH}") # add a directory to PATH
Важно: файл
overrides.cmake
необходимо добавить в ваш проект
.gitignore
чтобы позволить каждому разработчику указывать свои собственные переопределения.
Взгляните на MuseScorebuild.cmake
и документацию для разработчиков, где можно найти дополнительные идеи, например, как читать и обрабатывать аргументы, передаваемые сценарию в командной строке, и как использовать MSVC в качестве компилятора.