Учебник по 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_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 cd Step1_build cmake ../Step1 cmake --build .
Перейдите в каталог, где был собран Tutorial (вероятно, каталог make или подкаталог Debug или Release конфигурации сборки) и выполните эти команды:
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.
Используйте исполняемый файл ccmake или cmake-gui для обновления значения USE_MYMATH. Перестройте и запустите учебник снова. Какая функция дает лучшие результаты, 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) из командной строки или скомпилируйте целевой объект INSTALL из среды разработки. Это установит необходимые заголовочные файлы, библиотеки и исполняемые файлы.
Переменная CMake CMAKE_INSTALL_PREFIX используется для определения корня, куда будут установлены файлы. При использовании cmake --install пользовательский каталог установки можно указать с помощью аргумента --prefix. Для инструментов с несколькими конфигурациями используйте аргумент --config для указания конфигурации.
Проверьте, что установленный учебник работает.
Поддержка тестирования
Теперь давайте протестируем наше приложение. В конце файла главного уровня 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")
Первый тест просто проверяет, что приложение запускается, не завершается аварийно (не segfault) и имеет значение возврата ноль. Это базовая форма теста CTest.
Следующий тест использует свойство теста PASS_REGULAR_EXPRESSION, чтобы проверить, что вывод теста содержит определённые строки. В данном случае проверяется, что сообщение об использовании печатается при вводе неверного количества аргументов.
Наконец, у нас есть функция под названием do_test, которая запускает приложение и проверяет, что вычисленный квадратный корень верен для заданного входного значения. При каждом вызове do_test в проект добавляется ещё один тест с именем, входными данными и ожидаемыми результатами, основанными на переданных аргументах.
Перестройте приложение, затем перейдите в каталог бинарных файлов и запустите исполняемый файл ctest: ctest -N и ctest -VV. Для генераторов с несколькими конфигурациями (например, Visual Studio) необходимо указать тип конфигурации. Чтобы запустить тесты в режиме отладки, например, используйте ctest -C Debug -VV из каталога сборки (а не подкаталога Debug!). В качестве альтернативы скомпилируйте целевой объект RUN_TESTS из среды разработки.
Добавление системного анализа (Шаг 5)
Рассмотрим добавление в проект кода, зависящего от функций, которых может не быть на целевой платформе. В этом примере мы добавим код, зависящий от наличия функций log и exp на целевой платформе. Конечно, почти каждая платформа имеет эти функции, но для этого учебника предположим, что они не являются стандартными.
Если на платформе есть функции log и exp, то мы будем использовать их для вычисления квадратного корня в функции mysqrt. Сначала мы проверим доступность этих функций, используя модуль CheckSymbolExists в главном CMakeLists.txt. Мы собираемся использовать новые определения в TutorialConfig.h.in, поэтому убедитесь, что вы задали их до конфигурации этого файла.
include(CheckSymbolExists) set(CMAKE_REQUIRED_LIBRARIES "m") check_symbol_exists(log "math.h" HAVE_LOG) check_symbol_exists(exp "math.h" HAVE_EXP)
Теперь добавим эти определения в 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, чтобы сконфигурировать проект, а затем скомпилировать его с помощью выбранного инструмента сборки и запустить исполняемый файл Tutorial.
Вы заметите, что мы не используем log и exp, даже если считаем, что они должны быть доступны. Мы быстро поймём, что забыли включить TutorialConfig.h в mysqrt.cxx.
Нам также нужно обновить MathFunctions/CMakeLists.txt так, чтобы mysqrt.cxx знал, где находится этот файл:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_BINARY_DIR}
)
После внесения этого изменения перестройте проект и запустите созданный исполняемый файл Tutorial. Если 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)
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_compile_definitions(MathFunctions
PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()
После внесения этих изменений перестройте проект. Запустите созданный исполняемый файл Tutorial и убедитесь, что результаты совпадают с предыдущим шагом.
Добавление пользовательской команды и сгенерированного файла (Шаг 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.
Запустите исполняемый файл Tutorial и убедитесь, что он использует таблицу.
Сборка установщика (Шаг 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 из IDE.
Запустите установщик, найденный в бинарной директории. Затем запустите установленный исполняемый файл и убедитесь, что он работает.
Добавление поддержки панели мониторинга (Шаг 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
Или, из IDE, соберете целевой объект 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 собиралась статически.
В итоге, MathFunctions/CMakeLists.txt должно выглядеть следующим образом:
# 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
install(TARGETS MathFunctions 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, чтобы оно было истинным независимо от типа сборки.
# 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 следующим образом:
install(TARGETS MathFunctions tutorial_compiler_flags
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.17/guide/tutorial/index.html