Управление пакетами C/C++ может быть запутанным, но его можно значительно улучшить.

В этой статье я расскажу о проблемах управления пакетами в C/C++ и о том, как Conan может помочь вам решить эти проблемы.

Начните с простого

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

+root
    - mainapp.c
    - CMakeLists.txt

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

Затем мы используем CMake для компиляции проекта в исполняемый файл с именем myproject.

Настроим этот проект:

cmake --build ./build --config Debug --target MyProject -j 10 --

А теперь запустим приложение:

./build/MyProject -a foo -b bar

Результат:

a: foo
b: bar

Это работает, но «работает» не всегда достаточно.
Функция Getops использует глобальные переменные, такие как optarg для хранения связанных значений аргументов, optopt для имени переменной и optind для индекса.

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

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

Еще одна известная проблема getops — это сообщение об ошибке, которое скрыто внутри, что затрудняет перенаправление.

Итак, чтобы решить эту проблему, я решил использовать parg, библиотеку C с открытым исходным кодом для разбора аргументов с безопасностью потоков путем выделения структуры и использования ее для мониторинга состояния анализатора вместо глобальных переменных.

Но как я буду использовать этот код?

Вариант 1. Использовать исходный код

Мы можем использовать gits submodule tool, чтобы клонировать parg в каталог нашего проекта, а затем использовать cmake для его компиляции и связывания с нашим исполняемым файлом.

git submodules add https://github.com/jibsen/parg.git

Это создаст файл .gitsubmodules со следующим содержимым:

[submodule "parg"]
path = parg
url = https://github.com/jibsen/parg.git

Теперь мы можем получить репозиторий с его исходным кодом:

git submodules init

У нас есть весь исходный код parg в каталоге parg.

Теперь нам нужно добавить это в наши CMakeLists.txt конфигурации:

Мы добавили каталог и связали библиотеку с нашим исполняемым файлом.
Теперь мы можем изменить наш код, чтобы использовать библиотеку parg:

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

Давайте скомпилируем:

cmake --build ./build --config Debug --target MyProject -j 10 --

Бегать:

./build/MyProject -a foo -b bar

Результат:

a: foo
b: bar

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

Вариант 2 . Использовать скомпилированную библиотеку

В этом варианте мы скомпилируем parg один раз, сохраним библиотеку, а затем свяжем библиотеку напрямую без повторной компиляции.

После компиляции нашего проекта parg будет создан файл libaprg.a.

Теперь мы можем внести некоторые изменения в CMakeLists и код C, чтобы заставить его работать:

Смейк:

А теперь код C:

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

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

Теперь давайте снова скомпилируем и запустим.

cmake --build ./build --config Debug --target MyProject -j 10 --

Бегать:

./build/MyProject -a foo -b bar

Результат:

a: foo
b: bar

Оно работает! это лучше, но может быть и лучше!

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

Вариант 3. Используйте Конана, чтобы поплакать вслух!

Conan — это менеджер пакетов C\C++ с открытым исходным кодом, разработанный JFrog.
Он использует артефакт JFrog для управления зависимостями.
В основном он используется платформой Программное обеспечение как услуга (SaaS), но также может использоваться как локальный репозиторий для внутренних нужд компании.

В качестве предварительного условия у вас должен быть установлен Python.

Во-первых, нам нужно установить Conan из pip с помощью следующей команды:

pip install conan

Теперь нам нужно создать профиль для наших конфигураций:

conan profile new myprofile

Вывод будет:

Empty profile created: C:\Users\user\.conan\profiles\myprofile

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

[env]
CC=C:/Program Files (x86)/mingw-w64/i686-8.1.0-posix-dwarf-rt_v6-rev0/mingw32/bin/gcc.exe
CXX=C:/Program Files (x86)/mingw-w64/i686-8.1.0-posix-dwarf-rt_v6-rev0/mingw32/bin/g++.exe
[settings]
os_build=Windows
arch_build=x86
os=Windows
arch=x86
compiler=gcc
compiler.version=8
compiler.libcxx=libstdc++11
build_type=Release

Это устанавливает все пути и переменные, необходимые для компиляции проекта.

Установив профиль, я создам conanfile.txt в каталоге проекта.
Глядя на репозиторий parg в Conan center, я могу найти все необходимые мне детали.

[requires]
    parg/1.0.2
[generators]
    cmake

Теперь мой проект зависит от parg, и Конан знает об этом, поэтому он должен быть подготовлен для cmake.

Теперь я могу запустить команду установки Conan:

conan install . -pr=myprofile

Выход:

Configuration:
[settings]
arch=x86
arch_build=x86
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=8
os=Windows
os_build=Windows
[options]
[build_requires]
[env]
CC=C:/Program Files (x86)/mingw-w64/i686-8.1.0-posix-dwarf-rt_v6-rev0/mingw32/bin/gcc.exe
CXX=C:/Program Files (x86)/mingw-w64/i686-8.1.0-posix-dwarf-rt_v6-rev0/mingw32/bin/g++.exe
conanfile.txt: Installing package
Requirements
    parg/1.0.2 from 'conancenter' - Cache
Packages
    parg/1.0.2:a955db98e980a5ab86ae50d6df8bfee361185c27 - Cache
Installing (downloading, building) binaries...
parg/1.0.2: Already installed!
conanfile.txt: Generator txt created conanbuildinfo.txt
conanfile.txt: Generator cmake created conanbuildinfo.cmake
conanfile.txt: Aggregating env generators
conanfile.txt: Generated conaninfo.txt
conanfile.txt: Generated graphinfo

Прохладный!

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

Итак, мы включили информацию о сборке из папки с исходным кодом (она была создана при установке Conan) и связали библиотеку.

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

Разница в том, что теперь мы не засоряем наш код подмодулями git и нам не нужно каждый раз компилировать код.
Точно так же нам больше не нужно самостоятельно управлять зависимостями и артефактами.

Теперь давайте снова скомпилируем и запустим этот код:

cmake --build ./build --config Debug --target MyProject -j 10 --

Бегать:

./build/MyProject -a foo -b bar

Результат:

a: foo
b: bar

Сладкий!