Сборка примеров и приложений, использующих библиотеку com_ports

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

Важно: информация приводимая в этой статье актуальна на 3 сентября 2026 года. Изменения, внесенные в библиотеку com_ports после этой даты, могут быть не отражены в статье.

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

Рассмотрим все эти случаи отдельно.

Сборка примеров совместно с библиотекой

Совместная сборка осуществляется с помощью опции COM_PORTS_BUILD_EXAMPLES. Ниже приводится возможная последовательность команд.

cmake -S . -B build -DCOM_PORTS_BUILD_EXAMPLES=ON
cmake --build build

Обе эти команды и используемые в них флаги описывались в прошлой статье [1].

Если всё сделано правильно, то в текущем каталоге сборки будет создан подкаталог examples. А в нём — подкаталоги, содержащие скомпилированные примеры использования библиотеки.

Для сборки примеров отдельно от библиотеки последняя должна быть предварительно скомпилирована и установлена в систему [1]. Возможны два случая.

  1. Библиотека установлена по пути известному CMake.
  2. Библиотека установлена по пути неизвестному CMake.

Ниже мы рассмотрим оба этих случая.

Также нужно помнить о возможной ошибке компоновки в том случае, если библиотека и пример собираются в разной конфигурации. Например, установленная библиотека была собрана в конфигурации Debug, а пример — в Release. Для решения этой проблемы нужно использовать флаг --config. Более подробная информация об этой проблеме и флаге --config приводилась в прошлой статье [1].

Отдельная сборка примеров. Случай 1

Для сборки примера перейдите в каталог этого примера, содержащий списковый файл CMakeLists.txt, и выполните в нём уже известные нам команды:

cmake -S . -B build
cmake --build build

Если всё сделано правильно, то в подкаталоге build (или в одном из его подкаталогов в зависимости от используемой системы сборки) будет находиться скомпилированный файл примера с расширением exe.

Отдельная сборка примеров. Случай 2

Если библиотека com_ports была установлена в какой-либо пользовательский каталог, то CMake может её не найти. В этом случае конфигурирование примера завершится ошибкой. Ниже приводится пример сообщения об ошибке.

CMake Error at C:/Users/Sergey/Documents/projects/cpp/com_ports/cmake/build_example.cmake.in:23 (find_package):
  By not providing "Findcom_ports.cmake" in CMAKE_MODULE_PATH this project
  has asked CMake to find a package configuration file provided by
  "com_ports", but CMake did not find one.

  Could not find a package configuration file provided by "com_ports"
  (requested version 1.0.0) with any of the following names:

    com_portsConfig.cmake
    com_ports-config.cmake

  Add the installation prefix of "com_ports" to CMAKE_PREFIX_PATH or set
  "com_ports_DIR" to a directory containing one of the above files.  If
  "com_ports" provides a separate development package or SDK, be sure it has
  been installed.

В этом же сообщении CMake подсказывает нам, как исправить ошибку. Нам нужно с помощью переменной CMAKE_PREFIX_PATH [2] указать каталог, в который ранее была установлена библиотека com_ports. Тогда команды сборки примут вид:

cmake -S . -B build -DCMAKE_PREFIX_PATH=../../install_dir
cmake --build build 

В этом примере «../../install_dir» — это относительный путь к каталогу, в который ранее была установлена библиотека com_ports. У Вас он, скорее всего, будет другим.

Разбор спискового файла примера

Каждый пример имеет свой списковый файл. Но они мало чем отличаются друг от друга. Ниже приводится содержимое спискового файла для примера exception.

cmake_minimum_required(VERSION 3.15)

set(EXAMPLE_NAME exception_example)

project(${EXAMPLE_NAME} LANGUAGES CXX)

include("../../cmake/build_example.cmake.in")

В первой строке вызывается функция cmake_minimum_required [3]. Она устанавливает минимально допустимую версию утилиты CMake для проекта (примера). В нашем случае минимальная версия — 3.15.

В третьей строке объявляется переменная EXAMPLE_NAME. Ей присваивается значение exception_example [4]. Вообще, единственное чем отличаются списковые файлы примеров друг от друга это значением переменной EXAMPLE_NAME.

В пятой строке объявляется сам проект [5]. Здесь указывается, что проект имеет имя, заданное в переменной EXAMPLE_NAME, и что проект написан на языке C++. Как говорится в статье «CMake и C++ — братья навек» [6], CMake по умолчанию ищет два компилятора: для языка С и для языка C++. С помощью опции LANGUAGES мы указываем, что нам нужен только компилятор для языка C++. Компилятор для языка C нам не нужен.

В седьмой строке мы загружаем и выполняем содержимое файла build_example.cmake.in [7]. Именно в нём находится вся основная логика по сборке примеров.

Разбор файла build_example.cmake.in

Файл build_example.cmake.in мы будем разбирать по частям. С его полным содержимым можно ознакомиться в репозитории библиотеки com_ports [8].

set(CMAKE_CXX_STANDARD 17)

Указываем, что для сборки нашего приложения необходим стандарт C++17 [9].

set(CMAKE_CXX_STANDARD_REQUIRED ON)

Указываем, что требование стандарта C++17 является обязательным. Без установки переменной CMAKE_CXX_STANDARD_REQUIRED в значение истина, CMake может использовать при сборке более старый стандарт, так как воспринимает значение переменной CMAKE_CXX_STANDARD как рекомендацию, а не как жесткое требование [10].

set(CMAKE_CXX_EXTENSIONS OFF)

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

if(MSVC)
    add_compile_options(/W4 /WX)
endif()

Проверяем использование компилятора от компании Microsoft [12]. Если это так, то устанавливаем флаги компилятора: /W4 (максимальный уровень предупреждений) и /WX (воспринимать предупреждения компилятора как ошибки) [13]. Для установки флагов компилятора используется функция add_compile_options [14].

if(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang)$")
    add_compile_options(-Wall -Wextra -pedantic -Werror)
endif()

Здесь делается тоже самое, что и в предыдущей части но для компиляторов GCC и Clang.

К сожалению для компиляторов GCC и Clang нет отдельной переменной (по аналогии с MSVC) поэтому пришлось использовать переменную CMAKE_CXX_COMPILER_ID [15] для получения идентификатора компилятора и функцию MATCHES [16] для сравнения полученного идентификатора с ожидаемыми типами компиляторов.

add_executable(${EXAMPLE_NAME} main.cpp)

Объявляем, что в результате сборки нашего проекта должен получиться исполняемый файл с именем, хранящимся в переменной EXAMPLE_NAME [17]. Как Вы помните, эта переменная была объявлена в списковом файле примера, в который загружается разбираемый нами файл build_example.cmake.in.

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

string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" is_top_level)

Сравниваем переменные CMAKE_SOURCE_DIR [18] и CMAKE_CURRENT_SOURCE_DIR [19] друг с другом. Если их значения равны друг другу, то в переменную is_top_level будет записано значение истина, в противном случае — ложь. Зачем это нужно?

Как Вы уже видели, примеры могут собираться во время сборки библиотеки com_ports и отдельно от неё. Мне нужно как-то различать эти ситуации. Для этого и будет использоваться переменная is_top_level.

Разница между переменными CMAKE_SOURCE_DIR и CMAKE_CURRENT_SOURCE_DIR хорошо объясняется в обсуждении на StackOverflow [20]. В нём говорится:

CMAKE_SOURCE_DIR is where cmake was originally invoked, and CMAKE_CURRENT_SOURCE_DIR is where cmake is currently working. For instance, if you use add_subdirectory command to include a dependency to your project, the dependency will have its own CMAKE_CURRENT_SOURCE_DIR but CMAKE_SOURCE_DIR will remain the same.

или в моём переводе на русский язык:

CMAKE_SOURCE_DIR содержит каталог, в котором был вызван CMake. CMAKE_CURRENT_SOURCE_DIR — каталог, в котором CMake работает в настоящее время. Например, если вы используете команду add_subdirectory для добавления зависимости к вашему проекту, добавленная зависимость будет иметь своё значение CMAKE_CURRENT_SOURCE_DIR, но значение CMAKE_SOURCE_DIR будет тем же, что и у основного проекта.

При сборке примеров совместно с библиотекой com_ports, первые добавляются в основной проект библиотеки с помощью функции add_subdirectory [21]. Поэтому мы и видим разницу в значениях переменных CMAKE_SOURCE_DIR и CMAKE_CURRENT_SOURCE_DIR.

Синтаксис используемой команды сравнения строк можно посмотреть в документации CMake [22].

Этот приём с переменной is_top_level я позаимствовал из статьи Антона Пантюхина «Руководство по CMake для разработчиков C++ библиотек» [23] (раздел «Определение проекта»).

if(is_top_level)
    find_package(com_ports 1.0.0 REQUIRED)
    target_link_libraries(${EXAMPLE_NAME} com_ports::com_ports)
else()

Если значение переменной is_top_level равно истина, значит пример собирается независимо от библиотеки. В этом случае библиотека должна быть установлена в системе. Для её поиска вызывается функция find_package [24]. В параметрах этой функции указывается, что нужно найти пакет com_ports версии 1.0.0 (или совместимой с этой версией). Ключевое слово REQUIRED говорит о том, что пакет com_ports обязательно должен быть найден. Если он не найден, то произойдет ошибка конфигурирования.

После того как пакет com_ports найден, с помощью функции target_link_libraries [25] указывается, что собираемый пример должен быть скомпонован (слинкован) с найденной библиотекой com_ports.

else()
    target_include_directories(${EXAMPLE_NAME} PRIVATE
        "${CMAKE_BINARY_DIR}/include")
    target_link_directories(${EXAMPLE_NAME} PRIVATE "${CMAKE_BINARY_DIR}")
    target_link_libraries(${EXAMPLE_NAME} com_ports)
endif()

Если значение переменной is_top_level равно ложь, значит пример собирается совместно с библиотекой. В этом случае библиотека может быть не установлена в системе. Мы должны компоноваться с собираемой библиотекой. Но как её найти? find_package здесь нам не поможет.

К счастью существует переменная CMAKE_BINARY_DIR [26]. Она содержит путь к каталогу сборки, для которого был запущен CMake. Поскольку пример собирается во время сборки самой библиотеки, переменная CMAKE_BINARY_DIR будет содержать путь к каталогу сборки библиотеки. Это нам и нужно. Всё остальное — дело техники.

С помощью функции target_include_directories [27] указывается каталог, в котором нужно искать заголовочный файл библиотеки com_ports. Это аналог ключа -I в некоторых компиляторах.

С помощью функции target_link_directories [28] указывается каталог, в котором нужно искать статические библиотеки для их компоновки с примером. Это аналог ключа -L в некоторых компиляторах.

С помощью уже знакомой нам функции target_link_libraries [25] указывается, что собираемый пример должен быть скомпонован с библиотекой com_ports.

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

Итак, мы полностью разобрали списковый файл примеров, которые идут в составе библиотеки. Теперь предлагаю написать своё простейшее приложение, которое будет использовать библиотеку com_ports.

Простейшее приложение

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

Первым делом создадим основной файл приложения main.cpp вот с таким содержимым.

#include <com_ports/com_ports.hpp>

#include <iostream>

int main()
{
    std::wcout << L"COM ports:\n\n";

    auto ports = com_ports::get_names();

    for(const auto &port : ports)
        std::wcout << port << L'\n';

    return 0;
}

Здесь мы просто вызываем функцию com_ports::get_names и выводим в консоль полученный список COM портов.

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

Возможны два уже известных нам случая: библиотека com_ports установлена по пути известному CMake или же она установлена по пути неизвестному CMake. Рассмотрим оба этих случая.

Списковый файл. Случай 1

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

cmake_minimum_required(VERSION 3.15)

project(my_project)

add_executable(my_project main.cpp)

find_package(com_ports REQUIRED)

target_link_libraries(my_project com_ports::com_ports)

Все эти функции рассматривались выше.

Списковый файл должен располагаться в том же каталоге, что и файл main.cpp. (Да, для серьезного проекта это неправильно, но здесь мы рассматриваем простейший пример).

Далее в консоли выполняем уже знакомые нам команды.

cmake -S . -B build
cmake --build build --config Release

У Вас они могут отличаться в зависимости от используемой системы сборки (подробности по этим командам и флагам приводились в моей прошлой статье [1]).

Если всё сделано правильно, можно запустить приложение и увидеть результат его работы.

>.\build\Release\my_project.exe
COM ports:

COM3
COM4

Команда запуска примера и результат его работы у Вас могут быть другими в зависимости от используемой системы сборки и наличия COM портов в системе.

Списковый файл. Случай 2

Если библиотека com_ports была установлена по нестандартному пути, то CMake может её не найти, тогда наш пример не сконфигурируется. Мы уже сталкивались с этой проблемой ранее. Решение тоже — использовать переменную CMAKE_PREFIX_PATH [2].

В простейшем случае её можно явно задать при конфигурировании и сборке примера. Тогда последовательность команд примет вид

cmake -S . -B build -DCMAKE_PREFIX_PATH=../com_ports/install_dir
cmake --build build --config Debug

Чтобы не указывать её каждый раз при сборке приложения, её можно прописать в списковом файле примера. Тогда списковый файл примет вид.

cmake_minimum_required(VERSION 3.15)

project(my_project)

add_executable(my_project main.cpp)

list(APPEND CMAKE_PREFIX_PATH "../com_ports/install_dir")

find_package(com_ports REQUIRED)

target_link_libraries(my_project com_ports::com_ports)

Обратите внимание, мы добавили всего одну строчку с вызовом функции list [29, 30]. По сути мы просто добавили наш каталог к списку каталогов в переменной CMAKE_PREFIX_PATH.

Дальнейшие шаги аналогичны случаю установки библиотеки по стандартному пути.

cmake -S . -B build
cmake --build build --config Debug

Сборка без CMake

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

Ниже приводится пример содержимого файла Makefile для сборки приложения с помощью системы сборки nmake [31] и компилятора MSVC.

all: main.exe

main.exe: main.cpp
	cl main.cpp /EHsc /MDd /I "../com_ports/install_dir/1.0.0/include" /link /LIBPATH:"../com_ports/install_dir/1.0.0/lib" com_ports.lib advapi32.lib setupapi.lib

Ради экономии места я не буду описывать назначение всех ключей.

Создав этот файл, нужно открыть терминал Command Prompt for VS, перейти в каталог с примером и выполнить в нём команду

nmake

Если всё сделано правильно, в текущем каталоге появится собранный исполняемый файл.

Во втором примере мы будем использовать систему сборки Make и компилятор GCC, входящий в состав MSYS2 [32]. Ниже приводится содержимое соответствующего Makefile.

all: main.exe

main.exe: main.cpp
	g++ main.cpp -o main.exe -I"../com_ports/install_dir/1.0.0/include" -L"../com_ports/install_dir/1.0.0/lib" -lcom_ports -lsetupapi

Создав этот файл, нужно выполнить в терминале команду

make

Если всё сделано правильно, в текущем каталоге появится собранный исполняемый файл.

Заключение

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

Во-первых, мы разобрали как собрать примеры, входящие в состав библиотеки.

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

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

Ссылки

  1. Моя статья «Сборка и установка библиотеки com_ports средствами CMake»: https://norseev.ru/2026/08/06/building-and-installing-com_ports-library-by-cmake/
  2. Переменная CMAKE_PREFIX_PATH: https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html
  3. Функция cmake_minimum_required: https://cmake.org/cmake/help/latest/command/cmake_minimum_required.html
  4. Функция set: https://cmake.org/cmake/help/latest/command/set.html
  5. Функция project: https://cmake.org/cmake/help/latest/command/project.html
  6. Статья «CMake и C++ — братья навек»: https://habr.com/ru/articles/461817/
  7. Функция include: https://cmake.org/cmake/help/latest/command/include.html
  8. Файл build_example.cmake.in в репозитории библиотеки com_ports: https://gitflic.ru/project/sergey_norseev/com_ports/blob?file=cmake/build_example.cmake.in&branch=master
  9. Переменная CMAKE_CXX_STANDARD: https://cmake.org/cmake/help/latest/variable/CMAKE_CXX_STANDARD.html
  10. Переменная CMAKE_CXX_STANDARD_REQUIRED: https://cmake.org/cmake/help/latest/variable/CMAKE_CXX_STANDARD_REQUIRED.html
  11. Переменная CMAKE_CXX_EXTENSIONS: https://cmake.org/cmake/help/latest/variable/CMAKE_CXX_EXTENSIONS.html
  12. Переменная MSVC в CMake: https://cmake.org/cmake/help/latest/variable/MSVC.html
  13. Опции компилятора MSVC: https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options?view=msvc-170
  14. Функция add_compile_options: https://cmake.org/cmake/help/latest/command/add_compile_options.html
  15. Переменные CMAKE_<LANG>_COMPILER_ID: https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html
  16. Условные выражения в CMake: https://cmake.org/cmake/help/latest/command/if.html
  17. Функция add_executable: https://cmake.org/cmake/help/latest/command/add_executable.html
  18. Переменная CMAKE_SOURCE_DIR: https://cmake.org/cmake/help/latest/variable/CMAKE_SOURCE_DIR.html
  19. Переменная CMAKE_CURRENT_SOURCE_DIR: https://cmake.org/cmake/help/latest/variable/CMAKE_CURRENT_SOURCE_DIR.html
  20. Обсуждение отличия CMAKE_CURRENT_SOURCE_DIR от CMAKE_SOURCE_DIR: https://stackoverflow.com/questions/53195186/difference-between-cmake-current-source-dir-cmake-source-dir-variables
  21. Функция add_subdirectory: https://cmake.org/cmake/help/latest/command/add_subdirectory.html
  22. Функции работы со строками: https://cmake.org/cmake/help/v3.2/command/string.html
  23. Статья Антона Пантюхина «Руководство по CMake для разработчиков C++ библиотек»: https://habr.com/ru/articles/683204/
  24. Функция find_package: https://cmake.org/cmake/help/latest/command/find_package.html
  25. Функция target_link_libraries: https://cmake.org/cmake/help/latest/command/target_link_libraries.html
  26. Переменная CMAKE_BINARY_DIR: https://cmake.org/cmake/help/latest/variable/CMAKE_BINARY_DIR.html
  27. Функция target_include_directories: https://cmake.org/cmake/help/latest/command/target_include_directories.html
  28. Функция target_link_directories: https://cmake.org/cmake/help/latest/command/target_link_directories.html
  29. Функция list: https://cmake.org/cmake/help/latest/command/list.html
  30. Обсуждение вопроса установки значения переменной CMAKE_PREFIX_PATH внутри спискового файла: https://stackoverflow.com/questions/55120611/cmakelist-set-cmake-prefix-path
  31. Описание утилиты nmake на сайте компании Microsoft: https://learn.microsoft.com/en-us/cpp/build/reference/nmake-reference?view=msvc-170
  32. Официальный сайт проекта MSYS2: https://www.msys2.org/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *