Получение CMake для сборки из исходного кода без упаковки скриптов

Я пытаюсь заставить CMake встроить в каталог 'build', как в project/build, где CMakeLists.txt находится в project/.

Я знаю, что могу сделать:

mkdir build
cd build
cmake ../

но это громоздко Я мог бы поместить его в скрипт и вызвать его, но тогда было бы неприятно предоставлять разные аргументы CMake (например, -G "MSYS Makefiles"), или мне нужно было бы редактировать этот файл на каждой платформе.

Желательно, чтобы я делал что-то вроде SET(CMAKE_OUTPUT_DIR build) в основном CMakeLists.txt. Пожалуйста, скажите мне, что это возможно, и если да, то как? Или какой-то другой метод сборки вне исходного кода, который позволяет легко указывать разные аргументы?

Ответ 1

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"

Обратите внимание, что между параметром и путем к каталогу не должно быть пробела.

Ответ 2

Решение, которое я нашел недавно, состоит в том, чтобы объединить концепцию сборки вне исходного кода с оболочкой 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.

P.S. В Makefile мы используем префикс @ для подавления вывода из команды оболочки, а префикс @- - игнорировать ошибки из команды оболочки. При использовании rm как части цели distclean команда возвращает ошибку, если файлы не существуют (они, возможно, уже были удалены с использованием командной строки с помощью rm -rf build, или они никогда не были сгенерированы в первом место). Эта обратная ошибка заставит наш Makefile выйти. Мы используем префикс @-, чтобы предотвратить это. Это приемлемо, если файл уже был удален; мы хотим, чтобы наш Makefile продолжал идти и удалял остальные.

Еще одно замечание: этот Makefile может не работать, если вы используете переменное число переменных CMake для создания вашего проекта, например cmake .. -DSOMEBUILDSUSETHIS:STRING="foo" -DSOMEOTHERBUILDSUSETHISTOO:STRING="bar". Этот Makefile предполагает, что вы последовательно вызываете CMake, либо набрав cmake .., либо предоставив cmake последовательное количество аргументов (которые вы можете включить в свой Makefile).

Наконец, кредит, в котором должен быть кредит. Эта оболочка Makefile была адаптирована из Makefile, предоставленной С++ Application Project Template.

Этот ответ был изначально опубликован здесь. Я думал, что это применимо и к вашей ситуации.

Ответ 3

Основываясь на предыдущих ответах, я написал следующий модуль, который можно включить для принудительной сборки внекорпоративной сборки.

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, не будут переданы "вызову сборки вне сервера".

Предложения по улучшению этого модуля приветствуются.