Учебник по CMake
- Добавление библиотеки (Шаг 2)
- Добавление требований к использованию библиотеки (Шаг 3)
- Добавление пользовательской команды и сгенерированного файла (Шаг 6)
- Создание установщика (Шаг 7)
- Добавление поддержки панели мониторинга (Шаг 8)
- Смешивание статических и динамических библиотек (Шаг 9)
- Добавление выражений генератора (Шаг 10)
- Добавление экспортной конфигурации (Шаг 11)
- Импорт проекта CMake (потребитель)
- Упаковка Debug и Release (MultiPackage)
Учебник 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, чтобы установить номер версии.
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_definitionstarget_compile_optionstarget_include_directoriestarget_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 для конфигурации проекта, а затем соберете его с помощью выбранного инструмента сборки. Запустите шаг установки, набрав cmake --install . (введено в версии 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")
Первый тест просто проверяет, что приложение запускается, не завершается аварийно и возвращает нулевое значение. Это базовый вид теста CTest.
Следующий тест использует свойство теста PASS_REGULAR_EXPRESSION, чтобы проверить, содержит ли вывод теста определённые строки. В данном случае, проверка, что сообщение об использовании выводится, когда предоставлено неверное количество аргументов.
Наконец, у нас есть функция do_test, которая запускает приложение и проверяет, что вычисленное значение квадратного корня является корректным для заданного входного значения. Для каждого вызова do_test в проект добавляется другой тест с именем, входным значением и ожидаемым результатом на основе переданных аргументов.
Перестройте приложение, затем перейдите в каталог с двоичными файлами и запустите 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
Измените mysqrt.cxx для включения cmath. Далее, в том же файле, в функции mysqrt мы можем предоставить альтернативную реализацию, основанную на log и exp, если они доступны в системе, используя следующий код (не забудьте добавить #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;
Запустите 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, который будет использовать эти переменные и другие свойства текущей системы для настройки установщика.
Следующим шагом является сборка проекта обычным способом, а затем запуск 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. Результаты вашей панели мониторинга будут загружены на общедоступную панель мониторинга 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, поскольку она позволяет пользователям необязательно выбрать значение On или Off.
Далее мы собираемся переработать MathFunctions, чтобы превратить его в настоящую библиотеку, которая будет инкапсулировать использование mysqrt или sqrt, вместо того, чтобы требовать от вызывающего кода выполнения этой логики. Это также означает, что USE_MYMATH не будет управлять сборкой MathFuctions, а вместо этого будет управлять поведением этой библиотеки.
Первый шаг — обновить начальный раздел верхнего уровня 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
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 в значение 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)
Выражения генератора вычисляются во время генерации системы сборки, чтобы получить информацию, специфичную для каждой конфигурации сборки.
Выражения генератора разрешены в контексте многих свойств целей, таких как LINK_LIBRARIES, INCLUDE_DIRECTORIES, COMPILE_DEFINITIONS и других. Они также могут быть использованы при использовании команд для заполнения этих свойств, таких как target_link_libraries(), target_include_directories(), target_compile_definitions() и других.
Выражения генератора могут использоваться для условной линковки, условных определений, используемых при компиляции, условных включений директорий и многого другого. Условия могут основываться на конфигурации сборки, свойствах целей, информации о платформе или любой другой запрошиваемой информации.
Существуют разные типы выражений генератора, включая логические, информационные и выходные выражения.
Логические выражения используются для создания условного вывода. Основными выражениями являются 0 и 1. Выражение $<0:...> приводит к пустой строке, а <1:...> — к содержимому «…». Они также могут быть вложены.
Распространённое использование выражений генератора — условное добавление флагов компилятора, таких как флаги уровня языка или предупреждений. Хороший подход — связать эту информацию с целевым объектом 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 в каталоге сборки использоваться другими проектами без необходимости его установки.
Импорт проекта CMake (потребитель)
Этот пример демонстрирует, как проект может найти другие пакеты CMake, которые генерируют файлы Config.cmake.
Он также демонстрирует, как указать внешние зависимости проекта при генерации файла Config.cmake.
Упаковки Debug и Release (MultiPackage)
По умолчанию модель CMake предполагает, что каталог сборки содержит только одну конфигурацию, будь то Debug, Release, MinSizeRel или RelWithDebInfo.
Но возможно настроить CPack для одновременного объединения нескольких каталогов сборки, чтобы создать пакет, содержащий несколько конфигураций одного и того же проекта.
Сначала нам нужно создать каталог под названием multi_config, который будет содержать все сборки, которые мы хотим упаковать вместе.
Затем создайте каталоги debug и release в каталоге multi_config. В итоге должна получиться структура:
─ multi_config
├── debug
└── release
Теперь нам нужно настроить сборки Debug и Release, что примерно подразумевает следующее:
cd debug cmake -DCMAKE_BUILD_TYPE=Debug ../../MultiPackage/ cmake --build . cd ../release cmake -DCMAKE_BUILD_TYPE=Release ../../MultiPackage/ cmake --build . cd ..
Теперь, когда сборки Debug и Release завершены, мы можем использовать пользовательский файл MultiCPackConfig.cmake для упаковки обеих сборок в один релизный пакет.
cpack --config ../../MultiPackage/MultiCPackConfig.cmake
© 2000–2020 Kitware, Inc. and Contributors
Licensed under the BSD 3-clause License.
https://cmake.org/cmake/help/v3.16/guide/tutorial/index.html