CMake Install

CMake ist “das” Werkzeug zum Bauen von C/C++ Software. Doch danach soll diese auch verteilt werden können.

Und genau hierfür müssen die erzeugten Binärdateien und auch Interface Sourcecodes an die richtige Stelle geschoben werden.


Auf Grund meiner “Windows-Vergangenheit”, sah ich CMake in erster Linie immer als Build-Generator, der nur das Kompilieren kontrollieren soll.

In der Linux Welt ist die “Installation” des fertigen Kompilats an die richtige Stelle im / Dateisystem aber ebenso wichtig. Lässt man CMake auch diesen Schritt übernehmen, ergeben sich gleich mehrere Vorteile.

install(TARGETS)

Wenn man die öffentlichen Header in einem Projekt auch als solche in CMake bekannt gegeben hat, dann können diese parallel zu den Binaries auch gleich mitinstalliert werden.
Ein CMake-Projekt sieht dann beispielsweise so aus:

 1project(MyProject)
 2
 3file(GLOB MY_HEADERS "*.h")
 4file(GLOB MY_SOURCES "*.c" "*.cpp" )
 5
 6add_library(${PROJECT_NAME}
 7  ${MY_HEADERS} ${MY_SOURCES})
 8
 9set_target_properties(${PROJECT_NAME} PROPERTIES
10  PUBLIC_HEADER "${MY_HEADERS}"
11)
12
13target_link_libraries(${PROJECT_NAME}
14  mydependency
15)
16
17install(TARGETS ${PROJECT_NAME}
18  ARCHIVE DESTINATION "lib"
19  LIBRARY DESTINATION "lib"
20  RUNTIME DESTINATION "bin"
21  PUBLIC_HEADER DESTINATION "include"
22)

Wie in make lässt sich auch in cmake ein Prefix-Directory angeben und in diesem werden die Dateien in die Unterverzeichnisse lib, bin und include am Ende installiert.

Der Standard-Prefix-Pfad ist /usr unter Linux, womit wir am Ende bei den Standardverzeichnissen /usr/bin, /usr/lib und /usr/include landen.

Conan Pakete

Tatsächlich haben mich erst Conan Pakete auf die Notwendigkeit einer korrekten Installation aufmerksam gemacht.
Wenn nämlich ein cmake Install-Schritt alle Dateien in die Unterverzeichnisse bin, lib und include eines Zielverzeichnisses verschieben kann, dann ist die Conan package() Methode nichts weiter als ein:

1def package(self):
2    cmake = CMake(self)
3    cmake.install()

Runtime-Pfade

Wenn man komplexere Bibliotheken erstellt, ordnet man diese gerne in Hierarchien von Unterverzeichnissen ein. Die Dateien liegen dann nicht einfach in lib/ sondern z.B.: in lib/component/version/something.so.

Wenn man jetzt ein Projekt mit Bibliotheken und Programmen baut, linken die Programme direkt gegen die Pfade im Build-System.
Das wäre dann z.B.: ein ~/my_project/build/output/mycorp/mylib.so. Doch genau diesen Pfad wird es am Ende nicht mehr geben, weil wir die .so in /usr/lib/mycorp/mylib.so installieren wollen.

Das Programm findet mylib.so somit nicht mehr und verweigert die Ausführung.
Zur Lösung dieses Problems hat jedes Programm das RPATH Feld (Runtime-Path), welches die Verzeichnisse enthält, wo .so Dateien gesucht werden sollen.

cmake --install path/to/build_dir ändert bei einer Installation eines Programms auch diesen Pfad auf das neue Installationsziel um.
Dadurch erspart man sich üble Anpassungen der Library-Loader-Konfiguration oder das Setzen von LD_LIBRARY_PATH auf die eigenen Unterverzeichnisse.

Fazit

install(TARGETS) ist nun in allen meinen Projekten nachgepflegt, wo es bisher fehlte … und einem Tool wie Conan gefällt das sehr.

Bisher war das Thema Runtime-Pfade für mich eher nebensächlich, da ich in erste Linie statisch kompiliere und auf .dll / .so Varianten gerne verzichte.
Ein “ordentliches” Projekt muss aber mit beiden Möglichkeiten umgehen können und somit gehören Shared Libraries und deren korrekte Installation auch zum erwarteten Standard.

Es gibt da noch die interessante Möglichkeit den RPATH “relativ” zu setzen … aber das ist eine andere Geschichte.