Как написать cmake файл


Содержание

См. также статью Современный CMake: 10 советов по улучшению скриптов сборки.

Терминология

  • Файл CMakeLists.txt служит скриптом (рецептом, сценарием) сборки проекта. Обычно один такой файл собирает все исходники в своём каталоге и в подкаталогах, при этом подкаталоги могут содержать, а могут не содержать дочерние файлы CMakeLists.txt. С точки зрения IDE, таких как CLion или Visual Studio, файл CMakeLists.txt также служит проектом, с которым работает программист внутри IDE.
  • В cmake есть “цель” (“target”) — компонент, который следует собрать. Компонент может быть исполняемым файлом, так и статической либо динамической библиотекой.
  • В cmake есть “проект” (“project”) — это набор компонентов, по смыслу похожий на Solution в Visual Studio.
  • В cmake есть “флаги” (flags) — это аргументы командной строки для компилятора, компоновщика и других утилит, вызываемых при сборке.
  • В cmake есть переменные, и в процессе интерпретации файла CMakeLists.txt система сборки cmake вычисляет ряд встроенных переменных для каждой цели, тем самым получая флаги. Затем cmake создаёт вторичный скрипт сборки, который будет напрямую вызывать компилятор, компоновщик и другие утилиты с вычисленными флагами.

Сборка проекта из командной строки (Linux)

На первом шаге проект нужно сконфигурировать, то есть создать финальный скрипт сборки, запустив cmake <параметры> <путь-к-каталогу> в будущем каталоге сборки.

# Сейчас мы в каталоге `myapp` с файлом CMakeLists.txt
# Создадим каталог `myapp-release` и перейдём в него.
mkdir --parents ../myapp-release
cd ../myapp-release

# Сконфигурируем проект для сборки в Release.
# Флаг установит опцию CMAKE_BUILD_TYPE в значение "Release",
#  интерпретатор cmake считает это переключением на Release конфигурацию.
cmake -DCMAKE_BUILD_TYPE=Release ../myapp

На втором шаге нужно запустить финальный скрипт. Не вызывайте make! Утилита cmake сделает это сама:

# Просим CMake запустить сборку в каталоге `myapp-release`
# Можно добавить флаг `--target mylibrary` для сборки только mylibrary
# Можно добавить флаг `--clean-first`, чтобы в начале новой сборки
#  стирать остатки предыдущей.
cmake --build .

# Аналогичный способ для GNU/Linux. Его по привычке советуют в сети, хотя
#  make доступен не всегда, а cmake --build работает на всех платформах.
make

Структура CMakeLists.txt

В начале главного файла CMakeLists.txt ставят метаинформацию о минимальной версии CMake и названии проекта:

# Указывайте последнюю доступную вам версию CMake.
cmake_minimum_required(VERSION 3.8)

# Синтаксис: project(<имя> VERSION <версия> LANGUAGES CXX),
#  теги VERSION и LANGUAGES необязательные.
project(libmodel3d)

Затем следует список инструкций, служащих для вычисления различных переменных, создания целей сборки,
подключения проектов из подкаталогов и так далее. Например, подключить дополнительный CMakeLists.txt
из подкаталога можно так:

# Простая версия: подключает скрипт по пути <подкаталог>/CMakeLists.txt
add_subdirectory(<подкаталог>)

# Расширенная версия: дополнительно установит подкаталог сборки подпроекта
add_subdirectory(<подкаталог> <подкаталог сборки>)

Целью может стать исполняемый файл, собираемый из исходного кода

# Синтаксис: add_executable(<имя> <список исходников...>)
# Добавлять `.h` необязательно, но лучше для работы из IDE:
#  - IDE определит заголовок как часть проекта
#  - cmake будет отслеживать изменения в заголовке и пересобирать
#    проект при изменениях.
add_executable(pngconverter main.cpp PngReader.h PngReader.cpp)

Целью также может быть библиотека, статическая или динамическая.

# Синтаксис: add_library(<имя> [STATIC|SHARED|INTERFACE] <список исходников...>)

# Тип библиотеки (staic или shared) зависит от параметров сборки
add_library(libpngutils PngUtils.h PngUtils.cpp)

# Тип библиотеки: static
add_library(libpngtools STATIC PngTools.h PngTools.cpp)

Автогенерация проекта для Visual Studio (Windows)

Если используется Visual C++, то путь немного другой: на шаге конфигурирования создаётся проект для Visual Studio, который затем можно собрать из IDE либо так же из командной строки.

Созданный проект Visual Studio нельзя изменять и использовать постоянно, потому что при генерации проекта используются абсолютные пути и другие неприемлемые для постоянной работы вещи.

# Сейчас мы в каталоге `myapp` с файлом CMakeLists.txt
# Сгенерируем проект Visual Studio для сборки.
mkdir --parents ../myapp-build
cd ../myapp-build

# Конфигурируем для сборки с Visual Studio 2017, версия тулчейна v140
cmake -G "Visual Studio 2017"

Если проект был сконфигурирован успешно, то в каталоге ../myapp-build появятся автоматически сгенерированный BUILD_ALL.sln и проекты для Visual Studio. Их можно открыть к IDE, либо собрать из командной строки с помощью cmake. Названия опций говорят сами за себя:

cmake --build . 
    --target myapp 
    --config Release 
    --clean-first

Зависимости между библиотеками и приложениями

Не используйте директивы include_directories, add_definitions, add_compile_options!
Они меняют глобальные настройки для всех целей, это создаёт проблемы при масштабировании.

  • Используйте target_link_libraries для добавления статических и динамических библиотек, от которых зависит цель
  • Используйте target_include_directories вместо include_directories для добавления путей поиска заголовков, от которых зависит цель
  • Используйте target_compile_definitions вместо add_definitions для добавления макросов, с которыми собирается цель
  • Используйте target_compile_options для добавления специфичных флагов компилятора, с которыми собирается цель

Пример:

# Добавляем цель - статическую библиотеку
add_library(mylibrary STATIC 
    ColorDialog.h ColorDialog.cpp 
    ColorPanel.h ColorPanel.cpp)

# ! Осторожно - непереносимый код !
# Добавляем к цели путь поиска заголовков /usr/include/wx-3.0
# Лучше использовать find_package для получения пути к заголовкам.
target_include_directories(mylibrary /usr/include/wx-3.0)

Вы можете выбирать область видимости настройки:

  • PUBLIC делает настройку видимой для текущей цели и для всех зависящих от неё целей
  • PRIVATE делает настройку видимой только для текущей цели
  • INTERFACE делает настройку видимой только для всех зависящих от неё целей

Пример использования областей видимости:

# Каталог include будет добавлен к путям поиска заголовков в текущей цели и во всех зависимых целях
target_include_directories(myTarget PUBLIC ./include)

# Каталог src будет добавлен к путям поиска заголовков только в текущей цели
target_include_directories(myTarget PUBLIC ./src)

Схема зависимостей условного проекта:

Схема

Выбор стандарта и диалекта C++

Для настройки стандарта и флагов языка C++ не добавляйте флаги напрямую!

# ! Устаревший метод - прямое указание флага !
target_compile_options(hello PRIVATE -std=c++11)

В CMake версии 3.8+ вы можете прямо потребовать включить нужный стандарт:

# Источник: https://cmake.org/cmake/help/latest/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html

# Включаем C++ 2017
target_compile_features(myapp cxx_std_17)

# Альтернатива: включаем C++ 2014
target_compile_features(myapp cxx_std_14)

# Альтернатива: включаем C++ 2011
target_compile_features(myapp cxx_std_11)

В CMake версии до 3.7 включительно можно использовать set_target_properties (если не работает, то у вас слишком старый CMake):

# Стандарт: C++ 2014, расширения языка от производителя компилятора отключены
set_target_properties(myapp PROPERTIES
    CXX_STANDARD 14
    CXX_STANDARD_REQUIRED YES
    CXX_EXTENSIONS NO
)

Для разработчиков библиотек есть более тонкий контроль над возможностями языка:

# API библиотеки (т.е. заголовки) требуют лямбда-функций и override,
#  реализация библиотеки требует ещё и range-based for.
target_compile_features(mylibrary PUBLIC cxx_override cxx_lambdas PRIVATE cxx_range_for)

Функции в CMake

CMake позволяет объявлять функции командами function(name) / endfunction() и макросы командами macro(name) / endmacro(). Предпочитайте функции, а не макросы, т.к. у функций есть своя область видимости переменных, а у макросов — нет.

function(hello_get_something var_name)
  ...
  # Установить переменную в области видимости вызывающей стороны
  #  можно с помощью тега PARENT_SCOPE
  set(${var_name} ${ret} PARENT_SCOPE)
endfunction()

Добавление исходников к цели с target_sources

Лучше добавлять специфичные исходники с помощью target_sources, а не с помощью дополнительных переменных.

add_library(hello hello.cxx)

if(WIN32)
  target_sources(hello PRIVATE system_win.cxx)
elseif(UNIX)
  target_sources(hello PRIVATE system_posix.cxx)
else()
  target_sources(hello PRIVATE system_generic.cxx)
endif()

Интерфейс к утилитам командной строки

Подробнее см. Command-Line Tool Mode

# Создать каталог debug-build
cmake -E make_directory debug-build
# Перейти в каталог debug-build
cmake -E chdir debug-build

Функция find_package

Функция find_package принимает имя библиотеки как аргумент и обращается к CMake, чтобы найти скрипт для настройки переменных данной библиотеки. В итоге при сборке либо возникает ошибка из-за того что пакет не найден, либо добавляются переменные, хранящие пути поиска заголовков, имена библиотек для компоновщика и другие параметры.

Пример подключения Boost, вызывающего встроенный в CMake скрипт FindBoost:

# Весь Boost без указания конкретных компонентов
find_package(Boost REQUIRED)
# Теперь доступны переменные
# - Boost_INCLUDE_DIRS: пути к заголовочным файлам
# - Boost_LIBRARY_DIRS: пути к статическим/динамическим библиотекам
# - Boost_LIBRARIES: список библиотек для компоновщика
# - Boost_<C>_LIBRARY: библиотека для компоновки с компонентом <C> библиотек Boost

Пример подключения библиотеки Bullet с помощью встроенного скрипта FindBullet и компоновки с приложением my_app:

# Вызываем встроенный скрипт FindBullet.cmake
find_package(Bullet REQUIRED)

# Добавляем пути поиска заголовков к цели my_app
target_include_directories(my_app ${BULLET_INCLUDE_DIRS})

# Добавляем список библиотек для компоновки с целью my_app
target_link_libraries(my_app ${BULLET_LIBRARIES})

Полное руководство по CMake. Часть вторая: Система сборки +37

C++, Системы сборки, C


Рекомендация: подборка платных и бесплатных курсов таргетированной рекламе — https://katalog-kursov.ru/

Введение

В данной статье рассмотрено использование системы сборки CMake, применяемой в колоссальном количестве проектов на C/C++. Строго рекомендуется прочитать первую часть руководства во избежание непонимания синтаксиса языка CMake, явным образом фигурирующего на протяжении всей статьи.

Ниже приведены примеры использования языка CMake, по которым Вам следует попрактиковаться. Экспериментируйте с исходным кодом, меняя существующие команды и добавляя новые. Чтобы запустить данные примеры, установите CMake с официального сайта.

Принцип работы

Система сборки CMake представляет из себя оболочку над другими платформенно зависимыми утилитами (например, Ninja или Make). Таким образом, в самом процессе сборки, как бы парадоксально это ни звучало, она непосредственного участия не принимает.

Система сборки CMake принимает на вход файл CMakeLists.txt с описанием правил сборки на формальном языке CMake, а затем генерирует промежуточные и нативные файлы сборки в том же каталоге, принятых на Вашей платформе.

Сгенерированные файлы будут содержать конкретные названия системных утилит, директорий и компиляторов, в то время как команды CMake орудуют лишь абстрактным понятием компилятора и не привязаны к платформенно зависимым инструментам, сильно различающихся на разных операционных системах.

Проверка версии CMake

Команда cmake_minimum_required проверяет запущенную версию CMake: если она меньше указанного минимума, то CMake завершает свою работу фатальной ошибкой. Пример, демонстрирующий типичное использование данной команды в начале любого CMake-файла:

# Задать третью минимальную версию CMake:
cmake_minimum_required(VERSION 3.0)

Как подметили в комментариях, команда cmake_minimum_required выставляет все флаги совместимости (смотреть cmake_policy). Некоторые разработчики намеренно выставляют низкую версию CMake, а затем корректируют функционал вручную. Это позволяет одновременно поддерживать древние версии CMake и местами использовать новые возможности.

Оформление проекта

В начале любого CMakeLists.txt следует задать характеристики проекта командой project для лучшего оформления интегрированными средами и прочими инструментами разработки.

# Задать характеристики проекта "MyProject":
project(MyProject VERSION 1.2.3.4 LANGUAGES C CXX)

Стоит отметить, что если ключевое слово LANGUAGES опущено, то по умолчанию задаются языки C CXX. Вы также можете отключить указание любых языков путём написания ключевого слова NONE в качестве списка языков или просто оставить пустой список.

Запуск скриптовых файлов

Команда include заменяет строку своего вызова кодом заданного файла, действуя аналогично препроцессорной команде include языков C/C++. Этот пример запускает скриптовый файл MyCMakeScript.cmake описанной командой:

message("'TEST_VARIABLE' is equal to [${TEST_VARIABLE}]")

# Запустить скрипт `MyCMakeScript.cmake` на выполнение:
include(MyCMakeScript.cmake)

message("'TEST_VARIABLE' is equal to [${TEST_VARIABLE}]")

В данном примере, первое сообщение уведомит о том, что переменная TEST_VARIABLE ещё не определена, однако если скрипт MyCMakeScript.cmake определит данную переменную, то второе сообщение уже будет информировать о новом значении тестовой переменной. Таким образом, скриптовый файл, включаемый командой include, не создаёт собственной области видимости, о чём упомянули в комментариях к предыдущей статье.

Компиляция исполняемых файлов

Команда add_executable компилирует исполняемый файл с заданным именем из списка исходников. Важно отметить, что окончательное имя файла зависит от целевой платформы (например, <ExecutableName>.exe или просто <ExecutableName>). Типичный пример вызова данной команды:

# Скомпилировать исполняемый файл "MyExecutable" из
# исходников "ObjectHandler.c", "TimeManager.c" и "MessageGenerator.c":
add_executable(MyExecutable ObjectHandler.c TimeManager.c MessageGenerator.c)

Компиляция библиотек

Команда add_library компилирует библиотеку с указанным видом и именем из исходников. Важно отметить, что окончательное имя библиотеки зависит от целевой платформы (например, lib<LibraryName>.a или <LibraryName>.lib). Типичный пример вызова данной команды:

# Скомпилировать статическую библиотеку "MyLibrary" из
# исходников "ObjectHandler.c", "TimeManager.c" и "MessageConsumer.c":
add_library(MyLibrary STATIC ObjectHandler.c TimeManager.c MessageConsumer.c)

  • Статические библиотеки задаются ключевым словом STATIC вторым аргументом и представляют из себя архивы объектных файлов, связываемых с исполняемыми файлами и другими библиотеками во время компиляции.
  • Динамические библиотеки задаются ключевым словом SHARED вторым аргументом и представляют из себя двоичные библиотеки, загружаемые операционной системой во время выполнения программы.
  • Модульные библиотеки задаются ключевым словом MODULE вторым аргументом и представляют из себя двоичные библиотеки, загружаемые посредством техник выполнения самим исполняемым файлом.
  • Объектные библиотеки задаются ключевым словом OBJECT вторым аргументом и представляют из себя набор объектных файлов, связываемых с исполняемыми файлами и другими библиотеками во время компиляции.

Добавление исходников к цели

Бывают случаи, требующие многократного добавления исходных файлов к цели. Для этого предусмотрена команда target_sources, способная добавлять исходники к цели множество раз.

Первым аргументом команда target_sources принимает название цели, ранее указанной с помощью команд add_library или add_executable, а последующие аргументы являются списком добавляемых исходных файлов.

Повторяющиеся вызовы команды target_sources добавляют исходные файлы к цели в том порядке, в каком они были вызваны, поэтому нижние два блока кода являются функционально эквивалентными:

# Задать исполняемый файл "MyExecutable" из исходников
# "ObjectPrinter.c" и "SystemEvaluator.c":
add_executable(MyExecutable ObjectPrinter.c SystemEvaluator.c)

# Добавить к цели "MyExecutable" исходник "MessageConsumer.c":
target_sources(MyExecutable MessageConsumer.c)
# Добавить к цели "MyExecutable" исходник "ResultHandler.c":
target_sources(MyExecutable ResultHandler.c)

# Задать исполняемый файл "MyExecutable" из исходников
# "ObjectPrinter.c", "SystemEvaluator.c", "MessageConsumer.c" и "ResultHandler.c":
add_executable(MyExecutable ObjectPrinter.c SystemEvaluator.c MessageConsumer.c
ResultHandler.c)

Генерируемые файлы

Местоположение выходных файлов, сгенерированных командами add_executable и add_library, определяется только на стадии генерации, однако данное правило можно изменить несколькими переменными, определяющими конечное местоположение двоичных файлов:

  • Переменные RUNTIME_OUTPUT_DIRECTORY и RUNTIME_OUTPUT_NAME определяют местоположение целей выполнения.
  • Переменные LIBRARY_OUTPUT_DIRECTORY и LIBRARY_OUTPUT_NAME определяют местоположение библиотек.
  • Переменные ARCHIVE_OUTPUT_DIRECTORY и ARCHIVE_OUTPUT_NAME определяют местоположение архивов.

Исполняемые файлы всегда рассматриваются целями выполнения, статические библиотеки — архивными целями, а модульные библиотеки — библиотечными целями. Для «не-DLL» платформ динамические библиотеки рассматриваются библиотечными целями, а для «DLL-платформ» — целями выполнения. Для объектных библиотек таких переменных не предусмотрено, поскольку такой вид библиотек генерируется в недрах каталога CMakeFiles.

Важно подметить, что «DLL-платформами» считаются все платформы, основанные на Windows, в том числе и Cygwin.

Компоновка с библиотеками

Команда target_link_libraries компонует библиотеку или исполняемый файл с другими предоставляемыми библиотеками. Первым аргументом данная команда принимает название цели, сгенерированной с помощью команд add_executable или add_library, а последующие аргументы представляют собой названия целей библиотек или полные пути к библиотекам. Пример:

# Скомпоновать исполняемый файл "MyExecutable" с
# библиотеками "JsonParser", "SocketFactory" и "BrowserInvoker":
target_link_libraries(MyExecutable JsonParser SocketFactory BrowserInvoker)

Стоит отметить, что модульные библиотеки не подлежат компоновке с исполняемыми файлами или другими библиотеками, так как они предназначены исключительно для загрузки техниками выполнения.

Работа с целями

Как упомянули в комментариях, цели в CMake тоже подвержены ручному манипулированию, однако весьма ограниченному.

Имеется возможность управления свойствами целей, предназначенных для задания процесса сборки проекта. Команда get_target_property присваивает предоставленной переменной значение свойства цели. Данный пример выводит значение свойства C_STANDARD цели MyTarget на экран:

# Присвоить переменной "VALUE" значение свойства "C_STANDARD":
get_target_property(VALUE MyTarget C_STANDARD)

# Вывести значение полученного свойства на экран:
message("'C_STANDARD' property is equal to [${VALUE}]")

Команда set_target_properties устанавливает указанные свойства целей заданными значениями. Данная команда принимает список целей, для которых будут установлены значения свойств, а затем ключевое слово PROPERTIES, после которого следует список вида «<название свойства> <новое значение>»:

# Установить свойству 'C_STANDARD' значение "11",
# а свойству 'C_STANDARD_REQUIRED' значение "ON":
set_target_properties(MyTarget PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED ON)

Пример выше задал цели MyTarget свойства, влияющие на процесс компиляции, а именно: при компиляции цели MyTarget CMake затребует компилятора о использовании стандарта C11. Все известные именования свойств целей перечисляются на этой странице.

Также имеется возможность проверки ранее определённых целей с помощью конструкции if(TARGET <TargetName>):

# Выведет "The target was defined!" если цель "MyTarget" уже определена,
# а иначе выведет "The target was not defined!":
if(TARGET MyTarget)
    message("The target was defined!")
else()
    message("The target was not defined!")
endif()

Добавление подпроектов

Команда add_subdirectory побуждает CMake к незамедлительной обработке указанного файла подпроекта. Пример ниже демонстрирует применение описанного механизма:

# Добавить каталог "subLibrary" в сборку основного проекта,
# а генерируемые файлы расположить в каталоге "subLibrary/build":
add_subdirectory(subLibrary subLibrary/build)

В данном примере первым аргументом команды add_subdirectory выступает подпроект subLibrary, а второй аргумент необязателен и информирует CMake о папке, предназначенной для генерируемых файлов включаемого подпроекта (например, CMakeCache.txt и cmake_install.cmake).

Стоит отметить, что все переменные из родительской области видимости унаследуются добавленным каталогом, а все переменные, определённые и переопределённые в данном каталоге, будут видимы лишь ему (если ключевое слово PARENT_SCOPE не было определено аргументом команды set). Данную особенность упомянули в комментариях к предыдущей статье.

Поиск пакетов

Команда find_package находит и загружает настройки внешнего проекта. В большинстве случаев она применяется для последующей линковки внешних библиотек, таких как Boost и GSL. Данный пример вызывает описанную команду для поиска библиотеки GSL и последующей линковки:

# Загрузить настройки пакета библиотеки "GSL":
find_package(GSL 2.5 REQUIRED)

# Скомпоновать исполняемый файл с библиотекой "GSL":
target_link_libraries(MyExecutable GSL::gsl)

# Уведомить компилятор о каталоге заголовков "GSL":
target_include_directories(MyExecutable ${GSL_INCLUDE_DIRS})

В приведённом выше примере команда find_package первым аргументом принимает наименование пакета, а затем требуемую версию. Опция REQUIRED требует печати фатальной ошибки и завершении работы CMake, если требуемый пакет не найден. Противоположность — это опция QUIET, требующая CMake продолжать свою работу, даже если пакет не был найден.

Далее исполняемый файл MyExecutable линкуется с библиотекой GSL командой target_link_libraries с помощью переменной GSL::gsl, инкапсулирующей расположение уже скомпилированной GSL.

В конце вызывается команда target_include_directories, информирующая компилятора о расположении заголовочных файлов библиотеки GSL. Обратите внимание на то, что используется переменная GSL_INCLUDE_DIRS, хранящая местоположение описанных мною заголовков (это пример импортированных настроек пакета).

Вам, вероятно, захочеться проверить результат поиска пакета, если Вы указали опцию QUIET. Это можно сделать путём проверки переменной <PackageName>_FOUND, автоматически определяемой после завершения команды find_package. Например, в случае успешного импортирования настроек GSL в Ваш проект, переменная GSL_FOUND обратится в истину.

В общем случае, команда find_package имеет две разновидности запуска: модульную и конфигурационную. Пример выше применял модульную форму. Это означает, что во время вызова команды CMake ищет скриптовый файл вида Find<PackageName>.cmake в директории CMAKE_MODULE_PATH, а затем запускает его и импортирует все необходимые настройки (в данном случае CMake запустила стандартный файл FindGSL.cmake).

Способы включения заголовков

Информировать компилятора о располжении включаемых заголовков можно посредством двух команд: include_directories и target_include_directories. Вы решаете, какую из них использовать, однако стоит учесть некоторые различия между ними (идея предложена в комментариях).

Команда include_directories влияет на область каталога. Это означает, что все директории заголовков, указанные данной командой, будут применяться для всех целей текущего CMakeLists.txt, а также для обрабатываемых подпроектов (смотреть add_subdirectory).

Команда target_include_directories влияет лишь на указанную первым аргументом цель, а на другие цели никакого воздействия не оказывается. Пример ниже демонстрирует разницу между этими двумя командами:

add_executable(RequestGenerator RequestGenerator.c)
add_executable(ResponseGenerator ResponseGenerator.c)

# Применяется лишь для цели "RequestGenerator":
target_include_directories(RequestGenerator headers/specific)

# Применяется для целей "RequestGenerator" и "ResponseGenerator":
include_directories(headers)

В комментариях упомянуто, что в современных проектах применение команд include_directories и link_libraries является нежелательным. Альтернатива — это команды target_include_directories и target_link_libraries, действующие лишь на конкретные цели, а не на всю текущую область видимости.

Установка проекта

Команда install генерирует установочные правила для Вашего проекта. Данная команда способна работать с целями, файлами, папками и многим другим. Сперва рассмотрим установку целей.

Для установки целей необходимо первым аргументом описанной функции передать ключевое слово TARGETS, за которым должен следовать список устанавливаемых целей, а затем ключевое слово DESTINATION с расположением каталога, в который установятся указанные цели. Данный пример демонстрирует типичную установку целей:

# Установить цели "TimePrinter" и "DataScanner" в директорию "bin":
install(TARGETS TimePrinter DataScanner DESTINATION bin)

Процесс описания установки файлов аналогичен, за тем исключением, что вместо ключевого слова TARGETS следует указать FILES. Пример, демонстрирующий установку файлов:

# Установить файлы "DataCache.txt" и "MessageLog.txt" в директорию "~/":
install(FILES DataCache.txt MessageLog.txt DESTINATION ~/)

Процесс описания установки папок аналогичен, за тем исключением, что вместо ключевого слова FILES следует указать DIRECTORY. Важно подметить, что при установке будет копироваться всё содержимое папки, а не только её название. Пример установки папок выглядит следующим образом:

# Установить каталоги "MessageCollection" и "CoreFiles" в директорию "~/":
install(DIRECTORY MessageCollection CoreFiles DESTINATION ~/)

После завершения обработки CMake всех Ваших файлов Вы можете выполнить установку всех описанных объектов командой sudo checkinstall (если CMake генерирует Makefile), или же выполнить данное действие интегрированной средой разработки, поддерживающей CMake.

Наглядный пример проекта

Данное руководство было бы неполным без демонстрации реального примера использования системы сборки CMake. Рассмотрим схему простого проекта, использующего CMake в качестве единственной системы сборки:

+ MyProject
      - CMakeLists.txt
      - Defines.h
      - StartProgram.c
      + core
            - CMakeLists.txt
            - Core.h
            - ProcessInvoker.c
            - SystemManager.c

Главный файл сборки CMakeLists.txt описывает компиляцию всей программы: сперва происходит вызов команды add_executable, компилирующей исполняемый файл, затем вызывается команда add_subdirectory, побуждающая обработку подпроекта, и наконец, исполняемый файл линкуется с собранной библиотекой:

# Задать минимальную версию CMake:
cmake_minimum_required(VERSION 3.0)

# Указать характеристики проекта:
project(MyProgram VERSION 1.0.0 LANGUAGES C)

# Добавить в сборку исполняемый файл "MyProgram":
add_executable(MyProgram StartProgram.c)

# Требовать обработку файла "core/CMakeFiles.txt":
add_subdirectory(core)

# Скомпоновать исполняемый файл "MyProgram" со
# скомпилированной статической библиотекой "MyProgramCore":
target_link_libraries(MyProgram MyProgramCore)

# Установить исполняемый файл "MyProgram" в директорию "bin":
install(TARGETS MyProgram DESTINATION bin)

Файл core/CMakeLists.txt вызывается главным файлом сборки и компилирует статическую библиотеку MyProgramCore, предназначенную для линковки с исполняемым файлом:

# Задать минимальную версию CMake:
cmake_minimum_required(VERSION 3.0)

# Добавить в сборку статическую библиотеку "MyProgramCore":
add_library(MyProgramCore STATIC ProcessInvoker.c SystemManager.c)

После череды команд cmake . && make && sudo checkinstall работа системы сборки CMake завершается успешно. Первая команда запускает обработку файла CMakeLists.txt в корневом каталоге проекта, вторая команда окончательно компилирует необходимые двоичные файлы, а третья команда устанавливает скомпонованный исполняемый файл MyProgram в систему.

Заключение

Теперь Вы способны писать свои и понимать чужие CMake-файлы, а подробно прочитать про остальные механизмы Вы можете на официальном сайте.

Следующая статья данного руководства будет посвящена тестированию и созданию пакетов с помощью CMake и выйдет через неделю. До скорых встреч!

Эта статья находится в разработке!

Содержание

  • 1 Что это и зачем нужно
    • 1.1 Краткое описание
  • 2 Старт
  • 3 Подробное описание
    • 3.1 Указание необходимой версии cmake
    • 3.2 Название проекта
    • 3.3 Переменные
    • 3.4 Устанавливаем команды компилятору
    • 3.5 Папка с хедерами
    • 3.6 Самое важное — подключение библиотек
    • 3.7 Пример хорошего CMakeLists.txt и где он будет лежать
    • 3.8 Как создать библиотеку в поддиректории и слинковать ее с основной программой
  • 4 Как использовать CMake в связке с QtCreator
    • 4.1 Как добавить header в проект, чтобы его было видно в списке файлов

Что это и зачем нужно

CMake — кроссплатформенная автоматизированная система сборки проектов.
Непосредственно сборкой она не занимается, а только генерирует Makefile, который потом будет выполнен утилитой make.

CMake может проверять наличие необходимых библиотек и подключать их, собирать проекты под разными компиляторами и операционными системами. Т.е. у вас есть куча кода и файлик, содержащий информацию для cmake, и чтобы скомпилить
это дело где-нибудь еще, вам нужно просто запустить там cmake, который сделает всё сам. Удобно, полезно, просто.

Краткое описание

Если нет желания/времени/сил читать весь туториал и Вы используете какой-нибудь QtCreator (или любая другая IDE, умеющая работать с cmake), то:

  • Создайте в IDE проект под cmake
  • Найдите в папке с проектом CMakeFiles.txt
  • Пробегитесь глазами по туториалу, соотнося его с вашим CMakeFiles.txt

Про подключение библиотек рекомендуется все-таки прочитать целиком.

Старт

Предполагается, что найти и скачать сам cmake ты, %username%, в состоянии. //а если нет?

Предположим, у Вас есть исходничек «test.cpp» (// а если нет?)(А если нет, то CMake тебе трогать рано).
Для начала нужно создать файлик для cmake, который обычно называют «CMakeLists.txt», и написать туда вот это:

add_executable(test test.cpp)

Теперь запускаем (из консоли) в этой папке команду «cmake CMakeLists.txt» (аргументом можно передавать не только файл, но и директорию, в которой он лежит, тогда cmake найдет его сам).

cmake будет использовать переданный (или найденный) файл проекта (тот самый CMakeLists.txt), и в текущей директории будет создавать проект.
Проект — это много-много файлов и директорий (примечание: поэтому лучше запускать cmake из другой директории, чтобы можно было, например, быстро удалить все бинарники), из которых нас больше всего интересует Makefile.

Makefile — это файл, нужный для утилиты make.
Именно она запускает компиляторы, линковщики и прочие радости. Запускаем make в каталоге сборки (т.е. там же, где Вы запускали cmake).
В консоли вылезет примерно такой текст:

Scanning dependencies of target test
[100%] Building CXX object CMakeFiles/test.dir/test.cpp.o
Linking CXX executable test
[100%] Built target test

А у Вас в папочке появится исполняемый файл «test». Запустите, убедитесь, что это действительно то, что ожидается от компиляции файла «test.cpp».

Подробное описание

Поразбираемся с различными возможностями cmake.

Указание необходимой версии cmake

cmake_minimum_required(VERSION 2.6)

Указывайте высокую минимальную версию CMake.
Если используемая версия cmake меньше 2.6, он не захочет работать. Писать эту команду всегда — хороший стиль (cmake будет пыхтеть и обижаться, если вы не укажете версию, но собирать всё равно всё будет).

Название проекта

project(visualization)

Указывает, что этот cmake-файл является корневым для некоторого проекта. С проектами связаны определенные переменные и поведение cmake (читайте документацию).

Переменные

В cmake можно создавать текстовые переменные. Команда

set(VARIABLE The variable's value)

запишет в переменную «VARIABLE» значение «The variable’s value».
Чтобы где-либо использовать значение этой переменной, нужно написать ${VARIABLE}.

Чтобы добавить к переменной некий текст, можно сделать так:

set(VARIABLE "${VARIABLE} new text")

Как видите, использовать значение можно и внутри кавычек.
Переменные активно используются различными библиотеками — для установки флагов, параметров сборки/линковки и прочих вкусностей, об этом чуть-чуть попозже.

Пример коше’гного проекта со списком сорцов в отдельной переменной:

cmake_minimum_required(VERSION 2.6)

set(SOURCES test.cpp lib1.cpp lib2.cpp)

add_executable(test ${SOURCES})

Устанавливаем команды компилятору

add_definitions(-DSOME_IMPORTANT_DEFINITION)

Эта команда используется для установки дефайнов, которыe можно проверить в коде через, например, #ifdef SOME_IMPORTANT_DEFINITION.

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")

Эта команда добавит к флагам, используемым при сборке c++-кода, флаги -std=c++11 и -Wall.

Кто не знает: «-std=c++11» включает в gcc поддержку стандарта c++11, «-Wall» говорит gcc выводить все предупреждения (очень советую, помогает отловить много глупых багов и писать аккуратный код).

Если ваша версия GCC меньше, чем 4.7.0, вместо -std=c++11 нужно использовать -std=c++0x.

В GCC 4.8.0 появился флаг -std=c++1y, в котором начинают реализовывать фичи следующего стандарта.

Папка с хедерами

Допустим, Вы хотите, чтобы хедеры (файлики, подключаемые через #include) искались еще и в каталогах «headers/» и «more_headers/»:

include_directories("headers/" "more_headers/")

Надеюсь, и это понятно.

Самое важное — подключение библиотек

Научимся искать и подключать библиотеки при помощи cmake на примере Boost.
Для начала установим переменные для буста:

set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_MULTITHREADED ON)

Первое — мы не хотим, чтобы буст подключался к нам статически (т.е. хотим динамическую линковку). Если ты, %username%, не знаешь, что это, пока просто забей и используй этот флаг так, как написано. Но в ближайшее время узнай, о чем речь.
Второй флаг разрешает бусту внутри своих магических реализаций использовать треды для распараллеливания и прочих радостей.

Итак, мы установили флаги. Давайте найдем буст!

Допустим, нам нужны компоненты буста под названием chrono (библиотека для работы со временем) и filesystem (библиотека для работы с файловой системой):

find_package(Boost COMPONENTS chrono filesystem REQUIRED)

Win, будут искаться только нужные библиотеки, и их расположение будет записано в переменную Boost_LIBRARIES.

Опция «REQUIRED» говорит о том, что библиотека необходима проекту.
Без нее cmake решит, что отсутствие данной библиотеки — не так уж и страшно, и будет собирать дальше.

Добавим директории с хедерами буста для поиска в них хедеров:

include_directories(${Boost_INCLUDE_DIRS})

Итак, осталось найденные библиотеки подключить к исполняемому файлу.

target_link_libraries(test ${Boost_LIBRARIES})

В качестве библиотек нужно указать пути к необходимым собранным библиотекам. cmake нашел указанные нами библиотеки и записал в переменную, чем мы и пользуемся.

Заметим, что эту команду нужно вызывать после того, как создан target сборки (через add_executable).

Пример хорошего CMakeLists.txt и где он будет лежать

Итак, полный пример использования всего этого. У нас есть некая директория (отныне считаем ее «/sources»), и в ней лежат исходники

/sources/lib1/main.cpp
/sources/lib2/main.cpp
/sources/main.cpp

В корне «/» лежит файл «/CMakeLists.txt»:

cmake_minimum_required(VERSION 2.8)
project(cmake-example)

set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_MULTITHREADED ON)
find_package(Boost COMPONENTS chrono filesystem REQUIRED)

set(CMAKE_CXX_FLAGS "$$${CMAKE_CXX_FLAGS} -std=c++11 -Wall")

set(SRC_LIST lib1/main.cpp lib2/main.cpp main.cpp)

add_executable($$${PROJECT_NAME} $$${SRC_LIST})
target_link_libraries($$${PROJECT_NAME} $$${Boost_LIBRARIES})

Если Вам что-то в нём не понятно — перечитайте соответствующую информацию выше.

Создаем директорию «/build» (не «/sources/build»), переходим в нее, запускаем в ней «cmake ..».
«..» — метка родительской директории.
cmake возьмет из нее наш CMakeLists.txt и по нему создаст проект в папке «/build».
Чтобы проект собрать, запускаем «make» в той же папке «/build».

Таким образом, в корне у нас есть:

  • CMakeLists.txt
  • директория с исходниками
  • каталог сборки

Все разделено, автоматизировано и удобно.

Как создать библиотеку в поддиректории и слинковать ее с основной программой

Пусть в ./ лежит основной проект, а в ./subdir мы хотим сделать либу, а в ./build построить проект.

./subdir/CMakeLists.txt

project(MegaLibrary)
set(SOURCES lib.cpp)
set(HEADERS lib.h)
add_library(lib $$${SOURCES} $$${HEADERS})
target_include_directories(lib PUBLIC $$${CMAKE_CURRENT_SOURCE_DIR})

./CMakeLists.txt

project(MainProject)
set(MAIN_PROJECT_SRC_LIST main)
# Other stuff
add_executable(main $$${MAIN_PROJECT_SRC_LIST})
add_subdirectory(subdir)
target_link_libraries(main lib)

Теперь можно в файлах основного проекта делать #include «lib.h» (см. документацию по target_include_directories).

В ./build запускаем «cmake .. && make» и получаем собранный проект.

Как использовать CMake в связке с QtCreator

Интеграция с cmake у QtCreator не очень тесная, тем не менее, работать с ним можно.

Создаем новый проект без использования Qt, выбираем «Проект на С++ с использованием CMake». Создастся дефолтный файл сборки, который просто добавляет все исходники в директории проекта и компилирует их в один бинарник.

Если вы создали файл header.h в директорию проекта, просто строчку

add_executable($$${PROJECT_NAME} $$${SRC_LIST})

измените на

add_executable($$${PROJECT_NAME} $$${SRC_LIST} "header.h")

Добавлено 7 января 2023 в 23:23

С чего начать работу с CMake? Этот шаг познакомит вас с базовыми синтаксисом, некоторыми командами и переменными CMake. По мере введения этих концепций мы проработаем три упражнения и создадим простой проект CMake.

Руководство CMake. Шаг 1. Базовая отправная точка

Каждое упражнение на этом этапе начинается с некоторой исходной информации. Затем предоставляется цель и список полезных ресурсов. Каждый файл в разделе «Редактируемые файлы» находится в каталоге Step1 и содержит один или несколько комментариев TODO. Каждое TODO представляет собой одну или две строки кода, которые нужно изменить или добавить. TODO предназначены для выполнения в порядке нумерации, сначала выполните TODO 1, затем TODO 2 и т. д. В разделе «С чего начать» вы найдете несколько полезных советов и руководство по выполнению упражнения. Затем в разделе «Сборка и запуск» будет пошагово показано, как создать и протестировать упражнение. Наконец, в конце каждого упражнения обсуждается предполагаемое решение.

Также обратите внимание, что каждый шаг в руководстве основан на следующем. Так, например, исходный код для Шага 2 является полным решением для Шага 1.

Упражнение 1. Создание базового проекта

Самый простой проект CMake – это исполняемый файл, созданный из одного файла исходного кода. Для таких простых проектов достаточно файла CMakeLists.txt с тремя командами.

Примечание. Хотя CMake поддерживает команды, набранные в верхнем, нижнем и смешанном регистре, использование нижнего регистра предпочтительнее и будет использоваться на протяжении всего руководства.

Самый популярный файл CMakeLists.txt любого проекта должен начинаться с указания минимальной версии CMake с помощью команды cmake_minimum_required(). Она устанавливает параметры политики и гарантирует, что следующие функции CMake выполняются с совместимой версией CMake.

Чтобы начать проект, мы используем команду project() для установки имени проекта. Этот вызов требуется для каждого проекта и должен вызываться вскоре после cmake_minimum_required(). Как мы увидим позже, эту команду также можно использовать для указания другой информации на уровне проекта, такой как язык или номер версии.

Наконец, команда add_executable() указывает CMake создать исполняемый файл, используя указанные файлы исходного кода.

Цель

Узнать, как создать простой проект CMake.

Полезные ресурсы

  • add_executable()
  • cmake_minimum_required()
  • project()

Редактируемые файлы

  • CMakeLists.txt

С чего начать

Исходный код tutorial.cxx находится в каталоге Help/guide/tutorial/Step1 и может использоваться для вычисления квадратного корня числа. Этот файл на данном шаге редактировать не нужно.

В том же каталоге находится файл CMakeLists.txt, который вы должны заполнить. Начните с TODO 1 и проработайте до TODO 3.

Сборка и запуск

Как только TODO 1 – TODO 3 будут выполнены, мы будем готовы собрать и запустить наш проект! Сначала запустите исполняемый файл cmake или cmake-gui, чтобы сконфигурировать проект, а затем соберите его с помощью выбранного инструмента сборки.

Например, из командной строки мы можем перейти в каталог Help/guide/tutorial дерева исходного кода CMake и создать каталог сборки:

mkdir Step1_build

Затем перейдите в этот каталог сборки и запустите cmake, чтобы настроить проект и создать файлы нативной системы сборки:

cd Step1_build
cmake ../Step1

Затем вызовите эту систему сборки, чтобы скомпилировать/слинковать проект:

cmake --build .

Наконец, попробуйте использовать только что собранный Tutorial с этими командами:

Tutorial 4294967296
Tutorial 10
Tutorial

Решение

Как упоминалось выше, всё, что нам нужно для запуска, – это трехстрочный файл CMakeLists.txt. Первая строка – используем cmake_minimum_required() для установки версии CMake следующим образом:

TODO 1

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

Следующим шагом для создания базового проекта является использование команды project() следующим образом, чтобы установить имя проекта:

TODO 2

CMakeLists.txt

project(Tutorial)

Последней командой для вызова базового проекта является add_executable(). Мы вызываем его так:

TODO 3

CMakeLists.txt

add_executable(Tutorial tutorial.cxx)

Упражнение 2. Определение стандарта C++

В CMake есть специальные переменные, которые либо создаются за кулисами, либо имеют значение для CMake, если они установлены кодом проекта. Многие из этих переменных начинаются с CMAKE_. Согласно соглашению об именовании избегайте этого префикса при создании переменных для ваших проектов. Две из этих специальных устанавливаемых пользователем переменных – CMAKE_CXX_STANDARD и CMAKE_CXX_STANDARD_REQUIRED. Их можно использовать вместе, чтобы указать стандарт C++, необходимый для сборки проекта.

Цель

Добавить функцию, которая требует C++11.

Полезные ресурсы

  • CMAKE_CXX_STANDARD
  • CMAKE_CXX_STANDARD_REQUIRED
  • set()

Редактируемы файлы

  • CMakeLists.txt
  • tutorial.cxx

С чего начать

Продолжаем редактировать файлы в каталоге Step1. Начните с TODO 4 и завершите TODO 6.

Во-первых, отредактируйте tutorial.cxx, добавив функцию, для которой требуется C++11. Затем обновите CMakeLists.txt, чтобы он требовал C++11.

Сборка и запуск

Давайте снова соберем наш проект. Поскольку мы уже создали каталог сборки и запустили CMake для упражнения 1, мы можем перейти к этапу сборки:

cd Step1_build
cmake --build .

Теперь мы можем попробовать использовать только что собранное приложение Tutorial с теми же командами, что и раньше:

Tutorial 4294967296
Tutorial 10
Tutorial

Решение

Начнем с добавления в наш проект функций C++11, заменив atof на std::stod в tutorial.cxx. Это выглядит следующим образом:

TODO 4

tutorial.cxx

  const double inputValue = std::stod(argv[1]);

Чтобы выполнить TODO 5, просто удалите #include <cstdlib>.

Нам нужно будет явно указать в коде CMake, что он должен использовать правильные флаги. Один из способов включить поддержку определенного стандарта C++ в CMake – использовать переменную CMAKE_CXX_STANDARD. Для этого руководства установите для переменной CMAKE_CXX_STANDARD в файле CMakeLists.txt значение 11, а для CMAKE_CXX_STANDARD_REQUIRED – значение True. Объявления CMAKE_CXX_STANDARD обязательно добавьте перед вызовом add_executable().

TODO 6

CMakeLists.txt

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

Упражнение 3. Добавление номера версии и сконфигурированного заголовочного файла

Иногда бывает полезно, чтобы переменная, определенная в файле CMakelists.txt, также была доступна в исходном коде. В этом случае мы хотели бы распечатать версию проекта.

Один из способов добиться этого – использовать сконфигурированный заголовочный файл. Мы создаем входной файл с одной или несколькими переменными для замены. Эти переменные имеют специальный синтаксис, похожий на @VAR@. Затем мы используем команду configure_file(), чтобы скопировать входной файл в заданный выходной файл и заменить эти переменные текущим значением VAR в файле CMakelists.txt.

Хотя мы могли бы редактировать версию непосредственно в исходном коде, использование этой функции предпочтительнее, поскольку она создает единый источник и позволяет избежать дублирования.

Цель

Определить и сообщить номер версии проекта.

Полезные ресурсы

  • <PROJECT-NAME>_VERSION_MAJOR
  • <PROJECT-NAME>_VERSION_MINOR
  • configure_file()
  • target_include_directories()

Редактируемые файлы

  • CMakeLists.txt
  • tutorial.cxx

С чего начать

Продолжаем редактировать файлы из Step1. Начните с TODO 7 и завершите TODO 12. В этом упражнении мы начнем с добавления номера версии проекта в CMakeLists.txt. В том же файле используйте configure_file(), чтобы скопировать заданный входной файл в выходной файл и заменить некоторые значения переменных в содержимом входного файла.

Затем создайте входной заголовочный файл TutorialConfig.h.in, определяющий номера версий, которые будут принимать переменные, переданные из configure_file().

Наконец, обновите tutorial.cxx, чтобы распечатать номер его версии.

Сборка и запуск

Давайте снова соберем наш проект. Как и раньше, мы уже создали каталог сборки и запустили CMake, поэтому можем перейти к этапу сборки:

cd Step1_build
cmake --build .

Убедитесь, что теперь при запуске исполняемого файла без каких-либо аргументов выводится номер версии.

Решение

В этом упражнении мы улучшаем наш исполняемый файл с помощью вывода номера версии. Хотя мы могли бы сделать это исключительно в исходном коде, использование CMakeLists.txt позволяет нам поддерживать единый источник данных для номера версии.

Сначала мы модифицируем файл CMakeLists.txt, чтобы использовать команду project() для установки имени проекта и номера версии. Когда вызывается команда project(), CMake за кулисами определяет Tutorial_VERSION_MAJOR и Tutorial_VERSION_MINOR.

TODO 7

CMakeLists.txt

project(Tutorial VERSION 1.0)

Затем мы используем configure_file() для копирования входного файла с заменой переменных, указанных CMake:

TODO 8

CMakeLists.txt

configure_file(TutorialConfig.h.in TutorialConfig.h)

Поскольку сконфигурированный файл будет записан в бинарный каталог проекта, мы должны добавить этот каталог в список путей для поиска включаемых файлов.

Примечание. В этом руководстве мы будем ссылаться на сборку проекта и каталог бинарных файлов проекта как синонимы. Они одинаковы и не означают ссылку на каталог bin/.

Используем target_include_directories(), чтобы указать, где исполняемая цель должна искать включаемые файлы.

TODO 9

CMakeLists.txt

target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           )

TutorialConfig.h.in – это входной заголовочный файл, который необходимо сконфигурировать. Когда configure_file() вызывается из нашего CMakeLists.txt, значения для @Tutorial_VERSION_MAJOR@ и @Tutorial_VERSION_MINOR@ будут заменены в TutorialConfig.h соответствующими номерами версий из проекта.

TODO 10

TutorialConfig.h.in

// сконфигурированные параметры и настройки для Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

Затем нам нужно изменить tutorial.cxx, включив в него сконфигурированный заголовочный файл TutorialConfig.h.

TODO 11

tutorial.cxx

#include "TutorialConfig.h"

Наконец, мы распечатываем имя исполняемого файла и номер версии, обновляя tutorial.cxx следующим образом:

TODO 12

tutorial.cxx

  if (argc < 2) {
    // сообщить версию
    std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
              << Tutorial_VERSION_MINOR << std::endl;
    std::cout << "Usage: " << argv[0] << " number" << std::endl;
    return 1;
  }

Теги

C++ / CppCMakeАвтоматизация сборкиПрограммированиеСистема сборки

CMake Crash Course — Basics and Scripting

Author: methylDragon
Contains a syntax reference for CMake. We’ll be going through some concepts, the CLI, and scripting with CMake!


Pre-Requisites

Assumed knowledge

  • Have a rudimentary understanding of C/C++
    • Since CMake is used to build C/C++ projects!
  • Understood the linkage and build concepts from this tutorial
  • The tutorial is written with Linux users in mind, specifically Ubuntu
    • But the scripting section should apply in general
  • Recommended: Check out this 15 minute tutorial to familiarise yourself with CMake!

Table Of Contents

  1. Introduction
  2. CMake Concepts and Command Line
    2.1 Installation
    2.2 CMakeLists.txt
    2.3 CMake Build Order
    2.4 Project Structure
    2.5 Invoking CMake
    2.6 Configuring CMake
    2.7 Ordering
  3. CMake Scripting
    3.1 Code Style
    3.2 Minimal CMakeLists.txt
    3.3 Comments
    3.4 Printing
    3.5 Variables
    3.6 Variable Scope and Directories
    3.7 User Input: Cache Variables
    3.8 User Input: Options
    3.9 Prefixes
    3.10 Conditionals
    3.11 Loops
    3.12 Functions
    3.13 Macros

1. Introduction

CMake is Cross-platform Make, where Make is the utility for building programs from source code.

CMake is an extensible, open-source system that manages the build process in an operating system and compiler-independent manner. Unlike many cross-platform systems, CMake is designed to be used in conjunction with the native build environment. Simple configuration files placed in each source directory (called CMakeLists.txt files) are used to generate standard build files (e.g., makefiles on Unix and projects/workspaces in Windows MSVC) which are used in the usual way.

Overview

In other words, CMake is used to generate files that are used with Make to build a project. It’s like a meta-build system. (Though, actually, it can be used with a lot more build pipelines like Visual Studio, XCode, and more!)

Additionally, CMake is more generally used nowadays due to its convenience and simplicity (relative to pure Make), and so, it is far more useful to learn as a build tool. This is especially true since anything you can do in make, you can do in CMake.

When comparing CMake with Make, there are several advantages of using CMake:

  • Cross platform discovery of system libraries.
  • Automatic discovery and configuration of the toolchain.
  • Easier to compile your files into a shared library in a platform agnostic way, and in general easier to use than make.

CMake vs Make

Also note, this tutorial will not serve to be an exhaustive guide on everything CMake, but is designed to be just enough to get you up to speed and able to work somewhat effectively on projects.

Additionally, since CMake is mostly used with C or C++, this tutorial will focus on use of CMake with those languages.

In this section, we’ll go through some concepts, the command line interface, and scripting basics.

2. CMake Concepts and Command Line

Before we can even begin trying to code with CMake, we need to know what it actually does, and how to use it from the command line. So let’s take a look!

2.1 Installation

go to top

Wow! So easy! If this doesn’t work, check your software repositories, or seek out a PPA repository.

2.2 CMakeLists.txt

go to top

CMake is invoked by using a CMakeLists.txt file placed in the root directory of a project (though you can nest projects too!) This file tells CMake what to do and configures the final makefile that is produced, which will then be subsequently used with Make to build your project.

2.3 CMake Build Order

go to top

img

Image Source

CMake first configures itself by checking the CMakeLists.txt file. The configuration should specify the targets for build, their linkages, as well as any build properties or configurations, as well as run any configuration time logic.

Then, it runs that configuration through a generation step to produce a Makefile or some other build pipeline that can be used to actually build your targets.

2.4 Project Structure

go to top

There’s no standardised way to structure a C++ project, but here is a generally acceptable project structure.

This is especially important since CMake will generate a whole bunch of files where the cmake command is invoked. And no one wants generated files clogging up their source directories.

my_project
-	src/
-	include/
-	build/
-	CMakeLists.txt

There are also a couple of additional folder directories that you can put in, and a couple of other files that you can place in your project directory. Here’s a full description of them!

Also, do note that you can nest projects!

Folders

Name Description
src/ Stores source files and private headers
include/ Stores public headers for using the project’s public API
build/ Invoke CMake from within this folder for neatness’ sake, so all CMake-generated files go in here
extern/ External projects go here. Cool thing is that these projects can have their own CMakeLists.txt and you can invoke those files in the parent project’s CMakeLists.txt
doc/ Store documentation
test/ Stores test scripts
data/ Stores files that are used by the executables

Files

Name
CMakeLists.txt Input to CMake build system for building software packages
README.md README for the project. Check out how to write a good one!
LICENSE License for your code

2.5 Invoking CMake

go to top

We’ll assume your project follows the structure detailed in the previous section, and that it already has a preconfigured CMakeLists.txt. As such, we’ll build our project in the build/ directory.

# So, starting in the project directory...

cd build
cmake ..
make

So we:

  • Move into the build/ directory
  • Invoke CMake on the CMakeLists.txt in the project directory to generate the makefile
  • And call Make to build the project based off of the generated Makefile

2.6 Configuring CMake

go to top

You can also configure CMake further with command line arguments though! I can’t go through all of them, but here are a couple of handy ones.

Specify a non-default generator

The default pipeline that is created when CMake is invoked are makefiles. But you can specify a custom one to use!

Just use the -G flag!

cmake --help # See all available generators

cmake -G "GENERATOR_NAME_HERE"
cmake -G "Ninja" # Example

Set Variable (Example: Change Build Type)

Use the -D flag!

So for example, to change the build type,

cmake -DCMAKE_BUILD_TYPE=Debug # Build unoptimised code with debug flags
cmake -DCMAKE_BUILD_TYPE=MinSizeRel # Build optimised code, but at minimal size
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo # Build optimised code with debug flags
cmake -DCMAKE_BUILD_TYPE=Release # Build optimised code with no debug flags

cmake -D CMAKE_BUILD_TYPE=Release # This also works!

Additionally, do note that this is actually the best practice for invoking CMake

View Cached Variables

So you can see what variables were used during the latest invocation of CMake

Setting Build Directory

So, actually, you don’t have to call CMake from inside the build folder because you can actually just specify the build folder to build into using a couple of flags.

So your usual invocation of

Becomes

2.7 Ordering

go to top

You should think of CMake as a kind of scripting language that eventually produces files that are used to command the build of a project. (Because it actually IS a scripting language.)

As such, it is important to keep in mind the fact that the order of calls in the CMakeLists file matters.

3. CMake Scripting

Now that we have a rough understanding of how to use CMake. Let’s look at some basic programming constructs within CMake!

We’ll cover this before the actual build configuration part of CMake because it helps give a good background understanding moving forward, especially once variables and conditions start getting thrown all around.

So do be patient!

3.1 Code Style

go to top

If you’ve seen CMakeLists.txt files from projects around the web, you’ll notice that there’s a whole mix of styles being used, some with lowercase function names, some with uppercase, and a lot more.

Here’s a very rough code style guide that I like to use, and that is used in this tutorial:

  • Keywords and function names are all lowercase

    • Eg. if, for, message, etc.
  • CMake function and macro arguments are all UPPERCASE

    • Eg. message(STATUS "message body here...)
  • Indentation is in spaces, and each level of indent is 2 spaces

  • You may split function arguments into multiple lines, but do so indented

    • Eg.

      # Split
      message(STATUS
        1
        2
        3
        4
        5
      )
      
      # Not split
      message(STATUS 1 2 3 4 5)
  • And finally, string variables should be wrapped in double-quotes

    • Eg.

      # Do this
      message("Rawr")
      
      # Don't do this
      message(Rawr)

3.2 Minimal CMakeLists.txt

go to top

But before we can even begin, if you’d like to test your code, you need to write CMakeLists.txt files. So let’s just start with an example of how a minimal CMakeLists file that builds code.

The bare minimum CMakeLists that does meaningful work involves three things

  • CMake requirements
  • Project name
  • Build targets

But actually, if you just want to test the basic programming stuff in CMake, you can have an empty CMakeLists.txt file with just the statements to test (in most cases!) It won’t build anything, but it’s good enough.

But to be safe, at least include the cmake_minimum_required() statement. Lots of statements require it.

So suppose our file structure was as such

minimal_example
-	build/
-	src/
	- example.cpp
-	CMakeLists.txt

Then the corresponding CMakeLists.txt will look like this

cmake_minimum_required(VERSION 2.6)
project(minimal_example)

add_executable(Example src/example.cpp)

So we target src/example.cpp for compilation into the Example executable. Very simple!

(In this care the executable is only made of one translation unit. You can add more after the first to link them together)

You can also compile libraries, and link them! So for example, in some other directory…

cmake_minimum_required(VERSION 2.6)
project(another_example)

add_library(WOW somelib.cpp)
target_include_directories(WOW include) # Specifying the header locations

add_executable(Example src/example.cpp)
target_link_libraries(Example WOW)

As we move on, you’ll notice that more and more stuff will get added, but usually only after the project() call. But just keep this basic structure in mind.

Also, it is important to notice that in this case, we had to build the WOW library before linking it to the executable.

This is another example of the importance of the order of statements inside a CMakeLists.txt file! So in this case it is necessary to run add_library() before add_executable().

3.3 Comments

go to top

# This is a comment
# It's nothing special

3.4 Printing

go to top

General syntax

message([<mode>] "message to display" ...)

Where the modes are:

Name Description
(None) Important Info
STATUS Status Info
WARNING Warning Info
AUTHOR_WARNING Warning for Devs
SEND_ERROR Error, skip generation, but continue processing
FATAL_ERROR Error, stop processing and generation
DEPRECIATION Warning only if CMAKE_ERROR_DEPRECIATED or CMAKE_WARN_DEPRECIATED is enabled

There are also a couple others in the more recent versions of CMake, but at the time of writing the tutorial those versions don’t come shipped in an easily installable distribution of CMake.

Name Description
VERBOSE Detailed messages enabled by setting CMAKE_VERBOSE_MAKEFILE to ON
DEBUG Debug messages enabled by calling CMake with --debug-output

Example usage

1565761777925

message("HI!")
message(STATUS "This is a status message!")
message(WARNING "RAWR. BIG WARNS.")
message(FATAL_ERROR "Time to skedaddle out of this compilation script.")
message("This message shouldn't be printing")

Nice! Multi-line works within the quotation marks too!

(And of course, the backslash escapes characters.)

1565761892848

message(" ______
< Wow! >
 ------
          \           
           \   .     .
            .  |\-^-/|  .    
           /| } O.=.O { |\  
          /' \ \_ ~ _/ / '\
        /' |  \-/ ~ \-/  | '\
        |   |  /\\ //\  |   | 
         \|\|\/-""-""-\/|/|/
                 ______/ /
                 '------ 
")

Furthermore, subsequent arguments are just concatenated together

1565764747746

3.5 Variables

go to top

# Set variables
set(VARIABLE_NAME "value")
set(WOW_NUM 10)

# Or even as a list!
# Lists are just semi-colon delimited strings!
set(SOME_LIST "a" "b")
set(EQUIVALENT_LIST "a;b") # These are the same!

set(NUM_LIST 1 2)
set(EQUIVALENT_NUM_LIST 1;2)

# You can even append to the lists!
set(NUM_LIST ${NUM_LIST} 3 4) # So-so way
list(APPEND NUM_LIST 5 6) # Better way

# Use variables
message("WOW_NUM: ${WOW_NUM}")
message("EQUIVALENT_NUM_LIST: ${EQUIVALENT_NUM_LIST}")

# And yes, variables can be called even within a string declaration!
# Escape the characters if you don't want this to happen
# Like so: message("EQUIVALENT_NUM_LIST: ${EQUIVALENT_NUM_LIST}")

Output

1565764814617

Note: Variable names are case sensitive!

Additionally, there are some pre-defined environment or CMake variables that are set automatically! You can access them by using them in much the same way. The full list of environment variables will be put below.

But here’s an example call.

# Example CMake variable call
message(${CMAKE_MAJOR_VERSION})

# Environment variable set and get
set(ENV{variable_name} 10)
message($ENV{variable_name})

3.6 Variable Scope and Directories

go to top

img

Image Source

Variables are created in the scope they are set in. And scopes are defined by directories and functions.

Directories!?

Yes! Directories! Since each CMakeLists.txt file governs its directory, so treat them modularly like that

So just think of C++ scopes, and you should be fine.

Luckily, there is a way to state that a variable is set in its parent scope, instead of its current scope.

# Just use the PARENT_SCOPE argument!
set(PARENT_SCOPED_VAR 10 PARENT_SCOPE)

3.7 User Input: Cache Variables

go to top

CMake uses a cache, which is really just a text file called CMakeCache.txt to remember settings so you don’t have to restate them when running CMake.

Normal variables aren’t placed in the cache unless you explicitly tell CMake to though! Adding a variable as a cache variable also exposes them in the command line. If you don’t set them in the command line, they’ll use their default value, so it’s a good way to create settable variables with default values!

Listing Cache Variables

You can list out all the available settable cache variables from the command line!

Just use these commands:

cmake -L # List all non-advanced cache variables
cmake -LA # List all cache varialbes (including advanced ones)
cmake -LAH # List all cache variables, and also display help for them

Setting Cache Variables in the Command Line

Same as setting variables in the command line.

cmake -DCACHE_VAR_NAME=rawr

# Or with type hints
cmake -DTYPED_CACHE_VAR_NAME:STRING=raa

Setting Cache Variables

# General call
set(<variable> <value>... CACHE <type> <docstring> [FORCE])

The available types are

Name Description
BOOL ON or OFF
FILEPATH Path to a file on disk
PATH Path to a directory on disk
STRING Line of text
INTERNAL Line of text (does not show up inside cmake-gui)

Important: Variables will only go into the cache if it doesn’t exist yet in the cache. So command line cache variable setting will override these set() calls. Unless, of course, you use the FORCE argument.

Additionally: Normal variables bound to the same name as a cache variable will be deleted when the cache variable is set.

Example usage

# Example call, no force
# In this case, 10 will be CACHE_VAR's default value, if it isn't set in the command line
set(CACHE_VAR 10 CACHE STRING "Here's some description of the var...")

# Example call, force
# In this case, FORCED_VAR will overwrite whatever was stated in the command line
set(FORCED_VAR 10 CACHE STRING FORCE)

Recall:

You can set variables into the cache like so

# In this case we are setting the variable: VAR_NAME
cmake . -DVAR_NAME=10

# You can also do it with an explicit typing!
cmake.  -DVAR_NAME:STRING=10

3.8 User Input: Options

go to top

These are just boolean cache variables that you can set ON or OFF in the command line.

Listing Options

If you want to use options, you can use the same cache variable interface.

You can list out all the available settable cache variables from the command line!

Just use these commands:

cmake -L # List all non-advanced cache variables
cmake -LA # List all cache varialbes (including advanced ones)
cmake -LAH # List all cache variables, and also display help for them

Setting Options in the Command Line

Same as setting variables in the command line.

Setting Options

option(OPTION_NAME "SOME_RANDOM_DOCSTRINGS" ON) # OPTION_NAME is default ON

# You can also set a dependent option!
# This one defaults to ON 
# If and only if ON_ME_BEING_ON is ON and AND_ON_ME_BEING_OFF is OFF
CMAKE_DEPENDENT_OPTION(DEP_OPTION "I'm dependent!!" ON
                       "ON_ME_BEING_ON;NOT AND_ON_ME_BEING_OFF" OFF)

3.9 Prefixes

go to top

You can simulate data types or structures by using prefixes. Like: METHYL_A, METHYL_B, METHYL_C

The cool thing is you can resolve variables when within the declaration of the name of a variable! So it’s one very easy way to state prefixes!

set(PREFIX "methyl")

set(${PREFIX}_A 1)
set(${PREFIX}_B 2)
set(${PREFIX}_C 3)

# These generate: methyl_A, methyl_B, and methyl_C
# Which contain: 1, 2, and 3 respectively

3.10 Conditionals

go to top

if(CONDITION)
  # Do stuff
elseif(CONDITION)
  # Otherwise, do stuff
else()
  # Or, do this if everything else fails
endif()

Cool! You can control program flow depending on whether stuff evaluates TRUE or FALSE.

Boolean Constants

Named constants are case-insensitive.

True False
1 0
ON OFF
YES NO
TRUE FALSE
Y N

Additionally, the following are evaluated to false:

IGNORE, NOTFOUND, an empty string, or strings that end with the suffix «-NOTFOUND»

Example Usage

set(VAR TRUE)

if(VAR)
  message("Wow")
else()
  message("Oh no")
endif()

# This should message "Wow"

Wait a sec. Why wasn’t VAR encapsulated by ${}?

The if() statement is a little weird in this regard, because it takes in both variables and constants! In this case, if we wrapped VAR and passed it in as if(${VAR}), the if statement evaluates it as the string «TRUE» as opposed to the boolean constant TRUE.

Logic

You can do logic with the expressions that go into if()!

Here’s a handy list. But for the full list please check the reference

# Logical Operations
if(NOT <expression>) # Negation
if(<expression_1> AND <expression_2>) # AND
if(<expression_1> OR <expression_2) # OR

if( (<expression_1> AND <expression_2>) OR <expression_3> ) # Parentheses

# Comparisons
if(<expression_1> LESS <expression_2>) # Less than
if(<expression_1> GREATER <expression_2>) # Greater than
if(<expression_1> EQUAL <expression_2>) # Equals to

# Existence Checking
if(DEFINED <variable>) # Check if variable is defined
if(EXISTS <path>) # Check if path exists

# Misc
if(<string> MATCHES regex) # Regex matching

3.11 Loops

go to top

For Loop

1565773573410

Notice that in this case, loop_var is each successive element in the list passed into the for loop.

cmake_minimum_required(VERSION 3.10)

set(LIST_VAR 1 2 3)

foreach(loop_var ${LIST_VAR})
  message("CURRENT_LIST_ELEMENT: ${loop_var}")
endforeach()

You can also pass in individual elements, of course! So, these two code snippets have equivalent outputs

cmake_minimum_required(VERSION 3.10)

foreach(loop_var 1 2 3)
  message("CURRENT_LIST_ELEMENT: ${loop_var}")
endforeach()

There’s also alternate syntax, like so

cmake_minimum_required(VERSION 3.10)

set(LIST_VAR 1 2 3)

foreach(loop_var IN LISTS LIST_VAR)
  message("CURRENT_LIST_ELEMENT: ${loop_var}")
endforeach()

For Loop over a Range

1565773727787

cmake_minimum_required(VERSION 3.10)

foreach(loop_var RANGE 10)
  message("CURRENT_LIST_ELEMENT: ${loop_var}")
endforeach()

Or to have a specified start top and a step:

1565773781724

cmake_minimum_required(VERSION 3.10)

foreach(loop_var RANGE 10 20 2) # Start, stop, step
  message("CURRENT_LIST_ELEMENT: ${loop_var}")
endforeach()

While Loop

The while loop conditions are resolved in the same way the if() command resolves conditions.

while(<condition>)
  # do stuff
endwhile()

3.12 Functions

go to top

Define your own functions for convenience!

The signature is function(<NEW_FUNCTION_NAME> [ARG_1 ARG_2 ARG_3...])

1565775582248

cmake_minimum_required(VERSION 3.10)

function(echo_stuff num_argument)
  foreach(i RANGE ${num_argument})
    message("ECHOING: ${i}")
  endforeach()
endfunction()

echo_stuff(10)

Also note that any variables set() within a function is local to its function’s scope, and cannot be accessed outside of the function.

3.13 Macros

go to top

Macros are just like functions, except instead of being called normally, when macros are called, they inline the contents of the macro.

As such, while functions run in their own scope, macros don’t! (Since the variables are inlined directly.)

cmake_minimum_required(VERSION 3.10)

macro(echo_stuff num_argument)
  set(n ${num_argument})
  foreach(i RANGE ${num_argument})
    message("ECHOING: ${i}")
  endforeach()
endmacro()

echo_stuff(10)
message(${n}) # This is legal! Since macros constrain no scopes
                            .     .
                         .  |-^-/|  .    
                        /| } O.=.O { |

Yeah! Buy the DRAGON a COFFEE!

CMake Crash Course — Basics and Scripting

Author: methylDragon
Contains a syntax reference for CMake. We’ll be going through some concepts, the CLI, and scripting with CMake!


Pre-Requisites

Assumed knowledge

  • Have a rudimentary understanding of C/C++
    • Since CMake is used to build C/C++ projects!
  • Understood the linkage and build concepts from this tutorial
  • The tutorial is written with Linux users in mind, specifically Ubuntu
    • But the scripting section should apply in general
  • Recommended: Check out this 15 minute tutorial to familiarise yourself with CMake!

Table Of Contents

  1. Introduction
  2. CMake Concepts and Command Line
    2.1 Installation
    2.2 CMakeLists.txt
    2.3 CMake Build Order
    2.4 Project Structure
    2.5 Invoking CMake
    2.6 Configuring CMake
    2.7 Ordering
  3. CMake Scripting
    3.1 Code Style
    3.2 Minimal CMakeLists.txt
    3.3 Comments
    3.4 Printing
    3.5 Variables
    3.6 Variable Scope and Directories
    3.7 User Input: Cache Variables
    3.8 User Input: Options
    3.9 Prefixes
    3.10 Conditionals
    3.11 Loops
    3.12 Functions
    3.13 Macros

1. Introduction

CMake is Cross-platform Make, where Make is the utility for building programs from source code.

CMake is an extensible, open-source system that manages the build process in an operating system and compiler-independent manner. Unlike many cross-platform systems, CMake is designed to be used in conjunction with the native build environment. Simple configuration files placed in each source directory (called CMakeLists.txt files) are used to generate standard build files (e.g., makefiles on Unix and projects/workspaces in Windows MSVC) which are used in the usual way.

Overview

In other words, CMake is used to generate files that are used with Make to build a project. It’s like a meta-build system. (Though, actually, it can be used with a lot more build pipelines like Visual Studio, XCode, and more!)

Additionally, CMake is more generally used nowadays due to its convenience and simplicity (relative to pure Make), and so, it is far more useful to learn as a build tool. This is especially true since anything you can do in make, you can do in CMake.

When comparing CMake with Make, there are several advantages of using CMake:

  • Cross platform discovery of system libraries.
  • Automatic discovery and configuration of the toolchain.
  • Easier to compile your files into a shared library in a platform agnostic way, and in general easier to use than make.

CMake vs Make

Also note, this tutorial will not serve to be an exhaustive guide on everything CMake, but is designed to be just enough to get you up to speed and able to work somewhat effectively on projects.

Additionally, since CMake is mostly used with C or C++, this tutorial will focus on use of CMake with those languages.

In this section, we’ll go through some concepts, the command line interface, and scripting basics.

2. CMake Concepts and Command Line

Before we can even begin trying to code with CMake, we need to know what it actually does, and how to use it from the command line. So let’s take a look!

2.1 Installation

go to top

Wow! So easy! If this doesn’t work, check your software repositories, or seek out a PPA repository.

2.2 CMakeLists.txt

go to top

CMake is invoked by using a CMakeLists.txt file placed in the root directory of a project (though you can nest projects too!) This file tells CMake what to do and configures the final makefile that is produced, which will then be subsequently used with Make to build your project.

2.3 CMake Build Order

go to top

img

Image Source

CMake first configures itself by checking the CMakeLists.txt file. The configuration should specify the targets for build, their linkages, as well as any build properties or configurations, as well as run any configuration time logic.

Then, it runs that configuration through a generation step to produce a Makefile or some other build pipeline that can be used to actually build your targets.

2.4 Project Structure

go to top

There’s no standardised way to structure a C++ project, but here is a generally acceptable project structure.

This is especially important since CMake will generate a whole bunch of files where the cmake command is invoked. And no one wants generated files clogging up their source directories.

my_project
-	src/
-	include/
-	build/
-	CMakeLists.txt

There are also a couple of additional folder directories that you can put in, and a couple of other files that you can place in your project directory. Here’s a full description of them!

Also, do note that you can nest projects!

Folders

Name Description
src/ Stores source files and private headers
include/ Stores public headers for using the project’s public API
build/ Invoke CMake from within this folder for neatness’ sake, so all CMake-generated files go in here
extern/ External projects go here. Cool thing is that these projects can have their own CMakeLists.txt and you can invoke those files in the parent project’s CMakeLists.txt
doc/ Store documentation
test/ Stores test scripts
data/ Stores files that are used by the executables

Files

Name
CMakeLists.txt Input to CMake build system for building software packages
README.md README for the project. Check out how to write a good one!
LICENSE License for your code

2.5 Invoking CMake

go to top

We’ll assume your project follows the structure detailed in the previous section, and that it already has a preconfigured CMakeLists.txt. As such, we’ll build our project in the build/ directory.

# So, starting in the project directory...

cd build
cmake ..
make

So we:

  • Move into the build/ directory
  • Invoke CMake on the CMakeLists.txt in the project directory to generate the makefile
  • And call Make to build the project based off of the generated Makefile

2.6 Configuring CMake

go to top

You can also configure CMake further with command line arguments though! I can’t go through all of them, but here are a couple of handy ones.

Specify a non-default generator

The default pipeline that is created when CMake is invoked are makefiles. But you can specify a custom one to use!

Just use the -G flag!

cmake --help # See all available generators

cmake -G "GENERATOR_NAME_HERE"
cmake -G "Ninja" # Example

Set Variable (Example: Change Build Type)

Use the -D flag!

So for example, to change the build type,

cmake -DCMAKE_BUILD_TYPE=Debug # Build unoptimised code with debug flags
cmake -DCMAKE_BUILD_TYPE=MinSizeRel # Build optimised code, but at minimal size
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo # Build optimised code with debug flags
cmake -DCMAKE_BUILD_TYPE=Release # Build optimised code with no debug flags

cmake -D CMAKE_BUILD_TYPE=Release # This also works!

Additionally, do note that this is actually the best practice for invoking CMake

View Cached Variables

So you can see what variables were used during the latest invocation of CMake

Setting Build Directory

So, actually, you don’t have to call CMake from inside the build folder because you can actually just specify the build folder to build into using a couple of flags.

So your usual invocation of

Becomes

2.7 Ordering

go to top

You should think of CMake as a kind of scripting language that eventually produces files that are used to command the build of a project. (Because it actually IS a scripting language.)

As such, it is important to keep in mind the fact that the order of calls in the CMakeLists file matters.

3. CMake Scripting

Now that we have a rough understanding of how to use CMake. Let’s look at some basic programming constructs within CMake!

We’ll cover this before the actual build configuration part of CMake because it helps give a good background understanding moving forward, especially once variables and conditions start getting thrown all around.

So do be patient!

3.1 Code Style

go to top

If you’ve seen CMakeLists.txt files from projects around the web, you’ll notice that there’s a whole mix of styles being used, some with lowercase function names, some with uppercase, and a lot more.

Here’s a very rough code style guide that I like to use, and that is used in this tutorial:

  • Keywords and function names are all lowercase

    • Eg. if, for, message, etc.
  • CMake function and macro arguments are all UPPERCASE

    • Eg. message(STATUS "message body here...)
  • Indentation is in spaces, and each level of indent is 2 spaces

  • You may split function arguments into multiple lines, but do so indented

    • Eg.

      # Split
      message(STATUS
        1
        2
        3
        4
        5
      )
      
      # Not split
      message(STATUS 1 2 3 4 5)
  • And finally, string variables should be wrapped in double-quotes

    • Eg.

      # Do this
      message("Rawr")
      
      # Don't do this
      message(Rawr)

3.2 Minimal CMakeLists.txt

go to top

But before we can even begin, if you’d like to test your code, you need to write CMakeLists.txt files. So let’s just start with an example of how a minimal CMakeLists file that builds code.

The bare minimum CMakeLists that does meaningful work involves three things

  • CMake requirements
  • Project name
  • Build targets

But actually, if you just want to test the basic programming stuff in CMake, you can have an empty CMakeLists.txt file with just the statements to test (in most cases!) It won’t build anything, but it’s good enough.

But to be safe, at least include the cmake_minimum_required() statement. Lots of statements require it.

So suppose our file structure was as such

minimal_example
-	build/
-	src/
	- example.cpp
-	CMakeLists.txt

Then the corresponding CMakeLists.txt will look like this

cmake_minimum_required(VERSION 2.6)
project(minimal_example)

add_executable(Example src/example.cpp)

So we target src/example.cpp for compilation into the Example executable. Very simple!

(In this care the executable is only made of one translation unit. You can add more after the first to link them together)

You can also compile libraries, and link them! So for example, in some other directory…

cmake_minimum_required(VERSION 2.6)
project(another_example)

add_library(WOW somelib.cpp)
target_include_directories(WOW include) # Specifying the header locations

add_executable(Example src/example.cpp)
target_link_libraries(Example WOW)

As we move on, you’ll notice that more and more stuff will get added, but usually only after the project() call. But just keep this basic structure in mind.

Also, it is important to notice that in this case, we had to build the WOW library before linking it to the executable.

This is another example of the importance of the order of statements inside a CMakeLists.txt file! So in this case it is necessary to run add_library() before add_executable().

3.3 Comments

go to top

# This is a comment
# It's nothing special

3.4 Printing

go to top

General syntax

message([<mode>] "message to display" ...)

Where the modes are:

Name Description
(None) Important Info
STATUS Status Info
WARNING Warning Info
AUTHOR_WARNING Warning for Devs
SEND_ERROR Error, skip generation, but continue processing
FATAL_ERROR Error, stop processing and generation
DEPRECIATION Warning only if CMAKE_ERROR_DEPRECIATED or CMAKE_WARN_DEPRECIATED is enabled

There are also a couple others in the more recent versions of CMake, but at the time of writing the tutorial those versions don’t come shipped in an easily installable distribution of CMake.

Name Description
VERBOSE Detailed messages enabled by setting CMAKE_VERBOSE_MAKEFILE to ON
DEBUG Debug messages enabled by calling CMake with --debug-output

Example usage

1565761777925

message("HI!")
message(STATUS "This is a status message!")
message(WARNING "RAWR. BIG WARNS.")
message(FATAL_ERROR "Time to skedaddle out of this compilation script.")
message("This message shouldn't be printing")

Nice! Multi-line works within the quotation marks too!

(And of course, the backslash escapes characters.)

1565761892848

message(" ______
< Wow! >
 ------
          \           
           \   .     .
            .  |\-^-/|  .    
           /| } O.=.O { |\  
          /' \ \_ ~ _/ / '\
        /' |  \-/ ~ \-/  | '\
        |   |  /\\ //\  |   | 
         \|\|\/-""-""-\/|/|/
                 ______/ /
                 '------ 
")

Furthermore, subsequent arguments are just concatenated together

1565764747746

3.5 Variables

go to top

# Set variables
set(VARIABLE_NAME "value")
set(WOW_NUM 10)

# Or even as a list!
# Lists are just semi-colon delimited strings!
set(SOME_LIST "a" "b")
set(EQUIVALENT_LIST "a;b") # These are the same!

set(NUM_LIST 1 2)
set(EQUIVALENT_NUM_LIST 1;2)

# You can even append to the lists!
set(NUM_LIST ${NUM_LIST} 3 4) # So-so way
list(APPEND NUM_LIST 5 6) # Better way

# Use variables
message("WOW_NUM: ${WOW_NUM}")
message("EQUIVALENT_NUM_LIST: ${EQUIVALENT_NUM_LIST}")

# And yes, variables can be called even within a string declaration!
# Escape the characters if you don't want this to happen
# Like so: message("EQUIVALENT_NUM_LIST: ${EQUIVALENT_NUM_LIST}")

Output

1565764814617

Note: Variable names are case sensitive!

Additionally, there are some pre-defined environment or CMake variables that are set automatically! You can access them by using them in much the same way. The full list of environment variables will be put below.

But here’s an example call.

# Example CMake variable call
message(${CMAKE_MAJOR_VERSION})

# Environment variable set and get
set(ENV{variable_name} 10)
message($ENV{variable_name})

3.6 Variable Scope and Directories

go to top

img

Image Source

Variables are created in the scope they are set in. And scopes are defined by directories and functions.

Directories!?

Yes! Directories! Since each CMakeLists.txt file governs its directory, so treat them modularly like that

So just think of C++ scopes, and you should be fine.

Luckily, there is a way to state that a variable is set in its parent scope, instead of its current scope.

# Just use the PARENT_SCOPE argument!
set(PARENT_SCOPED_VAR 10 PARENT_SCOPE)

3.7 User Input: Cache Variables

go to top

CMake uses a cache, which is really just a text file called CMakeCache.txt to remember settings so you don’t have to restate them when running CMake.

Normal variables aren’t placed in the cache unless you explicitly tell CMake to though! Adding a variable as a cache variable also exposes them in the command line. If you don’t set them in the command line, they’ll use their default value, so it’s a good way to create settable variables with default values!

Listing Cache Variables

You can list out all the available settable cache variables from the command line!

Just use these commands:

cmake -L # List all non-advanced cache variables
cmake -LA # List all cache varialbes (including advanced ones)
cmake -LAH # List all cache variables, and also display help for them

Setting Cache Variables in the Command Line

Same as setting variables in the command line.

cmake -DCACHE_VAR_NAME=rawr

# Or with type hints
cmake -DTYPED_CACHE_VAR_NAME:STRING=raa

Setting Cache Variables

# General call
set(<variable> <value>... CACHE <type> <docstring> [FORCE])

The available types are

Name Description
BOOL ON or OFF
FILEPATH Path to a file on disk
PATH Path to a directory on disk
STRING Line of text
INTERNAL Line of text (does not show up inside cmake-gui)

Important: Variables will only go into the cache if it doesn’t exist yet in the cache. So command line cache variable setting will override these set() calls. Unless, of course, you use the FORCE argument.

Additionally: Normal variables bound to the same name as a cache variable will be deleted when the cache variable is set.

Example usage

# Example call, no force
# In this case, 10 will be CACHE_VAR's default value, if it isn't set in the command line
set(CACHE_VAR 10 CACHE STRING "Here's some description of the var...")

# Example call, force
# In this case, FORCED_VAR will overwrite whatever was stated in the command line
set(FORCED_VAR 10 CACHE STRING FORCE)

Recall:

You can set variables into the cache like so

# In this case we are setting the variable: VAR_NAME
cmake . -DVAR_NAME=10

# You can also do it with an explicit typing!
cmake.  -DVAR_NAME:STRING=10

3.8 User Input: Options

go to top

These are just boolean cache variables that you can set ON or OFF in the command line.

Listing Options

If you want to use options, you can use the same cache variable interface.

You can list out all the available settable cache variables from the command line!

Just use these commands:

cmake -L # List all non-advanced cache variables
cmake -LA # List all cache varialbes (including advanced ones)
cmake -LAH # List all cache variables, and also display help for them

Setting Options in the Command Line

Same as setting variables in the command line.

Setting Options

option(OPTION_NAME "SOME_RANDOM_DOCSTRINGS" ON) # OPTION_NAME is default ON

# You can also set a dependent option!
# This one defaults to ON 
# If and only if ON_ME_BEING_ON is ON and AND_ON_ME_BEING_OFF is OFF
CMAKE_DEPENDENT_OPTION(DEP_OPTION "I'm dependent!!" ON
                       "ON_ME_BEING_ON;NOT AND_ON_ME_BEING_OFF" OFF)

3.9 Prefixes

go to top

You can simulate data types or structures by using prefixes. Like: METHYL_A, METHYL_B, METHYL_C

The cool thing is you can resolve variables when within the declaration of the name of a variable! So it’s one very easy way to state prefixes!

set(PREFIX "methyl")

set(${PREFIX}_A 1)
set(${PREFIX}_B 2)
set(${PREFIX}_C 3)

# These generate: methyl_A, methyl_B, and methyl_C
# Which contain: 1, 2, and 3 respectively

3.10 Conditionals

go to top

if(CONDITION)
  # Do stuff
elseif(CONDITION)
  # Otherwise, do stuff
else()
  # Or, do this if everything else fails
endif()

Cool! You can control program flow depending on whether stuff evaluates TRUE or FALSE.

Boolean Constants

Named constants are case-insensitive.

True False
1 0
ON OFF
YES NO
TRUE FALSE
Y N

Additionally, the following are evaluated to false:

IGNORE, NOTFOUND, an empty string, or strings that end with the suffix «-NOTFOUND»

Example Usage

set(VAR TRUE)

if(VAR)
  message("Wow")
else()
  message("Oh no")
endif()

# This should message "Wow"

Wait a sec. Why wasn’t VAR encapsulated by ${}?

The if() statement is a little weird in this regard, because it takes in both variables and constants! In this case, if we wrapped VAR and passed it in as if(${VAR}), the if statement evaluates it as the string «TRUE» as opposed to the boolean constant TRUE.

Logic

You can do logic with the expressions that go into if()!

Here’s a handy list. But for the full list please check the reference

# Logical Operations
if(NOT <expression>) # Negation
if(<expression_1> AND <expression_2>) # AND
if(<expression_1> OR <expression_2) # OR

if( (<expression_1> AND <expression_2>) OR <expression_3> ) # Parentheses

# Comparisons
if(<expression_1> LESS <expression_2>) # Less than
if(<expression_1> GREATER <expression_2>) # Greater than
if(<expression_1> EQUAL <expression_2>) # Equals to

# Existence Checking
if(DEFINED <variable>) # Check if variable is defined
if(EXISTS <path>) # Check if path exists

# Misc
if(<string> MATCHES regex) # Regex matching

3.11 Loops

go to top

For Loop

1565773573410

Notice that in this case, loop_var is each successive element in the list passed into the for loop.

cmake_minimum_required(VERSION 3.10)

set(LIST_VAR 1 2 3)

foreach(loop_var ${LIST_VAR})
  message("CURRENT_LIST_ELEMENT: ${loop_var}")
endforeach()

You can also pass in individual elements, of course! So, these two code snippets have equivalent outputs

cmake_minimum_required(VERSION 3.10)

foreach(loop_var 1 2 3)
  message("CURRENT_LIST_ELEMENT: ${loop_var}")
endforeach()

There’s also alternate syntax, like so

cmake_minimum_required(VERSION 3.10)

set(LIST_VAR 1 2 3)

foreach(loop_var IN LISTS LIST_VAR)
  message("CURRENT_LIST_ELEMENT: ${loop_var}")
endforeach()

For Loop over a Range

1565773727787

cmake_minimum_required(VERSION 3.10)

foreach(loop_var RANGE 10)
  message("CURRENT_LIST_ELEMENT: ${loop_var}")
endforeach()

Or to have a specified start top and a step:

1565773781724

cmake_minimum_required(VERSION 3.10)

foreach(loop_var RANGE 10 20 2) # Start, stop, step
  message("CURRENT_LIST_ELEMENT: ${loop_var}")
endforeach()

While Loop

The while loop conditions are resolved in the same way the if() command resolves conditions.

while(<condition>)
  # do stuff
endwhile()

3.12 Functions

go to top

Define your own functions for convenience!

The signature is function(<NEW_FUNCTION_NAME> [ARG_1 ARG_2 ARG_3...])

1565775582248

cmake_minimum_required(VERSION 3.10)

function(echo_stuff num_argument)
  foreach(i RANGE ${num_argument})
    message("ECHOING: ${i}")
  endforeach()
endfunction()

echo_stuff(10)

Also note that any variables set() within a function is local to its function’s scope, and cannot be accessed outside of the function.

3.13 Macros

go to top

Macros are just like functions, except instead of being called normally, when macros are called, they inline the contents of the macro.

As such, while functions run in their own scope, macros don’t! (Since the variables are inlined directly.)

cmake_minimum_required(VERSION 3.10)

macro(echo_stuff num_argument)
  set(n ${num_argument})
  foreach(i RANGE ${num_argument})
    message("ECHOING: ${i}")
  endforeach()
endmacro()

echo_stuff(10)
message(${n}) # This is legal! Since macros constrain no scopes
                            .     .
                         .  |-^-/|  .    
                        /| } O.=.O { |

Yeah! Buy the DRAGON a COFFEE!

Некоторое время назад мы познакомились с Autotools. Несмотря на то, что Autotools до сих пор используется во многих известных проектах с открытым исходным кодом, инструмент этот трудно назвать особо удобным. Кроме того, нормально работает он только в *nix системах, а в каком-нибудь Windows пользоваться Autotools, скажем так, весьма непросто. В общем, Autotools — это легаси, и нормальные программисты в наше время пытаются использовать CMake или, например, SCons. В этой заметке мы познакомимся с CMake.

Говоря простыми словами, CMake — это такая штука, в которой вы описываете проект, а она вам генерирует Makefile’ы в *nix системах, проекты Visual Studio под Windows, файлы конкретных редакторов и IDE, например Sublime Text, Code::Blocks, Eclipse или KDevelop, и так далее. Несмотря на спорный в некоторых моментах синтаксис, в последнее время CMake становится стандартом де-факто в мире C/C++. В частности, CMake используется в LLVM, Qt, MariaDB, Blender, KiCad, GNU Radio и ряде других проектов. Кроме того, в CLion, IDE для C/C++ от компании JetBrains, по умолчанию также создаются проекты, основанные на CMake.

Примечание: В этом контексте вас может заинтересовать заметка Как править в CLion код любых проектов на С++, даже тех, в которых не используется CMake.

Использование CMake в простейшем случае выглядит следующим образом. В корне репозитория создается файл CMakeLists.txt примерно такого содержания:

cmake_minimum_required(VERSION 3.1)

# так пишутся комментарии

project(project_name)

find_library(PTHREAD_LIBRARY pthread)
find_library(PCRE_LIBRARY pcre)

include_directories(include)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED on)
set(CMAKE_CXX_FLAGS «${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror»)

add_executable(main src/Main.cpp src/HttpServer.cpp)

target_link_libraries(main ${PTHREAD_LIBRARY} ${PCRE_LIBRARY})

Хочется надеяться, какая строчка здесь что означает, пояснять не нужно. Затем исходники складываются в каталог src, а заголовочные файлы — в каталог include. Для сборки проекта говорим:

mkdir build
cd build
cmake ..
make

Просто, не правда ли?

Помимо приведенного выше find_library в CMake есть ряд скриптов для подключения конкретных библиотек. В частности, подключение OpenGL осуществляется как-то так:

find_package(OpenGL REQUIRED)

include_directories(${OPENGL_INCLUDE_DIR})

target_link_libraries(main ${OPENGL_LIBRARY} ${CMAKE_DL_LIBS})

CMake можно указать конкретный тип Makefile’ов, которые вы хотите получить на выходе:

cmake -G «Unix Makefiles» ..
cmake -G «MinGW Makefiles» ..
# для просмотра списка всех доступных генераторов:
cmake -G

В частности, многие программисты для ускорения сборки проектов предпочитают использовать Ninja:

cmake -G Ninja ..
ninja -j1

Выбор между отладочной и релизной сборкой осуществляется так:

cmake -DCMAKE_BUILD_TYPE=Release -G Ninja ..
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -G Ninja ..
cmake -DCMAKE_BUILD_TYPE=MinSizeRel -G Ninja ..
# Debug используется по умолчанию
cmake -DCMAKE_BUILD_TYPE=Debug -G Ninja ..

Вместо запуска напрямую make или ninja можно сказать что-то вроде:

cmake —build . —config Release —target main

Можно выбрать конкретный компилятор для сборки проекта

cmake -DCMAKE_C_COMPILER=`which clang`
  -DCMAKE_CXX_COMPILER=`which clang++` -G Ninja ..

… а также указать дополнительные флаги компиляции:

cmake -DCMAKE_C_FLAGS=«-O0 -g» -DCMAKE_CXX_FLAGS=«-O0 -g» ..

Например, для определения степени покрытия кода тестами при помощи lcov нужно сказать что-то вроде:

cmake -DCMAKE_C_FLAGS=«-O0 -g -fprofile-arcs -ftest-coverage»
  -DCMAKE_EXE_LINKER_FLAGS=«-lgcov» ..

В мире C/C++ нередко бывает, что сторонние библиотеки, использующие CMake, подключаются к проекту при помощи сабмодулей Git. Подключение таких библиотек к проекту осуществляется довольно просто:

cmake_minimum_required(VERSION 2.8)

project(c-algorithms-examples)

include_directories(deps/algorithms/include)
add_subdirectory(deps/algorithms/src)

add_executable(rbtree_example rbtree_example.c)
target_link_libraries(rbtree_example CAlgorithms)

В свою очередь, у библиотеки файл src/CMakeList.txt должен быть примерно таким:

cmake_minimum_required(VERSION 2.8)

project(c-algorithms)

add_library(CAlgorithms STATIC
  struct/ilist.c
  struct/rbtree.c
  struct/htable.c
  common/utils.c
)

Вообще, add_subdirectory может принимать путь до любого каталога, в котором есть файл CMakeLists.txt. Это позволяет разбивать проект на подпроекты даже в рамках одного репозитория. Опять же, в случае с библиотеками это позволяет поместить тесты в отдельный подпроект, который не будет собираться при подключении библиотеки в сторонние проекты.

Например, в корне библиотеки CMakeList.txt может быть таким:

cmake_minimum_required(VERSION 2.8)

project(c-algorithms-root)

enable_testing()

include_directories(include)

add_subdirectory(src)
add_subdirectory(test)

Непосредственно тесты добавляются в проект следующим образом:

cmake_minimum_required(VERSION 2.8)

project(c-algorithms-struct-tests)

set(CMAKE_C_FLAGS «${CMAKE_C_FLAGS} -O0 -g»)

add_executable(test_htable test_htable.c)
target_link_libraries(test_htable CAlgorithms)

add_executable(test_rbtree test_rbtree.c)
target_link_libraries(test_rbtree CAlgorithms)

add_test(test_htable «./test_htable»)
add_test(test_rbtree «./test_rbtree»)

Запуск тестов осуществляется простой командой:

make test
# или, с включением отладочного вывода:
make test ARGS=«-V»
# или, если используете Ninja:
ninja test

… выполненной в каталоге build. Если вас интересует тема написания модульных тестов на C++, она более подробно раскрыта в заметке Тестирование кода на C++ с помощью Google Test.

Если же вы используете какой-нибудь PyTest, просто допишите в CMakeList.txt что-то вроде:

find_package(PythonInterp REQUIRED)

enable_testing()

add_test(NAME python_test
         COMMAND py.test —capture=no ${CMAKE_SOURCE_DIR}/tests/run.py)

Вывод тестов пишется в файл Testing/Temporary/LastTest.log. Кстати, подробности о переменных окружения, доступных в CMake, таких, как CMAKE_SOURCE_DIR, можно найти здесь.

Помимо рассмотренных выше возможностей часто можно встретить поддержку сборки проектов с различными опциями. В частности, это используется в Assimp и LLDB. При сборке проекта опции выбираются так:

cmake -DLLDB_DISABLE_CURSES:BOOL=TRUE …
cmake -DASSIMP_BUILD_ASSIMP_TOOLS=OFF …

Опции обычно описывают в документации, но в крайнем случае их можно посмотреть и через curses-интерфейс:

В рамках одного поста, конечно, не представляется возможным рассмотреть все возможности CMake. Однако представленной выше информации вам должно вполне хватить в 90% случаев. Полноценные рабочие примеры использования CMake вы найдете, например, в этом, этом, а также в этом репозиториях на GitHub. Примеры использования опций и условных операторов можно найти в репозиториях уже упомянутых Assimp и LLDB. Ну и, конечно же, массу полезного вы найдете на официальном сайте CMake.

А пользуетесь ли вы CMake и если да, используете ли какие-то его возможности, о которых не было рассказано выше?

Метки: C/C++, Кроссплатформенность.

Понравилась статья? Поделить с друзьями:
  • Как написать cleo скрипт для gta san andreas
  • Как написать christmas cards
  • Как написать case study
  • Как написать call to action
  • Как написать bunnyhop