В прошлой статье Библиотека com_ports я дал краткое описание библиотеки com_ports. Сегодня мы начнём погружение в её недра и подробно поговорим о её сборке и установке. Оба этих действия выполняются с помощью CMake, которому и будет посвящена львиная доля статьи.
Важно: информация приводимая в этой статье актуальна на 6 августа 2026 года. Изменения, внесенные в библиотеку com_ports после этой даты, могут быть не отражены в статье.
Сборка приложений с помощью CMake
При работе с CMake важно понимать, что он не является системой сборки. Его главная задача — подготовка всех необходимых файлов для конкретной системы сборки. На основе этих файлов и осуществляется последующая сборка приложения. Таким образом сборка приложения с помощью CMake состоит минимум из двух этапов.
Первый этап — конфигурирование. На этом этапе CMake создаёт все необходимые для последующей сборки файлы. Но, как он понимает какие именно файлы нужно создать? Для этого используется так называемый списковый файл CMakeLists.txt и генератор [1].
Списковый файл — это файл, содержащий специальные инструкции для CMake. Его задача предоставить CMake всю необходимую информацию о собираемом приложении. Эта информация может включать в себя список файлов с исходными текстами приложения, пути к заголовочным файлам, зависимости, опции компиляции и компоновки, параметры установки и многое другое. Пишет этот файл автор приложения.
Проанализировав списковый файл, CMake задействует генератор. Задача генератора — создать необходимые файлы для конкретной системы сборки (например, make, nmake, Microsoft Visual Studio и т.д.). Генераторы уже входят в состав CMake. Устанавливать их отдельно не нужно.
Вообще этот процесс очень похож на работу компилятора. Сначала на основе исходных текстов программы (в случае CMake это списковый файл) строится абстрактное синтаксическое дерево, на основе которого генерируется двоичный код под конкретную аппаратную платформу (в случае CMake это файлы для конкретной системы сборки).
Чтобы посмотреть список доступных генераторов выполните в терминале команду cmake -help. В выводе этой команды после описания ключей запуска будут перечислены доступные генераторы. Ниже приводится фрагмент вывода этой команды для моей виртуальной машины с Windows на борту.
--help-variables [<file>] = Print cmake-variables manual and exit.
Generators
The following generators are available on this platform (* marks default):
* Visual Studio 17 2022 = Generates Visual Studio 2022 project files.
Use -A option to specify architecture.
Visual Studio 16 2019 = Generates Visual Studio 2019 project files.
Use -A option to specify architecture.
Visual Studio 15 2017 [arch] = Generates Visual Studio 2017 project files.
Optional [arch] can be "Win64" or "ARM".
Visual Studio 14 2015 [arch] = Generates Visual Studio 2015 project files.
Optional [arch] can be "Win64" or "ARM".
Borland Makefiles = Generates Borland makefiles.
NMake Makefiles = Generates NMake makefiles.
NMake Makefiles JOM = Generates JOM makefiles.
MSYS Makefiles = Generates MSYS makefiles.
MinGW Makefiles = Generates a make file for use with
mingw32-make.
Как видите список довольно внушительный. Звёздочкой помечен генератор, который будет использоваться по умолчанию.
Второй этап — сама сборка. Её можно запускать как с помощью CMake так и без него. По сути здесь происходит вызов системы сборки с теми файлами, которые сгенерировал CMake на предыдущем шаге. В результате работы этого этапа на выходе получается скомпилированное приложение.
Могут существовать и другие этапы. Например, установка, тестирование, создание контейнера и др. Тут всё зависит от автора спискового файла (читай автора приложения).
Опции сборки библиотеки com_ports
Списковый файл библиотеки com_ports поддерживает три дополнительные опции конфигурирования. Они перечислены ниже.
COM_PORTS_BUILD_EXAMPLESопределяет нужно ли собирать примеры работы с библиотекой. В состав библиотеки входит несколько примеров работы с ней. По умолчанию они не собираются. Но если задать этот флаг, примеры будут скомпилированы вместе с библиотекой.COM_PORTS_DOCUMENTATIONопределяет нужно ли создать цельdocsдля конвертирования документации к библиотеке в формат HTML. Документация представлена в формате reStructuredText. С помощью программы Sphinx [2] её можно сконвертировать в формат HTML. Именно это и делает цельdocs. Разумеется, чтобы осуществить конвертирование у Вас должна быть установлена программа Sphinx. По умолчанию цельdocsне создаётся.COM_PORTS_INSTALLопределяет нужно ли создать цельinstallдля установки скомпилированной библиотеки в систему. По умолчанию эта цель не создаётся.
В последующих разделах я покажу, как использовать эти опции при сборке библиотеки. Как они работают под капотом будет объяснено в следующих статьях.
Устаревший подход к сборке
В этом и последующих примерах я предполагаю, что Вы находитесь в том же каталоге что и списковый файл библиотеки com_ports. В простейшем случае для её сборки нужно выполнить следующую последовательность команд.
mkdir build cd build cmake -G "NMake Makefiles" .. nmake
В первой строчке создаётся подкаталог build. В нём будет осуществляться сборка библиотеки. Приём с отдельным каталогом сборки удобен тем, что Вы всегда можете легко удалить все промежуточные файлы, созданные во время конфигурирования CMake и во время сборки приложения. Оба этих этапа порождают большое количество файлов. Если в какой-то момент времени Вы захотите переконфигурировать CMake и/или полностью пересобрать приложение, Вам потребуется удалить все временные файлы, созданные ранее на этих этапах. Это сложно и всегда есть риск не удалить файл, который нужно было удалить, или, что еще хуже, удалить файл, который не нужно было удалять. Отдельный каталог сборки решает эту проблему. Если Вы хотите переконфигурировать CMake и/или пересобрать приложение, Вы просто очищаете содержимое каталога сборки. Риск что-то сломать — минимален.
Во второй строчке мы переходим внутрь созданного ранее каталога сборки.
В третьей строчке мы запускаем конфигурирование. С помощью ключа -G указывается желаемый генератор. Две точки в конце команды задают расположение спискового файла, на основе которого и будет осуществляться конфигурирование.
В четвёртой строчке запускается сборка библиотеки. Обратите внимание, мы не вызываем CMake. Мы вызываем nmake [3]. Именно для этой программы были подготовлены необходимые файлы на стадии конфигурирования.
Вообще говоря Вы можете сгенерировать полноценный проект для своей IDE и продолжить работу в ней. Для этого нужно воспользоваться соответствующим генератором.
Если всё сделано правильно, то в текущем каталоге сборки Вы увидите файл скомпилированной библиотеки com_ports.lib (при использовании какого-либо другого генератора, скомпилированный файл библиотеки может находиться в одном из подкаталогов текущего каталога сборки).
Давайте теперь попробуем воспользоваться опциями сборки библиотеки com_ports. Для этого полностью удалим каталог build и выполним следующие команды в терминале.
mkdir build cd build cmake -G "NMake Makefiles" -DCOM_PORTS_BUILD_EXAMPLES=ON -DCOM_PORTS_DOCUMENTATION=ON -DCOM_PORTS_INSTALL=ON .. nmake nmake docs nmake install
Первые две строчки ничем не отличаются. Мы создаём каталог сборки и переходим в него.
В третьей строчке запускается конфигурирование CMake. Как и в прошлый раз мы указываем генератор NMake Makefiles и путь к списковому файлу ... Но, дополнительно к этому мы указываем все опции сборки библиотеки (описывались ранее). Для этого используется ключ -D. Каждой опции присваивается значение ON (то есть истина).
Вообще говоря в CMake для обозначения истины можно использовать следующие литералы: ON, YES, TRUE, Y или любое число отличное от нуля. Для обозначения ложности можно использовать следующие литералы: OFF, NO, FALSE, N, IGNORE, NOTFOUND, ноль или пустую строку [4]. Здесь и далее я буду использовать литералы ON/OFF.
В четвёртой строчке запускается сборка библиотеки. Так как во время конфигурирования мы установили опцию COM_PORTS_BUILD_EXAMPLES, вместе с библиотекой будут скомпилированы и примеры её использования. Они будут находиться в подкаталоге examples текущего каталога сборки.
В пятой строчке запускается сборка документации. Чтобы сборка прошла успешно у Вас должен быть установлен Sphinx [2]. Во время этого процесса программа Sphinx обрабатывает файлы с расширением rst в подкаталоге doc и оформляет их в HTML. Результат этой работы будет находиться в подкаталоге doc текущего каталога сборки. Сформированные таким образом HTML страницы документации можно открыть в любом браузере.
В шестой строчке запускается установка скомпилированной библиотеки и документации к ней. Каталог, в который будет осуществлена установка, зависит от настроек CMake. На моей тестовой машине библиотека по умолчанию устанавливается в каталог c:\Program Files (x86)\com_ports\ (у Вас он может быть другим).
В зависимости от целевого каталога установки для успешного выполнения команды в шестой строчке могут потребоваться права администратора. Поскольку библиотека еще плохо отлажена, будьте осторожны.
Обратите внимание, в последних трёх строчках мы не использовали CMake. Мы использовали только nmake [3]. Объясняется это тем, что CMake уже сделал всю необходимую работу — подготовил все необходимые файлы для nmake (его мы задали посредством генератора) с учетом тех параметров, которые мы указали во время конфигурирования. Всю остальную работу (сборку и установку) nmake может выполнить самостоятельно без помощи CMake.
Главный недостаток такого подхода в том, что мы явно привязываемся к используемой системе сборки (nmake в нашем случае). Можно ли как-то сделать всё тоже самое, но не задумываясь о системах сборки и генераторах? Да, можно.
Более современный подход к сборке
В CMake версии 3.13 [5, раздел Generate a Project Buildsystem] появился другой подход.
Ниже приводится возможная последовательность команд для сборки и установки библиотеки com_ports без явного указания генератора.
cmake -S . -B build -DCOM_PORTS_BUILD_EXAMPLES=ON -DCOM_PORTS_DOCUMENTATION=ON -DCOM_PORTS_INSTALL=ON cmake --build build cmake --build build --target docs cmake --build build --target install
Обратите внимание на следующие изменения (по сравнению с первым подходом)
- Мы нигде не указываем генератор. Во всех командах используется только CMake. При этом мы не заботимся о том, какая система сборки реально используется.
- Мы не создаем каталог сборки. Он будет создан автоматически во время конфигурирования CMake (первая команда).
Рассмотрим кратко ключи, с которыми запускается CMake [5].
-Sзадаёт путь к каталогу, содержащему списковый файл собираемого приложения.-Bзадаёт каталог сборки. Если этого каталога нет, он будет автоматически создан.-Dпозволяет указать дополнительные опции. Мы уже использовали его ранее.--buildзадаёт каталог сборки, из которого читаются подготовленные на этапе конфигурирования файлы. Ключ--buildне используется на стадии конфигурирования. Он используется только на последующих этапах. В этом его главное отличие от ключа-B. Важный нюанс: ключ--buildдолжен задаваться первым в составе команды.--target(или-t) задаёт цель, которую нужно выполнить. В приведенном выше примере мы запускаем на выполнение целиdocs(собрать документацию) иinstall(установить библиотеку). Цели могут быть объединены в одну команду. Тогда пример выше сократится всего до двух строк.
cmake -S . -B build -DCOM_PORTS_BUILD_EXAMPLES=ON -DCOM_PORTS_DOCUMENTATION=ON -DCOM_PORTS_INSTALL=ON cmake --build build --target docs install
Здесь выполнение целей docs и install запускается в одной команде. Сами цели разделяются пробелом.
Зависимость целей
Должен предупредить об одном интересном нюансе. Если попытаться выполнить предыдущую последовательность команд без установки библиотеки, например, вот так
cmake -S . -B build -DCOM_PORTS_BUILD_EXAMPLES=ON -DCOM_PORTS_DOCUMENTATION=ON -DCOM_PORTS_INSTALL=ON cmake --build build --target docs
Сама библиотека и примеры к ней не соберутся. Почему? Потому что здесь мы не запускаем сборку самой библиотеки. Мы запускаем только сборку документации. Давайте вернёмся к полному примеру из начала предыдущего раздела.
cmake -S . -B build -DCOM_PORTS_BUILD_EXAMPLES=ON -DCOM_PORTS_DOCUMENTATION=ON -DCOM_PORTS_INSTALL=ON cmake --build build cmake --build build --target docs cmake --build build --target install
В первой строке осуществляется конфигурирование CMake. В трёх последующих строках запускаются цели сборки. Во второй строке имя цели не указано, поэтому запускается цель по умолчанию (именно в ней и происходит сборка библиотеки). В третьей строке запускается цель docs, в четвёртой — install.
Сборка библиотеки осуществляется потому, что мы явно запускаем её во второй строчке. Хорошо. Почему тогда работает вот это?
cmake -S . -B build -DCOM_PORTS_BUILD_EXAMPLES=ON -DCOM_PORTS_DOCUMENTATION=ON -DCOM_PORTS_INSTALL=ON cmake --build build --target docs install
Мы здесь не запускаем сборку. Мы вызываем только цели docs и install. Почему тогда библиотека собирается? Причина в том, что цель install зависит от цели по умолчанию. Как можно установить то, что еще не скомпилировано? Поэтому система сборки, выполняя цель install, видит, что библиотека еще не собрана, и автоматически запускает её сборку (цель по умолчанию).
Цель docs, в свою очередь, не зависит от цели по умолчанию. Поэтому при её запуске цель по умолчанию не выполняется и библиотека не собирается.
Хорошо, как мне вызвать цель по умолчанию без установки? Можно так.
cmake -S . -B build -DCOM_PORTS_BUILD_EXAMPLES=ON -DCOM_PORTS_DOCUMENTATION=ON cmake --build build cmake --build build --target docs
Как объединить вторую и третью команды в одну? Для этого нужно знать имя цели по умолчанию. Оно может различаться в зависимости от того, какую систему сборки Вы используете. Для Makefile и Ninja это all, для остальных — ALL_BUILD [6]. Таким образом команды сборки принимают вид.
cmake -S . -B build -DCOM_PORTS_BUILD_EXAMPLES=ON -DCOM_PORTS_DOCUMENTATION=ON cmake --build build --target ALL_BUILD docs
Альтернативный способ установки библиотеки
Существует еще один способ запустить установку библиотеки, с помощью ключа --install [5, раздел Install a Project]. Ниже приводится пример использования этого ключа для установки библиотеки com_ports (у Вас он может немного отличатьтся в зависимости от используемого генератора и конфигурации по умолчанию, см. чуть ниже).
cmake -S . -B build -DCOM_PORTS_BUILD_EXAMPLES=ON -DCOM_PORTS_DOCUMENTATION=ON -DCOM_PORTS_INSTALL=ON cmake --build build --target ALL_BUILD docs cmake --install build --config Debug
В первой строчке осуществляется конфигурирование CMake. Во второй строчке запускаются цели: ALL_BUILD (сборка библиотеки) и docs (сборка документации). В третьей строчке запускается установка библиотеки. При этом используется два новых для нас ключа.
--installзапускает установку библиотеки. После этого ключа указывается каталог сборки, в котором устанавливаемая библиотека была предварительно скомпилирована. В отличие от явного вызова целиinstall, зависимые цели автоматически не вызываются. Поэтому библиотека должна быть скомпилирована до использования ключа--install.--configопределяет конфигурацию, для которой осуществляется установка.
Все системы сборки можно разделить на две группы: поддерживающие одну конфигурацию (single configuration), такие как make и Ninja, и поддерживающие несколько конфигураций (multi-configuration), такие как Visual Studio, Xcode, Ninja Multi-Config [7].
Каждая конфигурация задаёт свои параметры сборки (в частности, флаги компиляции и линковки) и другие настройки. По умолчанию в CMake предусмотрены следующие конфигурации:
Debug— отладочная версия.Release— релизная версия, которая обычно отправляется заказчику. Обычно из неё удалена большая часть отладочных символов и проверок.RelWithDebInfo— релизная версия с дополнительной отладочной информацией.MinSizeRel— релизная версия минимального размера.
В случае работы с системой сборки, поддерживающей только одну конфигурацию, требуемую конфигурацию нужно указывать во время конфигурирования CMake с помощью переменной CMAKE_BUILD_TYPE [8]. Изменить её на последующих этапах нельзя.
В случае работы с системой сборки, поддерживающей несколько конфигураций, требуемая конфигурация указывается во время сборки (и на некоторых последующих этапах) с помощью ключа --config. Использование этого ключа во время конфигурирования приводит к ошибке:
CMake Error: Unknown argument --config
Используемая мной по умолчанию система сборки поддерживает несколько конфигураций. И здесь возникает конфликт. При сборке библиотеки по умолчанию используется конфигурация Debug, а при установке ожидается конфигурация Release. Поэтому если я попробую собрать и установить библиотеку без явного указания конфигурации, например, вот так:
cmake -S . -B build -DCOM_PORTS_INSTALL=ON cmake --build build cmake --install build
я получу ошибку вида:
-- Install configuration: "Release" CMake Error at build/cmake_install.cmake:39 (file): file INSTALL cannot find "C:/Users/Sergey/Documents/projects/cpp/com_ports/build/Release/com_ports.lib": File exists
Интересное наблюдение: конфликт возникает только при использовании ключа --install. При явном вызове цели install конфликта нет.
Если у Вас нет разногласий в части использования конфигураций на стадии сборки и установки, то у Вас этой ошибки не будет. Также её не будет при использовании системы сборки, поддерживающей только одну конфигурацию.
Поэтому при установке библиотеки я явно указываю, что устанавливать её нужно из конфигурации Debug, так как она была собрана именно в этой конфигурации. Вообще говоря для предотвращения проблем подобного рода можно явно указывать конфигурацию на всех этапах. Тогда команды сборки и установки примут вид.
cmake -S . -B build -DCOM_PORTS_BUILD_EXAMPLES=ON -DCOM_PORTS_DOCUMENTATION=ON -DCOM_PORTS_INSTALL=ON cmake --build build --target ALL_BUILD docs --config Release cmake --install build --config Release
Здесь на всех этапах явно указывается, что используется конфигурация Release.
Установка в произвольный каталог
По умолчанию приложение или библиотека устанавливаются в системный каталог зависящий от типа системы. Для систем на базе Windows это C:\Program Files, для систем на базе Linux — /usr/local [9]. Если же Вы хотите осуществить установку в произвольный каталог, Вы должны использовать переменную CMAKE_INSTALL_PREFIX. Она задаёт каталог, в который должна быть осуществлена установка. Ниже приводится пример использования этой переменной в случае библиотеки com_ports.
mkdir install_dir cmake -S . -B build -DCOM_PORTS_DOCUMENTATION=ON -DCOM_PORTS_INSTALL=ON -DCMAKE_INSTALL_PREFIX=install_dir cmake --build build --target ALL_BUILD docs --config Debug cmake --install build --config Debug
В первой строке создаётся каталог install_dir. Именно в него мы и будем устанавливать нашу библиотеку.
Во второй строке запускается конфигурирование CMake. В качестве значения переменной CMAKE_INSTALL_PREFIX задаётся путь к созданному ранее каталогу install_dir.
В третьей строке запускается сборка библиотеки и документации к ней.
В четвёртой строке запускается установка библиотеки. Для предотвращения возможных ошибок на этапах сборки и установки явно указывается конфигурация Debug.
Обратите внимание, целевой каталог install_dir указывается только один раз, во время конфигурирования.
Если всё сделано правильно, то в каталоге install_dir Вы увидите файлы скомпилированной библиотеки и документации к ней.
Содержимое каталога установки
Ниже приводится содержимое каталога установки при полной установке библиотеки (с документацией и в отладочной конфигурации).
1.0.0/
cmake/
com_portsConfigVersion.cmake
com_portsConfig.cmake
com_ports_export-debug.cmake
com_ports_export.cmake
doc/
include/
com_ports/
com_ports.hpp
lib/
com_ports.pdb
com_ports.lib
com_portsConfigVersion.cmake
com_portsConfig.cmake
Имя каталога 1.0.0 указывает, что установленная библиотека имеет версию 1.0.0 (официально выпущенных версий пока нет, версионирование будет описано в следующих статьях).
Подкаталог doc содержит документацию к библиотеке в формате HTML. Она устанавливается только в том случае, если документация была собрана перед установкой.
Файл com_ports.hpp это заголовочный файл, который Вы должны использовать в своих приложениях при работе с библиотекой.
Файл com_ports.pdb хранит в себе отладочную информацию о скомпилированной библиотеке [10]. Этот файл используется при отладке библиотеки с помощью Microsoft Visual Studio. Если при сборке библиотеки использовался генератор отличный от Microsoft Visual Studio, то pdb файла может и не быть. Также его не будет если использовалась конфигурация отличная от Debug и RelWithDebInfo.
Файл com_ports.lib это сама скомпилированная библиотека.
Файлы с расширением cmake это вспомогательные скрипты CMake необходимые для того, чтобы другие приложения могли найти библиотеку com_ports. Подробнее об этом в следующих статьях.
Заключение
CMake — очень мощный инструмент, но у него довольно высокий порог входа. Я сам долго не мог подступиться к нему. Но благодаря библиотеке com_ports мне наконец удалось это сделать.
Сегодня я описал только использование CMake для сборки и установки библиотеки. Это лишь малая часть вершины айсберга, но это уже неплохая отправная точка.
В следующих статьях мы продолжим рассматривать библиотеку com_ports и попутно CMake.
Ссылки
- Генераторы в CMake: https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html
- Официальный сайт программы Sphinx: https://www.sphinx-doc.org/ (на территории России без VPN к сожалению не открывается, хотя в списке запрещенных материалов РКН я его не нашел)
- Описание утилиты nmake на сайте компании Microsoft: https://learn.microsoft.com/en-us/cpp/build/reference/nmake-reference
- Условные выражения в CMake: https://cmake.org/cmake/help/v3.3/command/if.html
- Описание ключей запуска CMake: https://cmake.org/cmake/help/latest/manual/cmake.1.html
- Обсуждение имени цели по умолчанию в CMake: https://discourse.cmake.org/t/what-is-the-default-target-when-building-a-project/2341
- Конфигурации в CMake: https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#build-configurations
- Переменная CMAKE_BUILD_TYPE: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
- Переменная CMAKE_INSTALL_PREFIX: https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html
- Описание pdb файлов на сайте Microsoft: https://learn.microsoft.com/en-us/visualstudio/debugger/specify-symbol-dot-pdb-and-source-files-in-the-visual-studio-debugger
