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

Работа с системами сборки - не самая простая задача в мире программной инженерии, особенно это касается разработчиков на C ++, где нет «стандарта» использования того или иного. Но все меняется, и усилия по разработке стандарта или, по крайней мере, набора руководящих принципов положительно влияют на наши повседневные задачи, стремясь упростить нашу жизнь, круто!

Среди систем сборки, доступных для C ++, одна из самых популярных - CMake, подождите! Технически CMake не система сборки, вместо этого это система сборки генератор, в обязанности которой входит создание необходимых файлов, которые будут использоваться самой системой сборки (Make , Ninja, Visual Studio и т. Д.).

Разобраться с CMake оказалось не так просто, как хотелось бы, но набор принципов, вдохновленных современным C ++ и получивший название Modern (или Effective) CMake, помог нам разработать системы сборки, которые проще:

  1. Понимать
  2. Поддерживать
  3. Эволюционировать

Вы можете найти более подробную информацию о концепциях современного CMake в документации CMake и в выступлениях Даниэля Пфайфера на C ++ Now 2017 и Матье Роперт на CppCon 2017, ребята, вы молодцы!

Рассуждения о модулях и зависимостях

Основная идея современного CMake:

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

Какие?

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

К счастью, все изменилось, и теперь нас поощряют думать о модулях, например: в проекте у нас есть исполняемый файл app (модуль), который зависит от библиотеки lib1 (другой модуль), и мы разделяем то, что принадлежит интерфейсу lib1 (что он делает), и то, что принадлежит lib1 ' s реализация (как), более или менее, приложение объектно-ориентированного программирования или, в более общем смысле, модульного программирования.

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

Одним из преимуществ этого подхода является возможность скрыть от внешнего мира, что является частным для модуля, а что является общедоступным и может / должно использоваться его клиентами.

Цель и свойства, помните: цель и свойства

Вы создаете цель, вызывая такую ​​команду (они кажутся конструктором, не так ли?):

  • add_executable
  • add_library

И вы настраиваете цель, но изменяете ее свойства следующим образом (они кажутся сеттерами, не так ли?):

  • target_include_directories
  • target_compile_definitions

Каждая команда (а их так много ...) воздействует на одну цель и может быть сделана ЧАСТНЫМ (используется только модулем), ИНТЕРФЕЙС (используется только клиентами) или PUBLIC (используется модулем и его клиентами). Следовательно, вы можете более детально контролировать свои модули и инкапсулировать их характеристики.

Пример

Чтобы проиллюстрировать концепции, давайте рассмотрим простой пример проекта C ++. Вы можете скачать пример в этом репозитории GitHub: https://github.com/rvarago/modern-cmake-appAndLib.

Наш проект состоит из исполняемого файла с именем app и использует статическую библиотеку с именем lib1, которая находится внутри подкаталога проекта. Чтобы упростить подход, мы не будем устанавливать нашу библиотеку в стандартную папку ОС для библиотек, не будем писать тесты для библиотеки или исполняемого файла, но всегда не забываем писать тесты для вашего программного обеспечения! Кроме того, я, вероятно, не применяю все концепции эффективного современного CMake, но мне трудно достичь хотя бы значительного процента рекомендаций.

Библиотека экспортирует функцию: int sum (const int, const int), которая вычисляет сумму предоставленных аргументов и возвращает это значение. Исполняемый файл просто вызовет эту функцию и распечатает результат на stdout. Довольно просто, просто чтобы сосредоточиться на материалах CMake.

Наш проектный макет:

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

Между тем, файл CMakeLists.txt верхнего уровня содержит базовые настройки для проекта, он определяет минимальную версию CMake и добавляет папки app и libs в качестве подкаталогов путем выдачи команды add_subdirectory.

Единственная цель libs / CMakeLists.txt - добавить папки каждой библиотеки в качестве подкаталогов проекта, поэтому здесь она не будет указываться. Это может быть полезно в сценариях, когда у вас много библиотек и вы хотите, чтобы какая-то функция была включена для всех библиотек, будьте осторожны, чтобы не открывать больше, чем необходимо.

libs / lib1 / CMakeLists.txt определяет цель lib1, которая представляет собой статическую библиотеку, составленную из исходных файлов lib1 путем вызова:

  • add_library (lib1 STATIC $ {lib1_SOURCES})

Кроме того, он включает каталоги включения целевой lib1, вызывая:

  • target_include_directories (lib1 PUBLIC include PRIVATE src)

Обратите внимание, что папка include является PUBLIC и будет использоваться lib1 и ее клиентами, тогда как папка src является PRIVATE и будет использоваться только lib1.

Наконец, app / CMakeLists.txt определяет app, который представляет собой исполняемый файл, составленный из исходных файлов app путем вызова:

  • add_executable (приложение $ {app_SOURCES})

Кроме того, он связывает app с его зависимостью от цели lib1, в данном случае мы решили установить эту зависимость PRIVATE, поэтому ' ll только что использовался целью app и был бы скрыт, если бы у app были клиенты. Это делают:

  • target_link_libraries (приложение PRIVATE lib1)

Посмотрите, насколько проще управлять зависимостями с помощью Modern CMake: простой target_link_libraries достаточно, чтобы связать библиотеку, ее заголовок и все другие, возможно, INTERFACE или PUBLIC транзитивные зависимости, которые требуются библиотеке, довольно круто!

Вывод

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

  1. Какие у меня цели?
  2. Каковы свойства моей цели?
  3. Как моя цель должна взаимодействовать со своими клиентами?

Таким образом, ваш проект будет легче поддерживать, ваши клиенты будут счастливее, если будут проще использовать ваши библиотеки, и все выиграют.

использованная литература

[1] Документация CMake. Https://cmake.org/documentation/

[2] C ++ Now 2017: Дэниел Пфайфер Эффективный CMake. Https://www.youtube.com/watch?v=bsXLMQ6WgIk

[3] CppCon 2017: Матье Роперт Использование современных шаблонов CMake для обеспечения хорошего модульного дизайна. Https://www.youtube.com/watch?v=eC9-iRN2b04

[4] Эффективный современный CMake. Https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1

Сообщение размещено в Личном пространстве Рафаэля Вараго 20 августа 2018 г.