Получение 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 в качестве компилятора.

Другие вопросы по тегам