Forschen

Conan Paketmanager für C/C++


Beschäftigt man sich intensiver mit der Entwicklung von C-/C++-Programmen, so gibt es immer mal wieder den Fall, dass man externe Bibliotheken verwenden möchte. Findet die Entwicklung nur auf einem System statt, ist es in diesem Fall meist noch einfach, sich seine Abhängigkeiten mit dem Packetmanager des Systems zu installieren. Aber spätestens wenn eine CI im Einsatz ist, für mehrere verschiedene Architekturen und Zielsysteme gebaut wird oder die abhängigen Bibliotheken gepatched werden müssen, wird es knifflig.

Im Prinzip müsste dann jeder Entwickler (und die CI) alle Abhängigkeiten immer selbst bauen, was fehleranfällig sein kann. Auf einen System baut etwas nicht, weil andere Compiler verwendet werden, usw.

An dieser Stelle empfiehlt es sich, etwas Zeit in die integration einer brauchbaren Lösung zu investieren. Bis vor einiger Zeit haben wir hier auf eigene Skripte zurückgegriffen, mittlerweile gibt es aber in der Open-Source-Welt eine Handvoll Projekte, die diese Skripte überflüssig machen können. Eines davon ist conan, welches wir hier im Folgenden kurz vorstellen wollen.


Was ist conan?

Conan ist ein Paket-Manager für C/C++. Pakete schnüren sich aus fein konfigurierbaren Rezepten (oder besser “Bauanleitungen” für klassische C-/C++-Bibliotheken), Header- und pkg-config Informationen, sowie auch vorkompilierten Bibliotheken oder Programmen, die dann für den Entwickler direkt nutzbar sind. Pakete können in öffentlichen (z.B. Conan Center, Bincrafters) oder selbst gehosteten Repositorien (z. B. Artifactory und gitlab) vorgehalten werden - den so genannten remotes.

Wie ein Paket kompiliert werden soll, entscheiden Profile. Neben systemspezifischen Informationen - wie z. B. Compiler-Versionen und Architektur - entscheiden diese auch, ob eine bestimmte cmake- oder configure-Option beim Erstellen der Library gesetzt sein soll. Conan Center und Bincrafters bieten eine mitterlweile überwältigende Anzahl von vorkompilierten Binaries und Paketrezepten, in denen viel Wissen über die Kompilierung der jeweiligen Software steckt.

Letztendlich sind die vorkompilierten Binaries “ganz nett”, allerdings passen sie in der Realität meist nicht auf die doch mitunter speziellen Konfigurationsbedürfnisse der Projekte, sodass eigene Kopien von Rezepten und Binary-Repositories die Regel sein dürften.

Hat man alle gewünschten Pakete beisammen, werden diese in einer conanfile.txt (oder dynamisch in einer conanfile.py) Datei abgelegt und mit dem eigenen Projekt verteilt. Diese Datei liefert die Eingabe für Generatoren, die jeweils wissen wie z. B. eine cmake-Datei oder eine qmake-Datei mit den in den Paketen gelieferten Informationen gebaut werden kann. Sie wissen also z. B. um die ganzen -I... oder -l... Flags und wo sie angegeben werden müssen.

Ist das eigene Projekt dann passend konfiguriert, kann es wie gewohnt gebaut werden. Alle durch conan definierten Abhängigkeiten werden entsprechend berücksichtigt.


Installation

Wir beschäftigen uns im Folgenden mit Linux- bzw. Unix-ähnlichen Systemen. Informationen zur Installation, bzw. der Installation auf anderen Systemen können in der offiziellen Installationsdokumentation nachgelesen werden.

Soll immer die aktuellste Version von Conan (Python 3 erforderlich!) installiert sein, so ist pip der bevorzugte Weg:

pip3 install --user conan

Möchte man aktualisieren, so ist nach dem install noch ein --upgrade einzufügen. Bei der obigen Variante ist sicherzustellen, dass die mittels --user installierten Daten auch im PATH verfügbar sind. Auf unseren Systemen ist das z. B. ~/.local/bin/conan, auf MacOS sind die Binaries versionsabhängig meist unter Verzeichnissen wie /Users/user/Library/Python/3.8/bin zu finden.


Erste Einrichtung

Wie eingangs erwähnt, arbeitet conan mit Profilen. Ein Standardprofil kann man mit dem folgenden Befehl erstellen und damit gleich überprüfen, ob die Installation funktioniert:

$ conan profile new --detect default
Found gcc 10
Found clang 10.0
gcc>=5, using the major as version

************************* WARNING: GCC OLD ABI COMPATIBILITY ***********************
 
Conan detected a GCC version > 5 but has adjusted the 'compiler.libcxx' setting to
'libstdc++' for backwards compatibility.
Your compiler is likely using the new CXX11 ABI by default (libstdc++11).

If you want Conan to use the new ABI for the default profile, run:

    $ conan profile update settings.compiler.libcxx=libstdc++11 default

Or edit '/home/cajus/.conan/profiles/default' and set compiler.libcxx=libstdc++11

************************************************************************************


Profile created with detected settings: /home/cajus/.conan/profiles/default

Grunsätzlich hat das schon mal geklappt, allerdings warnt uns das (Linux) System nun, dass eine rückwärtskompatible ABI des Compilers verwendet wird. Einstellungen kann man über das conan Tool erledigen. Wir sind ja ‘modern’, daher probieren wir direkt die vorgeschlagene Profiländerung aus:

conan profile update settings.compiler.libcxx=libstdc++11 default

Diese aktualisiert die Einstellung von settings.compiler.libcxx im Profile default auf libstdc++11, was direkt überprüft werden kann:

$ cat /home/cajus/.conan/profiles/default
[settings]
os=Linux
os_build=Linux
arch=x86_64
arch_build=x86_64
compiler=gcc
compiler.version=10
compiler.libcxx=libstdc++
build_type=Release
[options]
[build_requires]
[env]

Das Profil ist also eine Datei im ini-Stil und deutet bereits an, dass es noch weitere Sektionen mit Konfigurationsmöglichkeiten gibt. Dazu später mehr.


Remotes

Zur initialen Konfiguration fehlen nun unter Umständen noch Remotes, also die Repositories, die Rezepte/Binärdaten für Pakete enthalten. Schauen wir zunächst einmal, ob Remotes voreingestellt sind:

$ conan remote list
WARN: Remotes registry file missing, creating default one in /home/cajus/.conan/remotes.json
conan-center: https://conan.bintray.com [Verify SSL: True]

Remotes werden also offensichtlich in der remotes.json hinterlegt und das conan-Skript hat uns (ungefragt) bereits ein Remote für das Conan Center angelegt. Da wir im weiteren Verlauf ein Paket benötigen, welches im Conan Center nicht verfügbar ist, fügen wir nun noch ein weiteres Remote für Bincrafters hinzu:

$ conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan

Nun haben wir zwei Remotes: default und bincrafters

Möchte man ein privates Repository hinzufügen, so kann man für einzelne Remotes auch Benutzer und Passwort festlegen. Der folgende Befehl ist ein Beispiel und würde für das Remote bincrafters den Benutzernamen klaus und das Passwort secret setzen. Funktioniert natürlich so nicht, daher wie gesagt nur exemplarisch:

conan user -p secret -r bincrafters klaus

Bei einigen conan-Unterkommandos kann angegeben werden, welches Remote verwendet werden soll.


Pakete

Pakete sind - wie bereits erwähnt - eine Sammlung von Informationen rund um eine Bibliothek, ein Programm oder auch eine Toolchain (wie z. B. ein Android NDK oder Emscripten). Pakete haben einen Namen, eine Version, sind einem Namespace zugeordnet und sind Teil eines Channels. Die Notation ist wie folgt:

paketname/version@namespace/channel

oder als Beispiel:

qt/5.15.1@gonicus/stable

Letzteres beschreibt Qt in der Version 5.15.1 und wurde von uns für den Channel stable selbst gebaut.

Sucht man ein bestimmtes Paket, kann man das Unterkommando search verwenden:

$ conan search bzip2 --remote conan-center
Existing package recipes:

bzip2/1.0.6
bzip2/1.0.6@conan/stable
bzip2/1.0.6@conan/testing
bzip2/1.0.8
bzip2/1.0.8@conan/stable

Informationen zu einem der gefunden Paketen lassen sich über das Unterkommando info abfragen:

$ conan info bzip2/1.0.8@conan/stable
bzip2/1.0.8@conan/stable: Not found in local cache, looking in remotes...
bzip2/1.0.8@conan/stable: Trying with 'conan-center'...
Downloading conanmanifest.txt completed [0.16k]                                          
Downloading conanfile.py completed [2.14k]                                               
Downloading conan_export.tgz completed [0.75k]                                           
Decompressing conan_export.tgz completed [0.00k]                                         
bzip2/1.0.8@conan/stable: Downloaded recipe revision 0
bzip2/1.0.8@conan/stable
    ID: 91a8b22c2c5a149bc617cfc06cdd21bf23b12567
    BuildID: None
    Remote: conan-center=https://conan.bintray.com
    URL: https://github.com/conan-community/conan-bzip2
    Homepage: http://www.bzip.org
    License: bzip2-1.0.8
    Author: Conan Community
    Description: bzip2 is a free and open-source file compression program that uses the Burrows–Wheeler algorithm.
    Topics: conan, bzip2, data-compressor, file-compression
    Recipe: Downloaded
    Binary: Missing
    Binary remote: conan-center
    Creation date: 2019-08-20 11:27:12

Weitere Informationen liefert unter anderem inspect. Interessant für den nachfolgenden Punkt sind die options, die den Build des Paketes beeinflussen:

$ conan inspect -a options -r bincrafters bzip2/1.0.8@conan/stable
options:
    build_executable: [True, False]
    fPIC: [True, False]
    shared: [True, False]

Möchte man vorkompilierte Binaries verwenden, lohnt sich noch ein Blick auf die Standard-Einstellungen, die mittels -a default_options abgefragt werden können:

$ conan inspect -a default_options -r conan-center bzip2/1.0.8@conan/stable
default_options:
    build_executable: True
    fPIC: True
    shared: False

Die gefundenen Pakete können dann im eigenen Projekt referenziert werden. Das Erstellen bzw. Anpassen von Paketen liegt nicht im Fokus dieses Artikels.


Profile

Neben den eigentlichen Paketen ist deren gewünschte Konfiguration wichtig, da Projekte bestimmte Anforderungen haben. So sollen Bibliotheken vielleicht nur mit den benötigten Features übersetzt oder es soll ein statischer Build erzeugt werden. All dies kann durch eine Erweiterung des Basisprofils erreicht werden:

include(default)

[options]
bzip2:build_executable=False
bzip2:fpic=True
bzip2:shared=False

Dieses Beispiel inkludiert das default-Profil und stellt unsere Anforderungen an den bzip2-Build: wir wollen keine bzip2-Programmdateien, einen statischen Build als libbz2.a und eine Konfiguration mit fpic.

Baut man sein Projekt mit dieser Konfiguration, würde bzip2 mit der passenden Konfiguration erstellt, um dann zur Verfügung zu stehen. Wie so ein “Bauvorgang” aussieht, schauen wir uns im Folgenden anhand des aus früheren Posts bekannten Qt-/Qml-Projektes an.


Nutzung im Projekt

Als Basis soll für diesen Artikel das vorbereitete Mini-Programm dienen. Es kann wie folgt bezogen und danach in das Projektverzeichnis gewechselt werden:

git clone https://github.com/gonicus/font-demo.git
cd font-demo

Der nun folgende Vorgang benötigt viel Zeit, da im Verlauf Qt kompiliert wird.


Remotes

Da es sich um ein Qt-Projekt handelt, muss letzteres natürlich installiert sein. Dies kann man über den Paketmanager des Betriebsystems erreichen oder über die von The Qt Company bereitgestellten Builds. In diesem Fall benutzen wir aber von conan bereitgestellte Qt-Pakete aus dem bincrafters-Projekt. Falls noch nicht geschehen, muss das bincrafters-Repository hinzugefügt werden:

conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan


conanfile.txt

Im ersten Schritt müssen wir auf oberster Ebene unseres Projektes eine Datei mit dem Namen conanfile.txt anlegen. Diese enthält die Abhängigkeiten unseres Projektes - also zu allererst Qt. Den genauen Namen erfahren wir durch einen search-Aufruf:

$ conan search qt -r bincrafters
Existing package recipes:

qt/5.9.8@bincrafters/stable
qt/5.11.3@bincrafters/stable
qt/5.12.0@bincrafters/stable
qt/5.12.1@bincrafters/stable
qt/5.12.2@bincrafters/stable
qt/5.12.3@bincrafters/stable
qt/5.12.4@bincrafters/stable
qt/5.12.5@bincrafters/stable
qt/5.12.6@bincrafters/stable
qt/5.12.7@bincrafters/stable
qt/5.12.8@bincrafters/stable
qt/5.12.9@bincrafters/stable
qt/5.13.0@bincrafters/stable
qt/5.13.1@bincrafters/stable
qt/5.13.2@bincrafters/stable
qt/5.14.0@bincrafters/stable
qt/5.14.1@bincrafters/stable
qt/5.14.2@bincrafters/stable
qt/5.15.0
qt/5.15.0@bincrafters/stable
qt/5.15.1@bincrafters/stable

Von Interesse ist natürlich die neueste Version - hier also qt/5.15.1@bincrafters/stable - welche direkt in der Datei conanfile.txt eingetragen wird:

[requires]
qt/5.15.1@bincrafters/stable

Qt besitzt ein eigenes Werkzeug zum Erstellen von Makefiles, welches wir hier benutzen wollen: qmake. Damit conan die notwendigen Compile- und Linker-Flags korrekt aufbereiten kann, müssen wir noch den gewünschten Generator konfigurieren:

[requires]
qt/5.15.1@bincrafters/stable

[generators]
qmake

Mit dieser conanfile.txt Datei können wir nun conan damit beauftragen, uns die für die Abhängigkeiten notwendigen Rezepte zu beschaffen. Allerdings kann es sein, dass wir noch bestimmte Optionen für den Bau von Qt benötigen: wir möchten eine Qml-Anwendung erstellen, die auf jeden Fall qtdeclarative benötigt.

Schauen wir uns einmal an, welche Optionen das Paket qt/5.15.1@bincrafters/stable bietet:

$ conan inspect -a default_options -r bincrafters qt/5.15.1@bincrafters/stable
default_options:
    GUI: True
    commercial: False
    config: None
    cross_compile: None
    device: None
    multiconfiguration: False
    opengl: desktop
    openssl: True
    qt3d: False
    qtactiveqt: False
    qtandroidextras: False
    qtcharts: False
    qtconnectivity: False
    qtdatavis3d: False
    qtdeclarative: False
    qtdoc: False
    qtgamepad: False
    qtgraphicaleffects: False
    qtimageformats: False
    qtlocation: False
    qtlottie: False
    qtmacextras: False
    qtmultimedia: False
    qtnetworkauth: False
    qtpurchasing: False
    qtqa: False
    qtquick3d: False
    qtquickcontrols: False
    qtquickcontrols2: False
    qtquicktimeline: False
    qtremoteobjects: False
    qtrepotools: False
    qtscript: False
    qtscxml: False
    qtsensors: False
    qtserialbus: False
    qtserialport: False
    qtspeech: False
    qtsvg: False
    qttools: False
    qttranslations: False
    qtvirtualkeyboard: False
    qtwayland: False
    qtwebchannel: False
    qtwebengine: False
    qtwebglplugin: False
    qtwebsockets: False
    qtwebview: False
    qtwinextras: False
    qtx11extras: False
    qtxmlpatterns: False
    shared: True
    sysroot: None
    widgets: True
    with_doubleconversion: True
    with_fontconfig: True
    with_freetype: True
    with_glib: True
    with_harfbuzz: True
    with_icu: True
    with_libalsa: False
    with_libjpeg: True
    with_libpng: True
    with_mysql: True
    with_odbc: True
    with_openal: True
    with_pcre2: True
    with_pq: True
    with_sdl2: True
    with_sqlite3: True
    with_vulkan: False
    with_zstd: True

Das ist wahrlich eine Flut von Optionen… Schaut man genauer hin, fällt aber ein qtdeclarative: False ins Auge. Das bedeutet, dass die vorkompilierten Binaries nicht für uns geeignet sind, weil sie qtdeclarative deaktiviert haben. Wir müssen also noch etwas tun, bevor unser erster Projekt-Build von Erfolg gekrönt ist.


Profil anpassen

Profile sind uns ja bereits eingangs begegnet. Neben Kommandozeilen-Optionen des conan-Skriptes stellen sie an dieser Stelle eine Möglichkeit dar, die Parametrierung für den Qt-Build anzupassen - und zwar über die oben bereits erwähnte [options]-Sektion.

Erstellen wir die Datei custom.profile:

include(default)

[options]
qt:qtdeclarative=True

Zunächst haben wir unser default-Profil inkludiert und dann die Option qt:qtdeclarative überschrieben. Vor dem Doppelpunkt findet sich der Paketname, dahinter der Optionsname.

Neben der Einstellung von Paketoptionen, kann man in Profilen auch Toolchains installieren. Diese können notwendig sein, um die Software auf bestimmten Platformen zu bauen (z.B. benötigt man für Android andere Compiler-Einstellungen, als für das Host-System).

Verwenden wir nun das custom.profile zum Erzeugen unseres Projektes unter Linux, wird ein passendes Qt für uns erzeugt.


Projekt erstellen

Möchten wir das Projekt erstellen, müssen wir vor dem gewohnten Erstellungsvorgang conan anweisen unsere Abhängigkeiten bereitzustellen. Dies geschieht mit dem Kommando:

conan install --build=missing --profile custom.profile .

conan lädt nun unser eben erstelltes Profil custom.profile und schaut im aktuellen Verzeichnis (.) nach der Datei conanfile.txt (bzw. conanfile.py für dynamische Konfigurationen). Mit diesen Informationen fängt es nun an, für das aktuelle Profil alle Abhängigkeiten von Qt sowie Qt selbst zu erstellen. Lässt man das --build=missing weg, wird der Build fehlschlagen, da es keine vorgebauten Pakete mit unserer Konfiguration gibt.

Wer schon einmal Qt kompiliert hat weiss, dass nun ein guter Zeitpunkt für eine ausgiebige Kaffepause ist…

Es kann sein, dass conan System-Abhängigkeiten nachinstallieren möchte (z. B. bestimmte X11-Header). Tut man das nicht, wird der Build zwangsläufig fehlschlagen.

Nachdem der Build abgeschlossen ist, können wir einen Blick in das aktuelle Verzeichnis werfen und sehen einige von conan erzeugte Dateien. Interessant für uns ist conanbuildinfo.pri, welche vom qmake-Generator angelegt wurde. Hier steht alles was zum Kompilieren notwendig ist. Sagen wir es qmake, indem wir am Anfang unserer Projektdatei ein include(conanbuildinfo.pri) einfügen:

include(conanbuildinfo.pri)

QT += quick gui

CONFIG += c++11 qmltypes

SOURCES += \
        IconFont.cpp \
        main.cpp

RESOURCES += qml.qrc

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =

# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =

QML_IMPORT_NAME = Demo
QML_IMPORT_MAJOR_VERSION = 1

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

HEADERS += \
    IconFont.h

Nun gibt es noch eine letzte Qt- und conan-spezifische Hürde zu nehmen: Um ein Qt-Projekt bauen zu können, muss man qmake aufrufen - und zwar das richtige. Dieses befindet sich jedoch in dem conan-Paket, dessen Pfad wir momentan noch nicht kennen.

Über Umwege kommt man an diesen heran:

$ conan info qt/5.15.1@bincrafters/stable --paths --profile custom.profile --only package_folder --package-filter qt/5.15.1@* 
    package_folder: /home/cajus/.conan/data/qt/5.15.1/bincrafters/stable/package/dda3c30f2f7ad8d8a148137d9e6d9aaf9b69bbe9

Es folgt der übliche Build-Prozess mit einem build-Verzeichnis:

mkdir build
cd build
/home/cajus/.conan/data/qt/5.15.1/bincrafters/stable/package/dda3c30f2f7ad8d8a148137d9e6d9aaf9b69bbe9/bin/qmake ..
make

Jetzt haben wir das font-demo gebaut und können es mittels ./demo aufrufen.


QtCreator

Möchte man den Qt Creator mit dieser Qt-Version benutzen, so kann man diese mit Hilfe des oben herausgefundenen package_folder in den Einstellungen hinterlegen und dann in einem Kit zuweisen. Solange man keine Patches für Qt hat, ist die Handhabung momentan aber noch etwas “sperrig”. Allerdings regt sich momentan in der Qt-Community etwas Interesse an conan, sodass dies bald vielleicht einfacher geht.


Fazit

Die Nutzung von conan für die Paketverwaltung fühlt sich zunächst etwas gewöhnungsbedürftig an. Letztendlich kann der oben beschriebene Prozess allerdings eine wichtige Komponente für das erfolgreiche Management größerer Projekte mit vielen Abhängigkeiten und dem Bauen für verschiedene Architekturen sein. Letztere sind (im Falles von Cross-Compiling) nur ein anderes Profil, für das gebaut wird.

Für exotischere Build-Konfigurationen können Anpassungen an den Rezepten notwendig sein. Aber ob man die Zeit nun für einen eigenen Satz Skripte oder für conan aufwendet (und die Änderungen an die Community zurückgibt), spielt eine untergeordnete Rolle. Und je besser die Rezepte in allen Lebenslagen funktionieren, umso mehr profitiert man von der gemeinsamen Arbeit an conan-Rezepten.

×
×

Nehmen Sie Kontakt zu uns auf!

Mit * gekennzeichnete Felder sind Pflichtfelder.

Wir haben Ihre Kontaktanfrage erhalten und melden uns kurzfristig bei Ihnen!

×

Ich möchte digital unabhängig werden!

Vielen Dank für Ihre Mitteilung!