
CMake targets
«« | 11 Aug 2024 | »»Eine große Last wird in der C/C++ Entwicklung von der Vergangenheit erzeugt. Code, der einmal funktioniert hat, bleibt auf unbestimmte Zeit unverändert.
Doch damit verbaut man sich auch oft viele neue Features und verkompliziert sich unnötig den Alltag.
Als ich 2019 endlich auch alle meine Projekte auf CMake
umstellte, tat ich
das wie so oft mit dem Hintergedanken den Abwärtskompatibilität.
Ich wollte nur “stabile” Features nutzen, die es schon in CMake
3.4 gab,
damit ich stets in der Lage war mit alten CMake
Varianten zu arbeiten, die
noch Codes aus dem Jahre 2000 bauen konnten.
Und so ergeben sich dann viele Aufrufe nach dem Schema
add_definitions(...)
include_directories(...)
link_directories(...)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --something)
die zwar alle toll funktionieren, aber den globalen Namensraum verschmutzen und gleichzeitig in anderen Projekten wiederholt werden müssen, damit es dort auch klappt.
Nur target_xxx_yyy(target ...)
ist richtig
Modernes CMake
sollte auf dem korrekten Ausbau von targets
aufbauen.
Sobald mit
add_library(my_target ...)
oder add_executable(my_target ...)
ein target
definiert wurde, sollten ihm öffentliche und private Einstellungen
zugewiesen werden.
Die privaten werden nur für das target
selbst gebraucht, doch öffentliche
vererben sich automatisch an ein Folgeprojekt weiter, das mit
target_link_libraries(other_target my_target)
an seine Abhängigkeit gebunden wird.
target_compile_definitions()
Meiner Erfahrung nach sind 90% aller “Definitions” (also von extern gesetzten
Makros) nur für ein einzelnes Projekt relevant.
Anstatt add_definition(-DBZ_EXPORT)
bei einer bzip2
hineinzuwerfen,
sollte es target_compile_defintion(bz2 PRIVATE "BZ_EXPORT")
heißen.
Ist ein Makro aber in öffentlichen Headern aktiv, kann es mit einer PUBLIC
Definition an alle abhängigen Projekt weitervererbt werden.
target_compile_options()
Das schlimmste, was man tun kann, ist: Wegen einer lästigen Warnung alle
Warnungen in allen Projekten abzuschalten. Krebsgeschwüre wie -fpermissive
haben so unzählige Opfer infiziert.
Anstatt mit solchen Optionen per add_definition
den ganzen Baum zu vergiften,
kann man per
target_compile_options(mydirtylib PRIVATE "-fpermissive")
nur das eine
schlampig geschrieben Projekt anpassen.
Ähnlich verhält es sich auch mit anderen - nicht ganz so bösen - Optionen.
target_include_directories()
include_directories(...)
zwingt dem Verzeichnisbaum einen Include-Pfad auf,
ganz egal, ob es sich um private Sourcen oder öffentliche Header handelt.
Die richtige Arznei ist: target_include_directories
Private Include-Verzeichnisse gelten nur für das Target selbst, während
öffentliche Includes an alle Targets “vererbt” werden, die das aktuelle
Target linken.
Das spart einiges an Redundanz, denn bisher mussten “Konsumenten” die
Include-Directory-Vorlieben ihrer Abhängigkeiten kennen.
Fazit
Meine eigenen externen Bibliotheken sind voll von den “alten” Funktionen, die teils auch von uralten Projekten stammen.
Doch wer ein neues Projekt auf die Beine stellt sollte
(von wenigen berechtigten Ausnahmen abgesehen) nur noch Eigenschaften auf
CMake
Targets anwenden.
Es ist ja auch vollkommen OK, wenn man sich ein öffentliche CMake
Funktion
schreibt, die einem Projekt bestimmt “Standards” angedeihen lässt, womit
viele Targets ähnlich bzw. gleich aufgesetzt sind.
“Das Target” ist der König in CMake
, daher muss alles immer erst
mit ebendiesem abgestimmt werden.