Linux Software Deployment - Verschiedene Wege, um die eigene Software zu verteilen
In diesem Artikel/Tutorial geht es um die verschiedenen Möglichkeiten, Software unter Linux zu verteilen.
Überblick über Deploymentmethoden
Mit Deployment wird die Verteilung von Software beschrieben, also wie der Nutzer an die Software kommt und wie diese installiert und aktualisiert wird.
Deployment via Quelltext
Sehr populär unter Linux ist immer noch das Verteilen von Open-Source-Programmen im Quelltext, den man dann auf dem eigenen System ganz individuell übersetzt und installiert. Da nicht-triviale Programme meist Abhängigkeiten von (vielen) anderen Bibliotheken haben, müssen die Entwickler sehr gut dokumentieren, welche Bibliotheksversionen benutzt werden müssen, wie diese installiert und konfiguriert werden. Das kann manchmal recht mühselig für die Anwender werden, vor allem weil Bibliotheken in verschiedenen Distributionen in unterschiedlichen Versionen ausgeliefert werden (siehe nachfolgender Abschnitt über Paketmanager). Und wenn man dann die zigste abhängige Bibliothek heruntergeladen und compiliert hat, nur um wieder irgendwo einen Compilefehler zu haben, dann nervt das und der geneigte Anwender gibt häufig auf.
Außerdem sind solche direkt compilierten und installierten Programme schlecht zu warten, zu aktualisieren oder zu entfernen. Dafür sind sie meist sehr schlank und eben passgenau für das System. Licht und Schatten eben…
Deployment als Archiv mit vorkompilierten Binaries
Man kann das kompilierte Programm und alle benötigten Ressourcendateien auch in ein Archiv stecken. Dabei gibt es zwei Spielarten:
- Archiv enthält die Zielverzeichnisstruktur des Linuxsystems, sodass das Archiv direkt nach
/usroder/usr/localentpackt werden kann und die ausführbaren Dateien in/usr/binbzw./usr/local/binlanden, als wären sie vom Paketmanager dorthin installiert worden. Die Programme finden die Ressourcen in den Standardsuchpfaden des Ubuntu-Systems. - Das Archiv ist ein “portable”-Archiv, welches irgendwohin installiert werden kann. Üblicherweise liegen alle Dateien/Ressourcen/Binaries des Programms unterhalb des entpackten Programmverzeichnisses (und können so auch einfach wieder entfernt werden). Ob das Archiv systemweit oder im Home-Verzeichnis installiert wird, ist meistens egal. Die Programme finden die Ressourcen über einen relativen Pfad zum Installationsverzeichnis und nicht in den Ubuntu-typischen Standardverzeichnissen. Das ist mitunter problematisch, weil der Start eines Programms nur über den Befehl (wenn der Pfad in der PATH Variable steht) dann kein gültiges Arbeitsverzeichnis liefert, d.h. im Programm selbst ist der Installationspfad gar nicht zu bestimmen, und damit auch nicht die relativen Verzeichnisse der Ressourcen. Aber hier gibt es Workarounds. Weiterer Nachteil ist, dass man Desktop-Verknüpfungen (*.desktop) manuell erstellen oder durch ein Skript generieren lassen muss.
Der Vorteil dieser Methode ist, dass die Download/Installationsgröße absolut minimal ist, und dass nicht zwingend der Quelltext mitgeliefert und übersetzt werden muss. Anwender müssen jedoch die vom Programm benötigten Laufzeitbibliotheken manuell vorher installieren. Und das gelingt natürlich nur, wenn diese (vom Paketmanager gelieferten) Laufzeitbibliotheken auf dem Zielsystem in der richtigen Version vorliegen. Daher müssen Binärpakete stets passgenau für eine Distro und Version erstellt werden (verlangt quasi eine virtuelle Maschine pro Zielsystem zur passgenauen Erstellung der Binärpakete).
Bei der Aktualisierung von im System installierten Binärpaketen (Option 1) ist es bei Aktualisierung schwierig, eventuell nicht mehr benötigte, ältere Binaries/Ressourcen-Dateien zu entfernen. Bei portablen Binärarchiven reicht jedoch in der Regel das Löschen des entpackten Verzeichnisses aus.
Deployment via Distro-Paketmanager
Unter Linux installieren/aktualisieren Anwender Software üblicherweise unter Verwendung eines Paketmanagers: apt bei Ubuntu und Derivaten, dnf bei Fedora etc.
Die Paketverwaltung hat den großen Vorteil, dass von einer Anwendung benötigte Bibliotheken (Dependencies) automatisch mit installiert werden. Außerdem werden von mehreren Programmen gemeinsam benutzte Bibliotheken nur einmal auf der Platte installiert. Das spart viel Platz auf der Platte, Downloadbandbreite und - weil diese Bibliotheken auch nur einmal im Speicher/Cache geladen sind - auch Hauptspeicher. Das ist eines der Vorteile von Linuxsystemen.
Ähnlich wie bei der Verteilung eines vorkompilierten Binärpakets muss auch hier für jede Distrobution/Version ein individuell angepasstes Paket erstellt werden. Das bedingt auch manchmal Anpassungen im Upstream Sourcecode-Repo, da neuere Compilerversionen ggfs. Quelltextanpassungen erfordern oder, falls man LTS Versionen mit 5 Jahre alten Compilern/Bibliotheken unterstützten will, man gegebenenfalls Workarounds für diese älteren Versionen einbauen muss. Insgesamt ist die Pflege von Binärpaketen/Debian-Paketen für mehreren Distributionen/Versionen durchaus zeitaufwändig.
Für die Anwender jedoch ist die Nutzung solcher vom Paketmanager verwalteten Pakete sehr einfach und funktioniert meist fehlerfrei, da die Abhängigkeiten passgenau installiert werden.
Ein elementarer Nachteil ist jedoch, dass eben alle Anwendungen sich mit den in der aktuellen Distributionsversion ausgelieferten Bibliotheksversionen zufrieden geben müssen. Falls eine Anwendung einen Bugfix in einer zentralen Bibliothek benötigt, muss man mit dem Anwendungsupdate erstmal auf das nächste Distroupdate warten.
Deployment in Containerumgebungen
Um es Anwendungen zu ermöglichen, komplette Kontrolle über die Laufzeitumgebung (Biblotheken und Konfigurationsdateien) zu erhalten, und so auch aktualisierte Versionen von abhängigen Bibliotheken auszuliefern, wurden Anwendungscontainer erfunden. MacOS macht das schon seit ewig mit den AppBundles. Nun gibt es unter Linux (auch schon seit etlichen Jahren) verschiedene Containerlösungen, wobei die aus meiner Sicht Wichtigsten sind:
- Snap (nur Ubuntu und Derivate)
- Flatpak
- AppImage
Alle Varianten haben so Ihre Vor- und Nachteile. Snap ist etwas in Ungnade gefallen, weil Ubuntu den Snap-Store mehr oder weniger in den Markt gedrückt hat und weil die Snaps beim Start erstmal entpackt werden müssen, was den Start von Anwendungen zum Teil deutlich verzögert (dafür sind die Snap wegen der Kompression eben kleiner auf der Platte und im Download).
Allen Containerlösungen ist gemein, dass die Verbandelung der Anwendung mit Laufzeitkomponenten eben eine gewisse Redundanz schafft (mehrere Versionen der gleichen Bibliotheken liegen parallel auf der Platte). Man kann zwar größere Abhängigkeiten ebenfalls in Laufzeitcontainer kapseln und diese dann als Abhängigkeit laden, aber wenn man die 5. Variante des Qt-Frameworks mit jeweils 600 Mb auf der Platte liegen hat, dann kann das schon nerven. Diese Platzverschwendung und massiv erhöhte Downloadmenge ist der Hauptnachteil der Containerlösungen.
Im Beispiel unten wird das gut sichtbar. Es wird ein Flatpak eines kleinen Kommandozeilenprogramms erstellt (Größe ca. 5 kB) . Unter Ubuntu 24.04 wird hierfür die üblicherweise bereits installierte Runtime org.freedesktop.Sdk/x86_64/25.08 verwendet. Daher ist beim Deployment auf anderen Ubuntu-Rechnern wirklich nur das kleine 5kB-Flatpak notwendig. Wenn man aber das gleiche Flatpak unter dem aktuellen Fedora installieren will, so muss die gesamte Runtime und deren Abhängigkeiten (ca. 800 Mb) heruntergeladen und installiert werden, da bei Fedora standardmäßig die Version org.freedesktop.Sdk/x86_64/25.07 installiert ist. Das ist schon ein erheblicher Overhead, oder?
Tutorial Teil 1: Traditionelles Deployment ohne Container
Um die einzelnen Wege des Softwaredeployments zu diskutieren, auszuprobieren und zu erläutern habe ich ein minimalistisches Quelltextprojekt erstellt: https://github.com/ghorwin/LinuxDeploymentTest.git.
In diesem Pseudo-Projekt gibt es eine Kommandozeilenanwendung, eine GUI-Anwendung, zu installieren Ressourcendateien und für das GUI-Programm auch noch Icons und einen .desktop-Link. Es handelt sich um ein C++ Projekt unter Verwendung des CMake Buildsystems.
Um das didaktisch etwas sinnvoll zu strukturieren, fangen wir mit einem kleinen Kommandozeilenprogrämmchen an und bauen das ganze Stück für Stück aus. Passend dazu habe ich git-Tags angelegt, die ich im Tutorial referenziere.
Repostruktur und Minimalquelltext
Beginnen wir mit dem minimalistischen Kommandozeilenprogramm deptool, welches lediglich die C++ Laufzeitbibliotheken braucht und minimale Konfiguration voraussetzt. Dazu den tag v1_command_line_tool auschecken und man sieht folgende Repo-Verzeichnisstruktur:
1
2
3
4
5
6
7
8
9
10
11
12
13
<repo-root>
├── build
│ ├── build.sh
│ └── readme.md
├── CMakeLists.txt
├── DepTool
│ ├── CMakeLists.txt
│ ├── doc
│ │ └── deptool.1
│ └── src
│ └── main.cpp
├── LICENSE
└── README.md
Das Verzeichnis build könnte man ignorieren - es ist aber guter Stil, im Repo ein Verzeichnis vorzusehen, wo man out-of-source bauen kann. Für die Bequemlichkeit hab ich dort ein shell-Script abgelegt, sodass man einfach:
1
2
cd build
./build.sh
tippen kann. Aber der Reihe nach…
Das Kommandozeilenprogramm selbst ist im Unterverzeichnis DepTool und besteht effektiv aus 2 Dateien:
DepTool/src/main.cpp- der QuelltextDepTool/doc/deptool.1- die man Page (gehört unter Linux bei Kommandozeilenprogrammen zum guten Ton)
Die Dateien LICENSE und README.md sind ebenfalls nicht von belang.
Wichtig sind aber die cmake Buildsystem-Dateien.
Top-Level CMakeLists.txt
Da es perspektivisch ja mehrere Programme in diesem Repository geben wird (Kommandozeilenprogramm + GUI), gibt es eine übergeordnete CMakeLists.txt, welche die jeweiligen Teilkomponenten einbindet und allgemeine Einstellungen definiert:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Require a fairly recent cmake version
cmake_minimum_required( VERSION 2.8...3.10 )
# The project name
project( LinuxDeploymentTest )
# Uncomment this to enable detailed make output
#set( CMAKE_VERBOSE_MAKEFILE ON )
# on Unix we want really detailed warnings
if (UNIX)
ADD_DEFINITIONS( -Wall -fPIC )
endif (UNIX)
# Set default build type
if (NOT CMAKE_BUILD_TYPE)
set( CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING
"Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE)
endif (NOT CMAKE_BUILD_TYPE)
# The submodules/binaries
add_subdirectory( DepTool )
Diese Datei ist recht minimalistisch gehalten (und wird später noch viel länger werden). cmake_minimum_required() und project() sind Pflicht. Dann kommen ein paar (optionale) Compilerdirektiven unter Linux (nur mal als Beispiel).
Dann kommt ein Block, wo bei nicht vorgegebenem Build-Typ automatisch RelWithDebInfo eingestellt wird.
Wenn man cmake ohne extra Kommandozeilenoptionen startet oder in der cmake-gui den Build-Typ nicht festlegt, wird automatisch Debug gewählt, was häufig ja nicht zielführend für eine veröffentlichte Anwendung ist. Der
if (NOT CMAKE_BUILD_TYPE)sorgt hier für eine sinnvolle Voreinstellung. Man könnte auch Release wählen, aber bei komplexeren Programmen ist das Einbetten von Debug-Symbolen im Falle eines Crashes für die Entwickler doch ganz sinnvoll.
Am Ende der top-level CMakeLists.txt werden noch die Unterverzeichnisse der einzelnen Programme (mit jeweils eigenen CMakeLists.txt-Dateien) eingebunden.
Kommandozeilenprogramm CMakeLists.txt
Die Datei DepTool/CMakeLists.txt definiert nun die Erstellung des kleinen Kommandozeilenprogramms:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# CMakeLists.txt file for command line test tool
# This CMakeLists.txt file is included from the top level master CMakeLists file
project( DepTool )
add_executable( ${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/src/main.cpp )
# we want the binary to be named 'deptool' as linux command line tools
# are usually in lower case
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "deptool")
if (UNIX AND NOT APPLE)
# installation targets for Unix systems
include(GNUInstallDirs)
# DepTool -> /usr/bin/deptool
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
# Man-page -> /usr/share/man/man1/deptool.1.gz
install(FILES ${PROJECT_SOURCE_DIR}/doc/deptool.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
endif (UNIX AND NOT APPLE)
Für das eigentliche Erstellen würden die Zeilen
1
2
project( DepTool )
add_executable( ${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/src/main.cpp )
schon reichen. Die Zeile set_target_properties() könnte man sich auch sparen, wenn man project(deptool) verwenden würde. Ich hab das aber mal so dringelassen, um die Linux Konvention von kleingeschriebenen Kommandozeilenprogrammen zu verdeutlichen.
Wichtig ist aber der ganze Installationsteil. Die if (UNIX AND NOT APPLE) Bedingung ist sinnvoll, wenn man das Programm auch unter Windows und MacOS bauen will. Unter diesen Betriebssystemen gibt es komplett anderen Deploymentmethoden, deshalb sollten die nachfolgenden install-Anweisungen nur für Unix/Linux gelten.
include(GNUInstallDirs) wird benötigt, damit man nachfolgend Platzhalter für Standardinstallationpfade nutzen kann.
WICHTIG: Nun muss man für jede zu installieren Datei eine install()-Anweisung eintragen. Dies ist die Grundvoraussetzung für alle Linux-Deploymentmethoden. Damit man Installationspräfixe wie /usr/bin nicht hardcodieren muss, verwendet man in den install() Anweisungen Platzhalter für die Pfade nach dem DESTINATION Argument.
- Bei kompilierten Anwendungen/Bibliotheken:
install(TARGETS ...) - Die allen anderen (Ressourcen)-Dateien:
install(FILES ...)
Das war’s auch schon. Das Projekt ist vorbereitet für das Softwaredeployment im Quelltext.
Deployment als Quelltext (als “Source-tarball”)
Jetzt, wo die CMakeLists.txt-Dateien mit install-Anweisungen versehen sind, kann man einfach ein Quelltextarchiv erstellen.
Im Wurzelverzeichnis des geclonten Repositories ruft man dazu auf:
1
git archive --format=tar.gz -o LinuxDeploymentTest-1.0.0.tar.gz --prefix=LinuxDeploymentTest-1.0.0/ master
Das letzte Argument gibt den zu verwendenden Branch/Tag an.
Das optionale Argument
--prefixerlaubt die Definition eines top-level Verzeichnisses im Archiv. Ohne dieses Argument würden beim Entpacken des Archivs alle Dateien und Verzeichnisse direkt im aktuellen Arbeitsverzeichnis landen, was beim unvorsichtigen Entpacken das Homeverzeichnis ganz schön zumüllen würde. Daher würde ich empfehlen, diesen prefix immer zu verwenden.
Wenn man ein Quelltextarchiv für den oben besprochenen v1_command_line_tool tag erstellen will, schreibt man einfach:
1
git archive --format=tar.gz -o LinuxDeploymentTest-1.0.0.tar.gz --prefix=LinuxDeploymentTest-1.0.0/ v1_command_line_tool
Dieses Quelltextarchiv kann man nun verteilen. Anwender laden sich das Archiv runter, entpacken das Archiv (z.B. mit tar -xzvf LinuxDeploymentTest-1.0.tar.gz) und legen los:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
g@X1:~/git$ tar -xzvf LinuxDeploymentTest-1.0.0.tar.gz
LinuxDeploymentTest-1.0.0/
LinuxDeploymentTest-1.0.0/.gitignore
LinuxDeploymentTest-1.0.0/CMakeLists.txt
LinuxDeploymentTest-1.0.0/DepTool/
LinuxDeploymentTest-1.0.0/DepTool/CMakeLists.txt
LinuxDeploymentTest-1.0.0/DepTool/doc/
LinuxDeploymentTest-1.0.0/DepTool/doc/deptool.1
LinuxDeploymentTest-1.0.0/DepTool/src/
LinuxDeploymentTest-1.0.0/DepTool/src/main.cpp
LinuxDeploymentTest-1.0.0/LICENSE
LinuxDeploymentTest-1.0.0/README.md
LinuxDeploymentTest-1.0.0/build/
LinuxDeploymentTest-1.0.0/build/.gitignore
LinuxDeploymentTest-1.0.0/build/build.sh
LinuxDeploymentTest-1.0.0/build/readme.md
g@X1:~/git$ mkdir LinuxDeploymentTest-1.0.0-build
g@X1:~/git$ cd LinuxDeploymentTest-1.0.0-build/
g@X1:~/git/LinuxDeploymentTest-1.0.0-build$ cmake ../LinuxDeploymentTest-1.0.0/
-- The C compiler identification is GNU 13.3.0
-- The CXX compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /home/ghorwin/git/LinuxDeploymentTest-1.0.0-build
g@X1:~/git/LinuxDeploymentTest-1.0.0-build$ make
[ 50%] Building CXX object DepTool/CMakeFiles/DepTool.dir/src/main.cpp.o
[100%] Linking CXX executable deptool
[100%] Built target DepTool
g@X1:~/git/LinuxDeploymentTest-1.0.0-build$ sudo make install
[100%] Built target DepTool
Install the project...
-- Install configuration: "RelWithDebInfo"
-- Installing: /usr/local/bin/deptool
-- Installing: /usr/local/share/man/man1/deptool.1
g@X1:~/git/LinuxDeploymentTest-1.0.0-build$
Beim Aufruf von cmake ohne weitere Argumente wird als Installationsprefix /usr/local/ festgelegt. Um dorthin zu installieren, muss man sudo make install verwenden. Direkt danach kann man das Tool in der Kommandozeile mit deptool starten und die Handbuchseite mit man deptool aufrufen.
Um den Basispfad für die Installation mit Cmake zu ändern, ruft man cmake mit dem Optionalen Argument
--install-prefixauf:
1 cmake --install-prefix=/usr ../LinuxDeploymentTest-1.0.0/Nun landed das Programm in
/usr/bin/deptool.
Deployment als Binärarchiv
Nun kann/will man ja nicht immer den Quelltext mitgeben. Man kann natürlich auch die installierten Dateien selbst in ein Archiv stecken. Das ginge über ein Skript, oder man bedient sich der in CMake eingebauten CPack-Lösung.
Man muss dazu nur die Toplevel CMakeLists.txt-Datei minimal erweitern:
1
2
3
4
5
6
7
8
# Versionsnummer hinzufügen
project( LinuxDeploymentTest VERSION 1.0.0 )
...
# CPack-Support
set(CPACK_GENERATOR TGZ) # nur tar.gz generieren
include(CPack)
Nun kann man make package verwenden, welches dann die Datei: LinuxDeploymentTest-1.0.0-Linux.tar.gz erstellt. Das Archiv enthält also genau die Dateien, die bei sudo make install kopiert werden.
Anwender können dieses Binärarchiv nun herunterladen, entpacken und die Dateien direkt in den gewünschten Installpräfix kopieren:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Archiv im lokalen Verzeichnis entpacken
g@X1:~/git$ tar -xzvf LinuxDeploymentTest-1.0.0-Linux.tar.gz
LinuxDeploymentTest-1.0.0-Linux/bin/
LinuxDeploymentTest-1.0.0-Linux/bin/deptool
LinuxDeploymentTest-1.0.0-Linux/share/
LinuxDeploymentTest-1.0.0-Linux/share/man/
LinuxDeploymentTest-1.0.0-Linux/share/man/man1/
LinuxDeploymentTest-1.0.0-Linux/share/man/man1/deptool.1
# systemweite Installation durch Kopieren nach /usr/local
g@X1:~/git$ sudo cp -Rv LinuxDeploymentTest-1.0.0-Linux/* /usr/local
'LinuxDeploymentTest-1.0.0-Linux/bin/deptool' -> '/usr/local/bin/deptool'
'LinuxDeploymentTest-1.0.0-Linux/share/man/man1/deptool.1' -> '/usr/local/share/man/man1/deptool.1'
Kommt effektiv auf das Gleiche hinaus, wie beim Source-Code-Archiv. Das Programm startet aber nur korrekt, wenn die auf dem System vorhandenen Laufzeitbibliotheken exakt der Version entsprechen, für die das Binary erstellt wurde. Letzteres ist in der Praxis sehr schwer sicherzustellen, vor allem bei der Verteilung von Programmen für mehrere Distributionen (und verschiedene Versionen).
Wegen Kompatibilitätsproblemen oder fehlenden Abhängigkeiten ist ein Binärarchiv für Anwender häufig ein Grund zur Ärgernis. Das Programm startet nicht, oder bringt kryptische Fehlermeldungen über fehlende Symbole oder crasht im schlimmsten Fall sogar. Daher würde ich bei dieser Art des Deployments immer eine explizite Dokumentation der Zieldistribution und Version mitliefern (am Besten gleich im Dateinamen des Download-Archivs), sowie einer Info über die vorab zu installieren Laufzeitumgebungen, bspw. über eine Zeile mit
sudo apt install ....
Deployment als Debian-Paket
Ein Debian-Paket ist an sich erstmal nichts weiter als ein ar-Archiv mit den zu installierenden Dateien (wie oben beim Binärarchiv) und einem Verzeichnis DEBIAN mit einer Metadatendatei. Diese Metadaten enthalten Informationen über das Paket (Lizenz, Ursprung, Maintainer, etc.) und ganz wichtig, Abhängigkeiten von anderen Bibliotheken (einschließlich der Versionierung).
Debian-Pakete kann man auf unterschiedliche Art und Weise erstellen. Für das minimalistische Beispiel kann man das auch direkt mit CPack machen. Dazu fügt man an die Top-Level CMakeLists.txt Datei folgende Zeilen hinzu:
1
2
3
4
5
6
7
8
9
10
11
...
# CPack-Support
set(CPACK_PACKAGE_VENDOR "org.ghorwin")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Deployment test tool")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Andreas Nicolai")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE amd64)
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "A test tool for deployment on linux.")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.39), libgcc-s1 (>= 3.3.1), libstdc++6 (>= 13.1)")
set(CPACK_GENERATOR DEB TGZ) # deb und tar.gz generieren
include(CPack)
- Wichtig ist hier die Definition der CPACK Variablen vor der Zeile
include(CPack). - Die Architektur muss angegeben werden, da sonst i386 voreingestellt ist.
- Ebenso wichtig für ein gutes Debian-Paket ist die Angabe der Abhängigkeiten. Genau das ist aber schwierig, denn wo kriegt man raus, welche Pakete und welche Versionen man hier als Abhängigkeiten definieren muss? Das geht auf jeden Fall einfacher und besser, siehe nachfolgend verlinkte Debian-Paket-Erstellung Doku. Im Beispiel hier sind die notwendigen C++ Laufzeitbibliotheken für Ubuntu 24.04 “noble” mit den jeweiligen Versionsnummern angegeben.
Wenn man nach dieser Ergänzung der CMakeLists.txt make package aufruft, wird neben dem Binärpaket (*.tar.gz) auch noch die Datei LinuxDeploymentTest-1.0.0-Linux.deb erstellt. Dieses können Anwender dann mit den Hausmitteln einer Debian-basierten Distribution installieren.
Obwohl man mit CPack recht einfach ein gültiges Debian-Paket erstellen kann, ist die korrekte Bestimmung der Abhängigkeiten zu anderen Bibliotheken schwierig. Auch können wichtige Attribute wie die Lizenz nicht mit CPack gesetzt werden. Und ein automatisches Aktualisieren mit dem Debian Paketmanager apt ist so auch nicht möglich. Besser ist deshalb die Verwendung der offiziellen Debian-Package Tools und das Verwenden eines eigenen Software-Paket-Repositories (ppa). Dies Aufzusetzen ist aber “etwas” aufwändiger.
Das Prozedere für das Erstellen und Hosten von Open-Source Debian-Paketen hab ich mal für mein Programm MasterSim im Detail dokumentiert: Ubuntu Paketerstellung und Veröffentlichung am Beispiel MasterSim.
Tutorial Teil 2: Deployment Kommandozeilenprogramm als Flatpak
Mit den Vorbereitungen aus Abschnitt Kommandozeilenprogramm CMakeLists.txt können wir nun ein Flatpak erstellen. Flatpak ist ein Containerformat, was eigentlich einem Debian-Paket ziemlich ähnlich ist. Im Container liegt das compilierte Programm und weitere zu installierende Dateien und dazu noch Metadaten. Im Unterschied zu einem Debian-Paket wird Start des Programms im Container jedoch nicht gegen die systemweit installierten Laufzeitbibliotheken gelinkt. Stattdessen werden die in den Abhängigkeiten definierten Flatpak-Runtimes benutzt. So kann man beispielsweise ein Flatpak unter Ubuntu 22.04 erstellen, und das läuft dann auf allen nachfolgenden Ubuntu-Releases ohne weitere Anpassungen.
Ähnlich wie beim Debian-Deployment-Prozedere (siehe oben) erwartet Flatpak eine “Upstream”-Datenquelle, zumeist also ein Quelltextrepository. Alternativ kann man auch ein Quelltextarchiv benutzen. Wir werden beide Varianten testen. Vom Workflow ist das aber ähnlich wie bei Debian-Packaging:
- Upstream wird eine neue Version erstellt
- Flatpak wird basierend auf dem geänderten Quelltext generiert und veröffentlicht
Los geht’s!
Vorbereitung Teil 1: Flatpak Builder
Zum Erstellen von Flatpak-Paketen braucht man das Flatpak-Builder - Tool, welches man am besten über die Paketquellen installiert:
1
sudo apt install flatpak-builder
Vorbereitung Teil 2: Installation der Runtime
Das Kommandozeilenprogramm braucht eine C/C++-Laufzeitumgebung, diese muss man mit Flatpak erstmal auf dem System installieren. Es bietet sich an, eine sowieso schon durch andere Flatpaks installierte Runtime auszusuchen. Anzeigen kann man die installierten Runtimes mit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
g@X1:~$ flatpak list
Name Anwendungskennung Version Zweig Installation
Google Chrome com.google.Chrome 142.0.7444.162-1 stable system
Erweiterungs-Manager com.mattjakeman.ExtensionManager 0.6.5 stable system
Element im.riot.Riot 1.12.3 stable system
Solaar io.github.pwr_solaar.solaar 1.1.16 stable system
Freedesktop Platform org.freedesktop.Platform freedesktop-sdk-24.08.28 24.08 system
Freedesktop Platform org.freedesktop.Platform freedesktop-sdk-25.08.4 25.08 system
Mesa org.freedesktop.Platform.GL.default 25.2.6 24.08 system
Mesa (Extra) org.freedesktop.Platform.GL.default 25.2.6 24.08extra system
Mesa org.freedesktop.Platform.GL.default 25.2.6 25.08 system
Mesa (Extra) org.freedesktop.Platform.GL.default 25.2.6 25.08-extra system
nvidia-580-95-05 org.freedesktop.Platform.GL.nvidia-580-95-05 1.4 system
Nvidia VAAPI driver org.freedesktop.Platform.VAAPI.nvidia 25.08 system
Codecs Extra Extension org.freedesktop.Platform.codecs-extra 25.08-extra system
openh264 org.freedesktop.Platform.openh264 2.5.1 2.5.1 system
GNOME Application Platform version 48 org.gnome.Platform 48 system
GNOME Application Platform version 49 org.gnome.Platform 49 system
Yaru Gtk Theme org.gtk.Gtk3theme.Yaru 3.22 system
Auf meinem System sind schon etliche Flatpak-runtimes installiert, alle systemweit (Installation = system). Man kann auch Flatpaks nur für den eigenen Nutzer installieren (Installation = user).
Für das Erstellen des Programms brauchen wir nun eine Laufzeitumgebung, z.B. org.freedesktop.Platform in der Version freedesktop-sdk-25.08.4. Da die schon installiert ist, brauchen wir aktuell keine Neue installieren.
Für das Qt-GUI-Programm werden wir später noch weitere Runtimes installieren, aber für das Kommandozeilenprogramm reicht es erstmal.
Die Laufzeitumgebungen alleine reichen aber nicht. Wenn wir Programme passgenau compilieren wollen, müssen wir noch das dazugehörige Sdk-Paket installieren. In diesem Fall org.freedesktop.Sdk/x86_64/25.08. Das macht man mit flatpak install:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
g@X1:~$ flatpak install org.freedesktop.Sdk
Suchen nach Übereinstimmungen …
Remotes found with refs similar to ‘org.freedesktop.Sdk’:
1) ‘flathub’ (system)
2) ‘flathub’ (user)
Which do you want to use (0 to abort)? [0-2]: 1
Similar refs found for ‘org.freedesktop.Sdk’ in remote ‘flathub’ (system):
1) runtime/org.freedesktop.Sdk/x86_64/20.08
2) runtime/org.freedesktop.Sdk/x86_64/21.08
3) runtime/org.freedesktop.Sdk/x86_64/22.08
4) runtime/org.freedesktop.Sdk/x86_64/23.08
5) runtime/org.freedesktop.Sdk/x86_64/18.08
6) runtime/org.freedesktop.Sdk/x86_64/1.6
7) runtime/org.freedesktop.Sdk/x86_64/24.08
8) runtime/org.freedesktop.Sdk/x86_64/19.08
9) runtime/org.freedesktop.Sdk/x86_64/25.08
Which do you want to use (0 to abort)? [0-9]: 9
Wenn man keine Version explizit angibt, kann man interaktiv die gewünscht Laufzeitversion auswählen. Oder man gibt gleich die volle Version zur Installation an:
1
flatpak install org.freedesktop.Sdk/x86_64/25.08
Flatpak Manifestdatei erstellen
Wie schon erwähnt braucht man beim Erstellen eines Flatpaks ein Upstream-Quelltextstand. Zuerst nehmen wir mal an, dass dieser Quelltext aus einem öffentlich zugänglichen github-Repository stammt.
Um ein Flatpak zu erstellen, benötigen wir eine Manifest-Datei. Wir erstellen im Verzeichnis release/flatpak-github eine Manifest-Datei org.ghorwin.linuxdeptool.yml, welche Anweisungen für den Flatpak-Builder enthält.
Manifest-Dateien für Flatpak (üblicherweise mit Erweiterunge yml) können im YAML-Format oder als JSON-Datei angelegt werden:
YAML-Format:
1 2 3 4 5 6 id: org.flatpak.Hello runtime: org.freedesktop.Platform runtime-version: '25.08' sdk: org.freedesktop.Sdk command: hello ...JSON-Format:
1 2 3 4 5 6 7 8 { "id": "org.flatpak.Hello", "runtime": "org.freedesktop.Platform", "runtime-version": "25.08", "sdk": "org.freedesktop.Sdk", "command": "hello", "...":"..." }Das JSON-Format scheint bei einigen Flatpak-Erstellern beliebter zu sein. Falls man die Syntax des eigenen JSON-Texts testen will, kann man das unter anderem auf dieser JSON-Test-Webseite machen. Ich verwende hier bei den einfachen Beispielen das YAML-Format.
Inhalt der Datei org.ghorwin.linuxdeptool.yml:
1
2
3
4
5
6
7
8
9
10
11
12
id: org.ghowin.linuxdeptool
runtime: org.freedesktop.Platform
runtime-version: '25.08'
sdk: org.freedesktop.Sdk
command: deptool
modules:
- name: DepTool
buildsystem: cmake
sources:
- type: git
url: https://github.com/ghorwin/LinuxDeploymentTest.git
tag: 'v1_command_line_tool'
Kurzerläuterung der Einträge:
idoderapp-id: Der eindeutige Identifizierer des Flatpak Pakets (entspricht üblicherweise dem Dateinamen, muss es aber nicht)runtime: welches Flatpak-Paket wird als Laufzeitumgebung verwendetruntime-version: die entsprechende Laufzeit-Version (siehe Ausgabe vonflatpak list). Frühere Versionen erlauben Deployment auf älteren Linuxsystemensdk: Gibt an, welches Flatpak-Paket als Laufzeitumgebung zum Übersetzen des Programms geladen sein muss, also welche Compilertools/toolchains benötigt werden. Bei der Installation dieses Sdk muss darauf geachtet werden, dass es in der gleichen Version wieruntime-versionvorliegt.command: Der Name der auszuführenden Datei
Dann gibt es den Block modules. Hier gibt es nur ein zu compilierendes “Modul”, nämlich die Kommandozeilenapp deptool. Jede Moduldefinition beginnt mit - name: und dann werden weitere Parameter entsprechend eingerückt definiert (hier muss man etwas auf die Anzahl der Spaces achten).
name: Ein eindeutiger Modulname, muss nicht mit dem Namen des zu generieren Binaries oder Bibliotheksnamen übereinstimmen, muss nur das Modul eindeutig benennenbuildsystem: hier kann man verschiedene Sachen auswählen.cmakeerwartet im Wurzelverzeichnis des Quelltextes eineCMakeLists.txt-Datei. Man kann auchsimplebenutzen und einfach ein Shellskript aufrufen.sources: Unterhalb dieses Eigenschaftbaums gibt man an, wo Flatpak den Upstream-Quelltext herbekommt. Damit das Erstellen eines Flatpak immer deterministisch und unabhängig von lokalen Änderungen im der git Arbeitskopie ist, legt man hier eine eindeutige Quelle für die Dateien fest. Es gibt hier viele Optionen: Quelltext von einem git-repo herunterladen, oder aus einem Quelltextarchiv entpacken oder auch bereits kompilierte Binaries benutzen (wobei man dann immer sicherstellen muss, dass die zur ausgewählten Laufzeitumgebung passen).type: hier wird festgelegt, wo das Zeug her kommt. Wir verwenden hier die Variantegit, d.h. Herunterladen von einem git repourl: Addresse des git repotag: Optional der zu verwendende Tag (muss im master branch sein)commit: Alternativ zutagkann man hier auch einen git commit hash angeben, um ein bestimmten Commit für den Quelltextbezug auszuwählen
Flatpak erstellen
Mit dieser Manifest-Datei kann man auch schon das Flatpak-Paket erstellen. Der allgemeine Aufruf sieht wie folgt aus:
1
flatpak-builder --force-clean <build-dir> <manifest-file>
Die Option --force-clean bereinigt das Erstellungsverzeichnis und muss angegeben werden, wenn vom letzten Build noch Daten im Arbeitsbauverzeichnis liegen.
Außerdem kann man noch die Option --repo=repo hinzufügen, um das geclonte Repository in ein bestimmtes Verzeichnis zu schieben.
Wenn man mehr Details sehen will (z.B. um Fehler zu finden), kann man noch das Argument --verbose hinzufügen.
Option 1: Flatpak direkt installieren: Nachdem ein Flatpak erstellt wurde, kann man es direkt lokal installieren und dann damit arbeiten. Das ist ungefährt vergleichbar mit dem Deployment im Source-Tarball. In diesem Fall fügt man das Argument --install hinzu.
Option 2: Flatpak in ein Repository exportieren: Man kann das erstellte Flatpak auch in ein Repository exportieren, welches man dann Anwendern zur Verfügung stellen kann (wie das geht, wird weiter unten beschrieben).
Ausgeführt für unser Beispiel sieht das so aus:
1
flatpak-builder --force-clean --repo=repo build-deptool org.ghowin.linuxdeptool.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
g@X1:~/git/_MyOpenSource/LinuxDeploymentTest/release/flatpak-gitsource$ flatpak-builder --force-clean --repo=repo build org.ghowin.linuxdeptool.yml
Downloading sources
Initialized empty Git repository in /home/ghorwin/git/_MyOpenSource/LinuxDeploymentTest/release/flatpak-gitsource/.flatpak-builder/git/https_github.com_ghorwin_LinuxDeploymentTest.git-VTGWG3/
Fetching git repo https://github.com/ghorwin/LinuxDeploymentTest.git, ref refs/tags/v1_command_line_tool
remote: Enumerating objects: 21, done.
remote: Counting objects: 100% (21/21), done.
remote: Compressing objects: 100% (16/16), done.
remote: Total 21 (delta 0), reused 17 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (21/21), 4.45 KiB | 2.22 MiB/s, done.
From https://github.com/ghorwin/LinuxDeploymentTest
* [new tag] v1_command_line_tool -> v1_command_line_tool
Initializing build dir
Committing stage init to cache
Starting build of org.ghowin.linuxdeptool
========================================================================
Building module DepTool in /home/ghorwin/git/_MyOpenSource/LinuxDeploymentTest/release/flatpak-gitsource/.flatpak-builder/build/DepTool-1
========================================================================
HEAD is now at 3d1c856 Added CPack instructions to CMakeLists.txt for optional "make package" target
-- The C compiler identification is GNU 14.3.0
-- The CXX compiler identification is GNU 14.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.5s)
-- Generating done (0.0s)
-- Build files have been written to: /run/build/DepTool
[ 50%] Building CXX object DepTool/CMakeFiles/DepTool.dir/src/main.cpp.o
[100%] Linking CXX executable deptool
[100%] Built target DepTool
[100%] Built target DepTool
Install the project...
-- Install configuration: "RelWithDebInfo"
-- Installing: /app/bin/deptool
-- Installing: /app/share/man/man1/deptool.1
compressing debuginfo in: /home/ghorwin/git/_MyOpenSource/LinuxDeploymentTest/release/flatpak-gitsource/.flatpak-builder/rofiles/rofiles-7xmc8g/files/bin/deptool
processing: /home/ghorwin/git/_MyOpenSource/LinuxDeploymentTest/release/flatpak-gitsource/.flatpak-builder/rofiles/rofiles-7xmc8g/files/bin/deptool
[29] .debug_aranges compressed -> .zdebug_aranges (320 => 109 34.06%)
[30] .debug_info compressed -> .zdebug_info (10348 => 5533 53.47%)
[31] .debug_abbrev compressed -> .zdebug_abbrev (2089 => 869 41.60%)
[32] .debug_line compressed -> .zdebug_line (995 => 544 54.67%)
[33] .debug_str compressed -> .zdebug_str (5882 => 2274 38.66%)
[34] .debug_line_str compressed -> .zdebug_line_str (1038 => 463 44.61%)
[35] .debug_loclists compressed -> .zdebug_loclists (432 => 186 43.06%)
[36] .debug_rnglists compressed -> .zdebug_rnglists (252 => 122 48.41%)
[39] Updating section string table
stripping /home/ghorwin/git/_MyOpenSource/LinuxDeploymentTest/release/flatpak-gitsource/.flatpak-builder/rofiles/rofiles-7xmc8g/files/bin/deptool to /home/ghorwin/git/_MyOpenSource/LinuxDeploymentTest/release/flatpak-gitsource/.flatpak-builder/rofiles/rofiles-7xmc8g/files/lib/debug/bin/deptool.debug
Committing stage build-DepTool to cache
Cleaning up
Committing stage cleanup to cache
Finishing app
Please review the exported files and the metadata
Committing stage finish to cache
Exporting org.ghowin.linuxdeptool to repo
Commit: b59b8245d9f6b06ce144a8008d4a6bf09c9fb3252e960cbcb298d2ded8c864b8
Metadata Total: 19
Metadata Written: 10
Content Total: 4
Content Written: 4
Content Bytes Written: 15726 (15,7 kB)
Exporting org.ghowin.linuxdeptool.Debug to repo
Commit: fd941002ae39838839ef35f1483a01f326b72b10f0a71c5fbc897c7f3055e833
Metadata Total: 15
Metadata Written: 8
Content Total: 3
Content Written: 3
Content Bytes Written: 16176 (16,2 kB)
Pruning cache
Hier passiert jetzt eine ganze Menge:
- Das git repository wird geclont und die Daten landen im Unterverzeichnis
repo(siehe Argument--repo) - Es wird der tag v1_command_line_tool im Arbeitsverzeichnis ausgecheckt
- cmake wird aufgerufen und danach make, beendet mit der Ausgabe
[100%] Built target DepTool. Dabei wechselt flatpak das Arbeitsverzeichnis in die Wurzel des ausgecheckten Verzeichnisses. - es wird
make installaufgerufen, allerdings mit dem Installprefix/app, siehe Ausgabe-- Installing: /app/bin/deptool - das Flatpak wird fertig gestellt
Im Arbeitsverzeichnis sind nun 3 neue Verzeichnisse entstanden:
.flatpak-builder: in diesem versteckten Verzeichnis liegen die Arbeitsdaten und die compilierten Dateien drinrepo- Repositorybuild-deptool- Arbeitsverzeichnis für das erstellte DepTool-Projekt
Flatpak-Builder arbeitet viel mit Caches. Sowohl das git-Repo wird lokal gecached, sodass man bei nachfolgenden aufrufen nur die Änderungen im Upstream-Repo herunterladen muss. Ebenso werden die compilierten Dateien gecached, und man muss nur die geänderten Dateien neu bauen. Das alles spart Download- und Erstellungszeit und zahlt sich vor allem bei großen Projekten aus. Wenn man diese gecachten Daten bereinigen will, einfach die generierten 3 Verzeichnisse löschen, und man fängt mit einem sauberen Build an.
Nun ist das Flatpak zwar fertig, aber noch nicht wirklich einsatzfähig. Man kann daraus aber ein sogenanntes single-file flatpak erstellen, was man dann zum Download anbieten kann.
Single-file Flatpak Paket erstellen
1
flatpak build-bundle repo linuxdeptool.25.08.flatpak org.ghowin.linuxdeptool --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
Es wird nun die Datei linuxdeptool.25.08.flatpak (hier mal zum Download: linuxdeptool.25.08.flatpak).
Single-file Flatpak Paket installieren und verwenden
Anwender können diese Datei nun herunterladen und installieren. Das geht entweder via Doppelklick mit der Softwareverwaltung oder auf der Kommandozeile:
1
2
3
4
5
6
7
ghorwin@AndisX1:~$ flatpak install linuxdeptool.25.08.flatpak
KENNUNG Zweig Op Gegenstelle Herunterladen
1. [✓] org.ghowin.linuxdeptool master i linuxdeptool-origin 0 Bytes
Installation abgeschlossen.
Das installierte Paket wird nun wie folgt angezeigt:
1
2
3
4
5
6
ghorwin@AndisX1:~$ flatpak list
Name Anwendungskennung Version Zweig Ursprung Installation
...
Freedesktop SDK org.freedesktop.Sdk freedesktop-sdk-25.08.4 25.08 flathub system
linuxdeptool org.ghowin.linuxdeptool master linuxdeptool-origin system...
...
und kann ausgeführt werden mit:
1
ghorwin@AndisX1:~$ flatpak run org.ghowin.linuxdeptool
Ein weiterer Nachteil von Flatpaks wird so sichtbar. Man kann nicht einfach wie gewohnt
deptoolauf der Kommandozeile eintippen, um das Programm zu starten. Man könnte natürlich einen Alias anlegen:
1 alias deptool='flatpak run org.ghowin.linuxdeptool'aber das ist wieder Extraarbeit für den Anwender.
Tutorial Teil 3: Qt-basierte GUI Anwendung und Deployment als Flatpak
Nun soll es um hinreichend realistisches Programm gehen:
- Qt-basierte Oberfläche (mit entsprechenden Abhängigkeiten zu den Qt Laufzeitbibliotheken)
- Übersetzungsdateien (zur Laufzeit geladene Ressourcendateien)
- Aufruf des mitinstallierten Kommandozeilenprogramms aus der GUI
- Zugriff auf Dateien im Dateisystem