Руководство по CMake
- Введение
- Добавление библиотеки (Шаг 2)
- Добавление требований к библиотеке (Шаг 3)
- Добавление пользовательской команды и сгенерированного файла (Шаг 6)
- Создание установщика (Шаг 7)
- Добавление поддержки панели управления (Шаг 8)
- Смешивание статических и динамических библиотек (Шаг 9)
- Добавление выражений генератора (Шаг 10)
- Добавление конфигурации экспорта (Шаг 11)
- Упаковка отладочной и релизной версий (Шаг 12)
Введение
Руководство по CMake предоставляет пошаговое руководство, которое охватывает общие проблемы систем сборки, с которыми помогает справиться CMake. Очень полезно увидеть, как различные темы взаимодействуют в примере проекта. Документация руководства и исходный код примеров находятся в каталоге Help/guide/tutorial в дереве исходного кода CMake. Каждый шаг имеет свой подкаталог с кодом, который можно использовать в качестве отправной точки. Примеры в руководстве поэтапно наращиваются, так что каждый шаг предоставляет полное решение для предыдущего шага.
Начальный проект (Шаг 1)
Самый простой проект — это исполняемый файл, созданный из исходных файлов. Для простых проектов достаточно файла CMakeLists.txt из трёх строк. Это будет отправной точкой для нашего руководства. Создайте файл CMakeLists.txt в каталоге Step1, который будет выглядеть так:
cmake_minimum_required(VERSION 3.10) # set the project name project(Tutorial) # add the executable add_executable(Tutorial tutorial.cxx)
Обратите внимание, что в этом примере в файле CMakeLists.txt используются команды в нижнем регистре. CMake поддерживает команды в верхнем, нижнем и смешанном регистре. Исходный код для tutorial.cxx приведен в каталоге Step1 и может быть использован для вычисления квадратного корня из числа.
Добавление номера версии и конфигурируемого заголовочного файла
Первое, что мы добавим, — это номер версии нашего исполняемого файла и проекта. Мы можем сделать это непосредственно в исходном коде, но использование CMakeLists.txt обеспечивает большую гибкость.
Сначала измените файл CMakeLists.txt для использования команды project() для задания имени проекта и номера версии.
cmake_minimum_required(VERSION 3.10) # set the project name and version project(Tutorial VERSION 1.0)
Затем, настройте заголовочный файл для передачи номера версии в исходный код:
configure_file(TutorialConfig.h.in TutorialConfig.h)
Так как конфигурируемый файл будет записан в дерево бинарных файлов, нам необходимо добавить этот каталог в список путей для поиска файлов заголовков. Добавьте следующие строки в конец файла CMakeLists.txt.
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
Используя ваш любимый редактор, создайте файл TutorialConfig.h.in в каталоге исходников со следующим содержимым:
// the configured options and settings for Tutorial #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@ #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
При конфигурации CMake этого заголовочного файла значения для @Tutorial_VERSION_MAJOR@ и @Tutorial_VERSION_MINOR@ будут заменены.
Далее, измените файл tutorial.cxx для включения конфигурированного заголовочного файла TutorialConfig.h.
Наконец, давайте выведем имя и номер версии исполняемого файла, обновив файл tutorial.cxx следующим образом:
if (argc < 2) {
// report version
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}
Укажите стандарт C++
Теперь давайте добавим некоторые возможности C++11 в наш проект, заменив atof на std::stod в файле tutorial.cxx. Одновременно удалите #include <cstdlib>.
const double inputValue = std::stod(argv[1]);
Нам необходимо явно указать в коде CMake, какие флаги следует использовать. Самый простой способ включить поддержку определенного стандарта C++ в CMake — использовать переменную CMAKE_CXX_STANDARD. Для этого руководства установите переменную CMAKE_CXX_STANDARD в файле CMakeLists.txt в значение 11 и CMAKE_CXX_STANDARD_REQUIRED в True. Убедитесь, что вы добавили объявления CMAKE_CXX_STANDARD выше вызова add_executable.
cmake_minimum_required(VERSION 3.10) # set the project name and version project(Tutorial VERSION 1.0) # specify the C++ standard set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED True)
Сборка и тестирование
Запустите исполняемый файл cmake или cmake-gui для конфигурации проекта, а затем соберите его с помощью выбранного вами инструмента сборки.
Например, из командной строки мы можем перейти в каталог Help/guide/tutorial в дереве исходного кода CMake и создать каталог для сборки:
mkdir Step1_build
Затем перейдите в каталог сборки и запустите CMake для конфигурации проекта и генерации системы сборки:
cd Step1_build cmake ../Step1
Затем вызовите эту систему сборки для фактической компиляции/связывания проекта:
cmake --build .
Наконец, попробуйте использовать только что собранный исполняемый файл Tutorial с помощью этих команд:
Tutorial 4294967296 Tutorial 10 Tutorial
Добавление библиотеки (Шаг 2)
Теперь мы добавим библиотеку в наш проект. Эта библиотека будет содержать наше собственное реализацию вычисления квадратного корня из числа. Исполняемый файл сможет использовать эту библиотеку вместо стандартной функции квадратного корня, предоставляемой компилятором.
Для этого руководства мы поместим библиотеку в подкаталог MathFunctions. Этот каталог уже содержит заголовочный файл MathFunctions.h и исходный файл mysqrt.cxx. Исходный файл содержит одну функцию под названием mysqrt, которая предоставляет функциональность, аналогичную функции компилятора sqrt.
Добавьте следующий файл CMakeLists.txt в каталог MathFunctions:
add_library(MathFunctions mysqrt.cxx)
Для использования новой библиотеки мы добавим вызов add_subdirectory() в файл CMakeLists.txt верхнего уровня, чтобы библиотека была собрана. Мы добавляем новую библиотеку в исполняемый файл и добавляем MathFunctions в качестве каталога заголовков, чтобы файл заголовка mqsqrt.h был найден. Последние несколько строк файла CMakeLists.txt верхнего уровня должны теперь выглядеть так:
# add the MathFunctions library
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
Теперь давайте сделаем библиотеку MathFunctions необязательной. Хотя в этом руководстве нет необходимости, для более крупных проектов это обычно встречается. Первый шаг – добавить опцию в файл CMakeLists.txt верхнего уровня.
option(USE_MYMATH "Use tutorial provided math implementation" ON) # configure a header file to pass some of the CMake settings # to the source code configure_file(TutorialConfig.h.in TutorialConfig.h)
Эта опция будет отображаться в cmake-gui и ccmake с начальным значением ON, которое может быть изменено пользователем. Это значение будет храниться в кэше, чтобы пользователю не приходилось устанавливать его каждый раз, когда он запускает CMake в каталоге сборки.
Следующее изменение – сделать сборку и связывание библиотеки MathFunctions условными. Для этого мы изменяем конец файла CMakeLists.txt верхнего уровня, чтобы он выглядел так:
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()
# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
${EXTRA_INCLUDES}
)
Обратите внимание на использование переменной EXTRA_LIBS для сбора любых необязательных библиотек, чтобы позже их связать с исполняемым файлом. Переменная EXTRA_INCLUDES используется аналогичным образом для необязательных заголовочных файлов. Это классический подход при работе со множеством необязательных компонентов; мы рассмотрим современный подход в следующем шаге.
Соответствующие изменения в исходном коде довольно просты. Во-первых, в tutorial.cxx, включите заголовочный файл MathFunctions.h если это необходимо:
#ifdef USE_MYMATH # include "MathFunctions.h" #endif
Затем, в том же файле, сделайте USE_MYMATH управляет выбором функции квадратного корня:
#ifdef USE_MYMATH const double outputValue = mysqrt(inputValue); #else const double outputValue = sqrt(inputValue); #endif
Поскольку исходный код теперь требует USE_MYMATH, мы можем добавить его в TutorialConfig.h.in с помощью следующей строки:
#cmakedefine USE_MYMATH
Упражнение: Почему важно, чтобы мы настроили TutorialConfig.h.in после опции для USE_MYMATH? Что произойдет, если мы поменяем их местами?
Запустите исполняемый файл cmake или cmake-gui для конфигурации проекта и затем соберите его с помощью выбранного инструмента сборки. Затем запустите собранный исполняемый файл Tutorial.
Теперь обновим значение USE_MYMATH. Самый простой способ — использовать cmake-gui или ccmake, если вы работаете в терминале. Или, если вы хотите изменить параметр из командной строки, попробуйте:
cmake ../Step2 -DUSE_MYMATH=OFF
Перестройте и снова запустите учебник.
Какая функция даёт лучшие результаты, sqrt или mysqrt?
Добавление требований к использованию библиотеки (Шаг 3)
Требования к использованию позволяют гораздо лучше контролировать строки связывания и включения библиотеки или исполняемого файла, а также обеспечивают больший контроль над переходным свойством целей внутри CMake. Основные команды, использующие требования к использованию, следующие:
target_compile_definitions()target_compile_options()target_include_directories()target_link_libraries()
Давайте переработаем наш код из Добавление библиотеки (Шаг 2), чтобы использовать современный подход CMake с требованиями к использованию. Сначала мы укажем, что любой, кто связывает MathFunctions, должен включить текущий каталог исходных файлов, в то время как MathFunctions сам этого не делает. Поэтому это может стать требованием к использованию INTERFACE.
Помните, что INTERFACE означает вещи, которые потребители требуют, но производитель — нет. Добавьте следующие строки в конец MathFunctions/CMakeLists.txt:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
Теперь, когда мы указали требования к использованию для MathFunctions, мы можем безопасно удалить использование переменной EXTRA_INCLUDES из верхнего уровня CMakeLists.txt, здесь:
if(USE_MYMATH) add_subdirectory(MathFunctions) list(APPEND EXTRA_LIBS MathFunctions) endif()
И здесь:
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
После этого выполните исполняемый файл cmake или cmake-gui для конфигурации проекта, а затем скомпилируйте его с помощью выбранного вами инструмента сборки или с помощью cmake --build . из каталога сборки.
Установка и тестирование (Шаг 4)
Теперь мы можем начать добавлять правила установки и поддержку тестирования в наш проект.
Правила установки
Правила установки довольно просты: для MathFunctions мы хотим установить библиотеку и заголовочный файл, а для приложения — исполняемый файл и настроенный заголовочный файл.
Итак, в конец MathFunctions/CMakeLists.txt добавим:
install(TARGETS MathFunctions DESTINATION lib) install(FILES MathFunctions.h DESTINATION include)
И в конец верхнего уровня CMakeLists.txt добавим:
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include
)
Этого достаточно для создания базовой локальной установки учебника.
Теперь выполните исполняемый файл cmake или cmake-gui для конфигурации проекта, а затем скомпилируйте его с помощью выбранного вами инструмента сборки.
Затем выполните этап установки, используя параметр install команды cmake (введена в 3.15, более старые версии CMake должны использовать make install) из командной строки. Для многоконфигурационных инструментов не забудьте использовать аргумент --config для указания конфигурации. Если вы используете IDE, просто скомпилируйте целевой объект INSTALL. Этот шаг установит соответствующие заголовочные файлы, библиотеки и исполняемые файлы. Например:
cmake --install .
Переменная CMake CMAKE_INSTALL_PREFIX используется для определения корня, куда будут установлены файлы. Если используется команда cmake --install, префикс установки можно переопределить с помощью аргумента --prefix. Например:
cmake --install . --prefix "/home/myuser/installdir"
Перейдите в каталог установки и проверьте, что установленный учебник запускается.
Поддержка тестирования
Далее протестируем наше приложение. В конце файла верхнего уровня CMakeLists.txt мы можем включить тестирование, а затем добавить ряд базовых тестов, чтобы проверить, что приложение работает правильно.
enable_testing()
# does the application run
add_test(NAME Runs COMMAND Tutorial 25)
# does the usage message work?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
# define a function to simplify adding tests
function(do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction(do_test)
# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is [-nan|nan|0]")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
Первый тест просто проверяет, что приложение запускается, не вылетает и имеет значение возврата ноль. Это базовая форма теста CTest.
Следующий тест использует свойство теста PASS_REGULAR_EXPRESSION, чтобы проверить, что вывод теста содержит определённые строки. В данном случае, проверка, что сообщение об использовании печатается при вводе некорректного числа аргументов.
Наконец, у нас есть функция do_test, которая запускает приложение и проверяет, что вычисленный квадратный корень верен для данного ввода. Для каждого вызова do_test, в проект добавляется другой тест с именем, вводом и ожидаемыми результатами, основанными на переданных аргументах.
Перестройте приложение, затем перейдите в каталог двоичных файлов и запустите исполняемый файл ctest: ctest -N и ctest -VV. Для многоконфигурационных генераторов (например, Visual Studio) тип конфигурации должен быть указан. Чтобы запустить тесты в режиме отладки, например, используйте ctest -C Debug -VV из каталога сборки (не из подкаталога Debug!). В качестве альтернативы, скомпилируйте целевой объект RUN_TESTS из IDE.
Добавление системной интроспекции (Шаг 5)
Рассмотрим добавление в наш проект некоторого кода, который зависит от функций, которые могут отсутствовать на целевой платформе. В этом примере мы добавим код, который зависит от того, есть ли на целевой платформе функции log и exp. Конечно, почти на каждой платформе эти функции есть, но в этом учебнике предположим, что они не являются общими.
Если на платформе есть log и exp, то мы будем использовать их для вычисления квадратного корня в функции mysqrt. Сначала проверим доступность этих функций, используя модуль CheckSymbolExists в верхнем уровне CMakeLists.txt. На некоторых платформах нам потребуется связаться с библиотекой m. Если log и exp не найдутся, потребуйте библиотеку m и повторите попытку.
Мы будем использовать новые определения в TutorialConfig.h.in, поэтому убедитесь, что вы их установили до конфигурации этого файла.
include(CheckSymbolExists)
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(NOT (HAVE_LOG AND HAVE_EXP))
unset(HAVE_LOG CACHE)
unset(HAVE_EXP CACHE)
set(CMAKE_REQUIRED_LIBRARIES "m")
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(HAVE_LOG AND HAVE_EXP)
target_link_libraries(MathFunctions PRIVATE m)
endif()
endif()
Теперь добавим эти определения в TutorialConfig.h.in, чтобы мы могли использовать их из mysqrt.cxx:
// does the platform provide exp and log functions? #cmakedefine HAVE_LOG #cmakedefine HAVE_EXP
Если log и exp доступны в системе, мы будем использовать их для вычисления квадратного корня в функции mysqrt . Добавьте следующий код в функцию mysqrt в MathFunctions/mysqrt.cxx (не забудьте #endif перед возвращением результата!):
#if defined(HAVE_LOG) && defined(HAVE_EXP)
double result = exp(log(x) * 0.5);
std::cout << "Computing sqrt of " << x << " to be " << result
<< " using log and exp" << std::endl;
#else
double result = x;
Мы также должны изменить mysqrt.cxx для включения cmath.
#include <cmath>
Выполните исполняемый файл cmake или cmake-gui для конфигурации проекта, а затем скомпилируйте его с помощью выбранного инструмента сборки и запустите исполняемый файл учебника.
Вы заметите, что мы не используем log и exp, даже если считаем, что они должны быть доступны. Мы быстро осознаем, что забыли включить TutorialConfig.h в mysqrt.cxx.
Мы также должны обновить MathFunctions/CMakeLists.txt так, чтобы mysqrt.cxx знал, где находится этот файл:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_BINARY_DIR}
)
После внесения этого обновления, перестройте проект и запустите созданный исполняемый файл учебника. Если log и exp по-прежнему не используются, откройте созданный файл TutorialConfig.h из каталога сборки. Может быть, они недоступны на текущей системе?
Какая функция теперь даёт лучшие результаты, sqrt или mysqrt?
Указать определение компиляции
Есть ли лучшее место для сохранения значений HAVE_LOG и HAVE_EXP кроме TutorialConfig.h? Давайте попробуем использовать target_compile_definitions().
Сначала удалите определения из TutorialConfig.h.in . Больше не нужно включать TutorialConfig.h из mysqrt.cxx или дополнительное включение в MathFunctions/CMakeLists.txt.
Далее можно перенести проверку на HAVE_LOG и HAVE_EXP в MathFunctions/CMakeLists.txt и указать эти значения как определения компиляции PRIVATE.
include(CheckSymbolExists)
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(NOT (HAVE_LOG AND HAVE_EXP))
unset(HAVE_LOG CACHE)
unset(HAVE_EXP CACHE)
set(CMAKE_REQUIRED_LIBRARIES "m")
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(HAVE_LOG AND HAVE_EXP)
target_link_libraries(MathFunctions PRIVATE m)
endif()
endif()
# add compile definitions
if(HAVE_LOG AND HAVE_EXP)
target_compile_definitions(MathFunctions
PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()
После внесения этих изменений перестройте проект. Запустите созданный исполняемый файл учебника и проверьте, что результаты такие же, как и ранее в этом шаге.
Добавление пользовательской команды и генерируемого файла (Шаг 6)
Предположим, что для целей этого учебника мы решили, что никогда не будем использовать платформу log и функции exp и вместо этого хотели бы сгенерировать таблицу предопределенных значений для использования в функции mysqrt. В этом разделе мы создадим таблицу в процессе сборки, а затем скомпилируем эту таблицу в наше приложение.
Сначала удалим проверку функций log и exp в MathFunctions/CMakeLists.txt. Затем удалим проверку HAVE_LOG и HAVE_EXP из mysqrt.cxx. Одновременно мы можем удалить #include <cmath>.
В подкаталоге MathFunctions предоставлен новый исходный файл с именем MakeTable.cxx для генерации таблицы.
После проверки файла мы можем увидеть, что таблица создается как допустимый код C++ и что имя выходного файла передается в качестве аргумента.
Следующим шагом является добавление соответствующих команд в файл MathFunctions/CMakeLists.txt для построения исполняемого файла MakeTable и его запуска в рамках процесса сборки. Для этого потребуются несколько команд.
Во-первых, в верхней части файла MathFunctions/CMakeLists.txt добавляется исполняемый файл для MakeTable, как и любой другой исполняемый файл.
add_executable(MakeTable MakeTable.cxx)
Затем мы добавим пользовательскую команду, которая указывает, как создать Table.h путем запуска MakeTable.
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
Далее нам нужно сообщить CMake, что mysqrt.cxx зависит от сгенерированного файла Table.h. Это делается путем добавления сгенерированного Table.h в список исходных файлов для библиотеки MathFunctions.
add_library(MathFunctions
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
Мы также должны добавить текущий бинарный каталог в список каталогов включения, чтобы Table.h можно было найти и включить в mysqrt.cxx.
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
)
Теперь давайте воспользуемся сгенерированной таблицей. Сначала измените mysqrt.cxx для включения Table.h. Затем мы можем переписать функцию mysqrt, чтобы использовать таблицу:
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
// use the table to help find an initial value
double result = x;
if (x >= 1 && x < 10) {
std::cout << "Use the table to help find an initial value " << std::endl;
result = sqrtTable[static_cast<int>(x)];
}
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
Запустите исполняемый файл cmake или cmake-gui для настройки проекта, а затем выполните сборку с помощью выбранного инструмента сборки.
При сборке этого проекта сначала будет собран исполняемый файл MakeTable. Затем будет запущен MakeTable для создания Table.h. Наконец, он скомпилирует mysqrt.cxx, включающий Table.h, для создания библиотеки MathFunctions.
Запустите исполняемый файл учебника и проверьте, использует ли он таблицу.
Создание установщика (Шаг 7)
Далее предположим, что мы хотим распространить наш проект другим людям, чтобы они могли его использовать. Мы хотим предоставить дистрибутивы как бинарные, так и исходные на различных платформах. Это немного отличается от предыдущей установки в Установка и тестирование (Шаг 4), где мы устанавливали бинарные файлы, которые были построены из исходного кода. В этом примере мы будем создавать пакеты установки, которые поддерживают установку бинарных файлов и функции управления пакетами. Для этого мы будем использовать CPack для создания платформоспецифичных установщиков. В частности, нам нужно добавить несколько строк в конец файла верхнего уровня CMakeLists.txt.
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
include(CPack)
Это всё. Мы начинаем с включения InstallRequiredSystemLibraries. Этот модуль включит все необходимые библиотеки выполнения для проекта на текущей платформе. Далее мы устанавливаем некоторые переменные CPack, в которых хранятся информация о лицензии и версии для этого проекта. Информация о версии была установлена ранее в этом учебнике, а license.txt включен в каталог исходных файлов верхнего уровня для этого шага.
Наконец, мы включаем CPack module, который будет использовать эти переменные и некоторые другие свойства текущей системы для настройки установщика.
Следующим шагом является сборка проекта в обычном режиме, а затем запуск исполняемого файла cpack. Для создания дистрибутива бинарных файлов из каталога бинарных файлов выполните:
cpack
Для указания генератора используйте опцию -G. Для многоконфигурационных сборок используйте -C для указания конфигурации. Например:
cpack -G ZIP -C Debug
Чтобы создать дистрибутив исходных кодов, введите:
cpack --config CPackSourceConfig.cmake
В качестве альтернативы, запустите make package или щелкните правой кнопкой мыши цель Package и выберите Build Project из среды разработки.
Запустите установщик, расположенный в каталоге бинарных файлов. Затем запустите установленный исполняемый файл и проверьте, работает ли он.
Добавление поддержки панели мониторинга (Шаг 8)
Добавление поддержки отправки результатов наших тестов на панель мониторинга очень просто. Мы уже определили ряд тестов для нашего проекта в Поддержка тестирования. Теперь нам просто нужно запустить эти тесты и отправить их на панель мониторинга. Для включения поддержки панелей мониторинга мы включаем модуль CTest в наш файл верхнего уровня CMakeLists.txt.
Замените:
# enable testing enable_testing()
На:
# enable dashboard scripting include(CTest)
Модуль CTest автоматически вызовет enable_testing(), поэтому мы можем удалить его из наших файлов CMake.
Нам также потребуется создать файл CTestConfig.cmake в каталоге верхнего уровня, где мы можем указать имя проекта и место отправки данных на панель мониторинга.
set(CTEST_PROJECT_NAME "CMakeTutorial") set(CTEST_NIGHTLY_START_TIME "00:00:00 EST") set(CTEST_DROP_METHOD "http") set(CTEST_DROP_SITE "my.cdash.org") set(CTEST_DROP_LOCATION "/submit.php?project=CMakeTutorial") set(CTEST_DROP_SITE_CDASH TRUE)
Исполняемый файл ctest будет считывать этот файл при запуске. Для создания простой панели мониторинга можно запустить исполняемый файл cmake или cmake-gui для настройки проекта, но пока не выполняйте сборку. Вместо этого измените каталог на бинарное дерево, а затем выполните:
ctest [-VV] -D Experimental
Помните, для многоконфигурационных генераторов (например, Visual Studio) тип конфигурации должен быть указан:
ctest [-VV] -C Debug -D Experimental
Или, из среды разработки, выполните сборку цели Experimental.
Исполняемый файл ctest выполнит сборку и тестирование проекта и отправит результаты на публичную панель мониторинга Kitware: https://my.cdash.org/index.php?project=CMakeTutorial.
Смешение статических и динамических библиотек (Шаг 9)
В этом разделе мы покажем, как переменная BUILD_SHARED_LIBS может использоваться для управления поведением по умолчанию add_library(), и позволит управлять тем, как строятся библиотеки без явного типа (STATIC, SHARED, MODULE или OBJECT).
Для этого нам нужно добавить BUILD_SHARED_LIBS в файл верхнего уровня CMakeLists.txt. Мы используем команду option(), так как она позволяет пользователям необязательно выбрать, должно ли значение быть включено или выключено.
Далее мы переработаем MathFunctions, чтобы сделать ее реальной библиотекой, которая будет обобщать использование mysqrt или sqrt, вместо того, чтобы требовать от вызывающего кода выполнения этой логики. Это также означает, что USE_MYMATH больше не будет управлять сборкой MathFunctions, а вместо этого будет управлять поведением этой библиотеки.
Первый шаг — обновить начальную часть файла верхнего уровня CMakeLists.txt следующим образом:
cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# control where the static and shared libraries are built so that on windows
# we don't need to tinker with the path to run the executable
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
# configure a header file to pass the version number only
configure_file(TutorialConfig.h.in TutorialConfig.h)
# add the MathFunctions library
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)
Теперь, когда мы заставили MathFunctions всегда использоваться, нам нужно обновить логику этой библиотеки. Таким образом, в MathFunctions/CMakeLists.txt нам нужно создать SqrtLibrary, которая будет условно собираться и устанавливаться, когда USE_MYMATH включено. Теперь, так как это учебник, мы будем явно требовать, чтобы SqrtLibrary собиралась статически.
Конечный результат должен быть таким:
# add the library that runs
add_library(MathFunctions MathFunctions.cxx)
# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
# should we use our own math functions
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
# library that just does sqrt
add_library(SqrtLibrary STATIC
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
# state that we depend on our binary dir to find Table.h
target_include_directories(SqrtLibrary PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
# define the symbol stating we are using the declspec(dllexport) when
# building on windows
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
# install rules
set(installable_libs MathFunctions)
if(TARGET SqrtLibrary)
list(APPEND installable_libs SqrtLibrary)
endif()
install(TARGETS ${installable_libs} DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
Далее обновите MathFunctions/mysqrt.cxx для использования пространств имен mathfunctions и detail.
#include <iostream>
#include "MathFunctions.h"
// include the generated table
#include "Table.h"
namespace mathfunctions {
namespace detail {
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
// use the table to help find an initial value
double result = x;
if (x >= 1 && x < 10) {
std::cout << "Use the table to help find an initial value " << std::endl;
result = sqrtTable[static_cast<int>(x)];
}
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
}
}
Мы также должны внести некоторые изменения в tutorial.cxx, чтобы она больше не использовала USE_MYMATH:
- Всегда включайте
MathFunctions.h - Всегда используйте
mathfunctions::sqrt - Не включайте cmath
Наконец, обновите MathFunctions/MathFunctions.h для использования определений экспорта DLL:
#if defined(_WIN32)
# if defined(EXPORTING_MYMATH)
# define DECLSPEC __declspec(dllexport)
# else
# define DECLSPEC __declspec(dllimport)
# endif
#else // non windows
# define DECLSPEC
#endif
namespace mathfunctions {
double DECLSPEC sqrt(double x);
}
В этот момент, если вы соберете всё, вы можете заметить, что линковка завершается ошибкой, так как мы объединяем статическую библиотеку без кода независимого от позиции с библиотекой, содержащей код, независимый от позиции. Решением является явное задание свойства целевого объекта POSITION_INDEPENDENT_CODE SqrtLibrary в значение True независимо от типа сборки.
# state that SqrtLibrary need PIC when the default is shared libraries
set_target_properties(SqrtLibrary PROPERTIES
POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
Упражнение: Мы изменили MathFunctions.h на использование определений экспорта dll. Используя документацию CMake, можете ли вы найти вспомогательный модуль для упрощения этой задачи?
Добавление выражений генератора (Шаг 10)
Generator expressions вычисляются во время генерации системы сборки, чтобы получить информацию, специфичную для каждой конфигурации сборки.
Generator expressions разрешены в контексте многих свойств целей, таких как LINK_LIBRARIES, INCLUDE_DIRECTORIES, COMPILE_DEFINITIONS и другие. Они также могут использоваться при использовании команд для заполнения этих свойств, таких как target_link_libraries(), target_include_directories(), target_compile_definitions() и другие.
Generator expressions можно использовать для включения условной компоновки, условных определений, используемых при компиляции, условных включений каталогов и многого другого. Условия могут основываться на конфигурации сборки, свойствах целей, информации о платформе или любой другой запрошиваемой информации.
Существуют различные типы generator expressions, включая логические, информационные и выходные выражения.
Логические выражения используются для создания условного вывода. Основные выражения — это выражения 0 и 1. $<0:...> приводит к пустой строке, а <1:...> — к содержимому «…». Они также могут быть вложены.
Распространённое использование generator expressions — условно добавлять флаги компилятора, такие как для уровней языка или предупреждений. Хорошая практика — связывать эту информацию с INTERFACE целью, позволяя этой информации распространяться. Начнём с создания INTERFACE цели и указания требуемого уровня стандарта C++ 11 вместо использования CMAKE_CXX_STANDARD.
Следующий код:
# specify the C++ standard set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED True)
Будет заменён на:
add_library(tutorial_compiler_flags INTERFACE) target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
Далее добавим желаемые флаги предупреждений компилятора для нашего проекта. Поскольку флаги предупреждений различаются в зависимости от компилятора, мы используем выражение генератора COMPILE_LANG_AND_ID для управления применяемыми флагами в зависимости от языка и набора идентификаторов компилятора, как показано ниже:
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)
Глядя на это, мы видим, что флаги предупреждений заключены в условие BUILD_INTERFACE. Это делается для того, чтобы потребители нашей установленной библиотеки не наследовали наши флаги предупреждений.
Упражнение: Измените MathFunctions/CMakeLists.txt таким образом, чтобы у всех целей вызывался метод target_link_libraries() для tutorial_compiler_flags.
Добавление конфигурации экспорта (Шаг 11)
Во время Установки и тестирования (Шаг 4) в туториале мы добавили возможность для CMake устанавливать библиотеку и заголовочные файлы проекта. Во время Сборки установщика (Шаг 7) мы добавили возможность упаковывать эту информацию для распространения другим пользователям.
Следующим шагом является добавление необходимой информации, чтобы другие проекты CMake могли использовать наш проект, будь то из каталога сборки, локальной установки или при упаковке.
Первый шаг — обновить команды install(TARGETS) для указания не только DESTINATION, но также и EXPORT. Ключевое слово EXPORT генерирует и устанавливает файл CMake, содержащий код для импорта всех целей, указанных в команде установки, из дерева установки. Итак, давайте явно EXPORT библиотеку MathFunctions, обновив команду install в MathFunctions/CMakeLists.txt следующим образом:
set(installable_libs MathFunctions tutorial_compiler_flags)
if(TARGET SqrtLibrary)
list(APPEND installable_libs SqrtLibrary)
endif()
install(TARGETS ${installable_libs}
DESTINATION lib
EXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)
Теперь, когда MathFunctions экспортируется, нам также необходимо явно установить сгенерированный файл MathFunctionsTargets.cmake. Это делается путем добавления следующего в конец файла верхнего уровня CMakeLists.txt:
install(EXPORT MathFunctionsTargets FILE MathFunctionsTargets.cmake DESTINATION lib/cmake/MathFunctions )
На этом этапе вы должны попробовать запустить CMake. Если всё настроено правильно, вы увидите ошибку CMake, похожую на:
Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains path: "/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions" which is prefixed in the source directory.
CMake пытается сказать, что при генерации информации об экспорте он будет экспортировать путь, который неразрывно связан с текущей машиной и не будет действительным на других машинах. Решением является обновление вызова target_include_directories() MathFunctions, чтобы понять, что ему нужны разные INTERFACE расположения при использовании из каталога сборки и из каталога установки/пакета. Это означает преобразование вызова target_include_directories() для MathFunctions следующим образом:
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
После этого обновления можно повторно запустить CMake и проверить, что больше нет предупреждений.
На этом этапе CMake должным образом упаковывает требуемую информацию о цели, но нам всё ещё нужно сгенерировать MathFunctionsConfig.cmake для того, чтобы команда CMake find_package() могла найти наш проект. Давайте создадим новый файл в корне проекта, назовём его Config.cmake.in, и добавим в него следующее содержимое:
@PACKAGE_INIT@
include ( "${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake" )
Затем, для правильной конфигурации и установки этого файла, добавьте следующее в конец файла верхнего уровня CMakeLists.txt:
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions
)
include(CMakePackageConfigHelpers)
# generate the config file that is includes the exports
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
INSTALL_DESTINATION "lib/cmake/example"
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# generate the version file for the config file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
COMPATIBILITY AnyNewerVersion
)
# install the configuration file
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake
DESTINATION lib/cmake/MathFunctions
)
На этом этапе мы сгенерировали переносимую конфигурацию CMake для нашего проекта, которую можно использовать после установки или упаковки проекта. Если мы хотим, чтобы наш проект также использовался из каталога сборки, нам нужно только добавить следующее в конец файла верхнего уровня CMakeLists.txt:
export(EXPORT MathFunctionsTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake"
)
С этим вызовом экспорта мы теперь генерируем Targets.cmake, что позволяет использовать конфигурированный MathFunctionsConfig.cmake в каталоге сборки другими проектами без необходимости установки.
Упаковка Debug и Release (Шаг 12)
Примечание: Этот пример подходит для генераторов с одной конфигурацией и не будет работать для генераторов с несколькими конфигурациями (например, Visual Studio).
По умолчанию модель CMake такова, что каталог сборки содержит только одну конфигурацию, будь то Debug, Release, MinSizeRel или RelWithDebInfo. Однако можно настроить CPack на связывание нескольких каталогов сборки и создание пакета, содержащего несколько конфигураций одного проекта.
Сначала убедимся, что сборки Debug и Release используют разные имена для исполняемых файлов и библиотек, которые будут установлены. Будем использовать d в качестве постфикса для исполняемых файлов и библиотек Debug.
Установите CMAKE_DEBUG_POSTFIX в начале файла верхнего уровня CMakeLists.txt:
set(CMAKE_DEBUG_POSTFIX d) add_library(tutorial_compiler_flags INTERFACE)
И свойство DEBUG_POSTFIX для исполняемого файла туториала:
add_executable(Tutorial tutorial.cxx)
set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
target_link_libraries(Tutorial PUBLIC MathFunctions)
Давайте также добавим номеров версии к библиотеке MathFunctions. В MathFunctions/CMakeLists.txt, установите свойства VERSION и SOVERSION:
set_property(TARGET MathFunctions PROPERTY VERSION "1.0.0") set_property(TARGET MathFunctions PROPERTY SOVERSION "1")
Из каталога Step12 создайте подкаталоги debug и release.
- Step12 - debug - release
Теперь нам нужно настроить сборки Debug и Release. Мы можем использовать переменную CMAKE_BUILD_TYPE для установки типа конфигурации:
cd debug cmake -DCMAKE_BUILD_TYPE=Debug .. cmake --build . cd ../release cmake -DCMAKE_BUILD_TYPE=Release .. cmake --build .
Теперь, когда сборки Debug и Release завершены, мы можем использовать пользовательский файл конфигурации для упаковки обеих сборок в один релиз. В каталоге Step12 создайте файл MultiCPackConfig.cmake. В этом файле сначала включите файл конфигурации по умолчанию, который был создан исполняемым файлом cmake.
Затем используйте переменную CPACK_INSTALL_CMAKE_PROJECTS для указания проектов, которые нужно установить. В этом случае мы хотим установить как Debug, так и Release.
include("release/CPackConfig.cmake")
set(CPACK_INSTALL_CMAKE_PROJECTS
"debug;Tutorial;ALL;/"
"release;Tutorial;ALL;/"
)
Из каталога Step12, запустите cpack, указав наш пользовательский файл конфигурации с опцией config:
cpack --config MultiCPackConfig.cmake
© 2000–2020 Kitware, Inc. and Contributors
Licensed under the BSD 3-clause License.
https://cmake.org/cmake/help/v3.19/guide/tutorial/index.html