Руководство по CMake
- Введение
- Добавление библиотеки (Шаг 2)
- Добавление требований к использованию библиотеки (Шаг 3)
- Добавление системной интроспекции (Шаг 5)
- Добавление пользовательской команды и сгенерированного файла (Шаг 6)
- Создание установщика (Шаг 7)
- Добавление поддержки панели мониторинга (Шаг 8)
- Смешивание статических и динамических библиотек (Шаг 9)
- Добавление выражений генератора (Шаг 10)
- Добавление конфигурации экспорта (Шаг 11)
- Упаковка версий Debug и Release (Шаг 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 в качестве каталога включения, чтобы можно было найти заголовочный файл mysqrt.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"
Перейдите в директорию установки и проверьте, что установленная программа Tutorial работает.
Поддержка тестирования
Далее добавим поддержку тестирования нашего приложения. В конце файла верхнего уровня 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 в MathFunctions/CMakeLists.txt. На некоторых платформах нам необходимо подключиться к библиотеке m. Если log и exp изначально не найдены, потребуйте подключение библиотеки m и попробуйте снова.
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()
Если они доступны, используйте target_compile_definitions(), чтобы указать HAVE_LOG и HAVE_EXP в качестве PRIVATE определений компиляции.
if(HAVE_LOG AND HAVE_EXP)
target_compile_definitions(MathFunctions
PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()
Если 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.
Какая функция теперь даёт лучшие результаты: sqrt или mysqrt?
Добавление пользовательской команды и сгенерированного файла (Шаг 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 из среды разработки.
Запустите установщик, расположенный в каталоге бинарных файлов. Затем запустите установленный исполняемый файл и проверьте его работоспособность.
Добавление поддержки панели мониторинга (Шаг 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
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 в каталоге сборки использоваться другими проектами без необходимости её установки.
Упаковщивание отладки и релизной версии (шаг 12)
Примечание: Этот пример действителен для генераторов с одной конфигурацией и не будет работать для генераторов с несколькими конфигурациями (например, Visual Studio).
По умолчанию модель CMake заключается в том, что каталог сборки содержит только одну конфигурацию, будь то Debug, Release, MinSizeRel или RelWithDebInfo. Однако можно настроить CPack на объединение нескольких каталогов сборки и создание пакета, содержащего несколько конфигураций одного и того же проекта.
Сначала убедимся, что сборки отладки и релизной версии используют разные имена для исполняемых файлов и библиотек, которые будут установлены. Давайте используем d в качестве постфикса для отладочных исполняемых файлов и библиотек.
Установите 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
Теперь нам нужно настроить сборки отладки и релиза. Мы можем использовать CMAKE_BUILD_TYPE для установки типа конфигурации:
cd debug cmake -DCMAKE_BUILD_TYPE=Debug .. cmake --build . cd ../release cmake -DCMAKE_BUILD_TYPE=Release .. cmake --build .
Теперь, когда сборки отладки и релиза завершены, мы можем использовать пользовательский конфигурационный файл для упаковки обеих сборок в один релизный пакет. В каталоге Step12 создайте файл под названием MultiCPackConfig.cmake. В этом файле сначала включите файл конфигурации по умолчанию, который был создан исполняемым файлом cmake.
Далее, используйте переменную CPACK_INSTALL_CMAKE_PROJECTS для указания проектов, которые необходимо установить. В данном случае мы хотим установить и отладку, и релиз.
include("release/CPackConfig.cmake")
set(CPACK_INSTALL_CMAKE_PROJECTS
"debug;Tutorial;ALL;/"
"release;Tutorial;ALL;/"
)
Из каталога Step12 запустите cpack, указав наш пользовательский конфигурационный файл с опцией config:
cpack --config MultiCPackConfig.cmake
© 2000–2021 Kitware, Inc. and Contributors
Licensed under the BSD 3-clause License.
https://cmake.org/cmake/help/v3.20/guide/tutorial/index.html