2c21df4be0
Die Meldung 'This file does not belong to any project target' kommt nicht vom CMakeLists.txt: die File API ordnet calculator.hpp korrekt dem Target pda zu (LIBPDA_HEADERS geht an add_library). CLion hatte nur kein Modell. Dokumentiert dazu die Falle, dass eine aus dem Dock gestartete GUI die PATH aus .zprofile nicht erbt -- 'CMAKE_C_COMPILER: clang' loest dort auf Apple clang auf, im Terminal auf Homebrew-LLVM. Da binaryDir in beiden Faellen build/debug ist, gewinnt lautlos, wer zuerst konfiguriert. .gitignore deckte /build/ ab, aber nicht CLions Default cmake-build-<profil>/ im Wurzelverzeichnis. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
555 lines
18 KiB
Markdown
555 lines
18 KiB
Markdown
# CMake verstehen
|
|
|
|
Diese Datei erklärt CMake an *diesem* Projekt. Kein Referenzhandbuch — die
|
|
Reihenfolge folgt der Frage „wann mache ich was?".
|
|
|
|
---
|
|
|
|
## 1. Das Modell: es gibt nur Targets
|
|
|
|
CMake ist keine Skriptsprache, die Compiler-Kommandos zusammenbaut. Es ist ein
|
|
**Generator**: es baut einen Graphen aus *Targets* und schreibt daraus
|
|
Ninja-Dateien. Alles, was du in einer `CMakeLists.txt` schreibst, hat genau
|
|
einen Zweck — Targets anzulegen und ihre Eigenschaften zu setzen.
|
|
|
|
Ein Target ist:
|
|
|
|
| Art | Befehl | in diesem Projekt |
|
|
|---|---|---|
|
|
| Bibliothek | `add_library` | `pda`, `pda_shell`, `unity` |
|
|
| Programm | `add_executable` | `pda_app`, `calculator.test` |
|
|
| INTERFACE-Target | `add_library(x INTERFACE)` | `pda_warnings`, `doctest` |
|
|
|
|
Ein **INTERFACE-Target** hat keinen eigenen Quellcode. Es ist ein Bündel
|
|
Eigenschaften, das man weiterreicht — `pda_warnings` in `cmake/Warnings.cmake`
|
|
ist nichts als eine Liste von `-W`-Flags mit einem Namen.
|
|
|
|
Eigenschaften vererben sich am Graphen entlang. Das ist der ganze Trick, und
|
|
es ist der Grund, warum man **niemals** `include_directories()` oder
|
|
`add_definitions()` benutzt: die wirken global auf alles, statt an einem Target
|
|
zu hängen.
|
|
|
|
> **Faustregel:** Jeder Befehl, den du benutzt, sollte `target_` am Anfang
|
|
> haben. Wenn nicht, frag dich, warum.
|
|
|
|
---
|
|
|
|
## 2. PUBLIC, PRIVATE, INTERFACE
|
|
|
|
Das ist der Begriff, an dem die meisten hängenbleiben. Die drei Wörter
|
|
beantworten *eine* Frage: **Wer sieht diese Eigenschaft?**
|
|
|
|
```
|
|
ich selbst wer mich linkt
|
|
PRIVATE ja nein
|
|
INTERFACE nein ja
|
|
PUBLIC ja ja
|
|
```
|
|
|
|
Aus `libpda/CMakeLists.txt`:
|
|
|
|
```cmake
|
|
target_include_directories(pda PUBLIC ...) # contact.hpp braucht der Benutzer
|
|
target_link_libraries(pda PRIVATE pda_warnings) # unsere Warnungen sind unsere Sache
|
|
target_compile_features(pda PUBLIC cxx_std_23) # <expected> steht IM Header
|
|
```
|
|
|
|
Die Entscheidung hängt nur davon ab, **ob die Sache im Header vorkommt**:
|
|
|
|
- Etwas steht in einem öffentlichen Header → `PUBLIC`
|
|
- Etwas kommt nur in der `.cpp` vor → `PRIVATE`
|
|
|
|
`target_compile_features(pda PUBLIC cxx_std_23)` ist dafür das beste Beispiel.
|
|
`contact.hpp` gibt `std::expected` zurück. Wer libpda mit C++17 benutzen will,
|
|
soll eine klare Meldung bekommen und keinen Header-Fehler dreißig Zeilen tief.
|
|
|
|
Ein Fall aus diesem Projekt, in `pda/CMakeLists.txt`:
|
|
|
|
```cmake
|
|
target_link_libraries(pda_shell PUBLIC pda::pda)
|
|
```
|
|
|
|
`PUBLIC`, obwohl `pda_shell` intern ist — weil `shell.hpp` die Zeile
|
|
`#include <libpda/contact.hpp>` enthält. Wäre das `PRIVATE`, würde
|
|
`shell.test.cpp` mit „file not found" scheitern.
|
|
|
|
---
|
|
|
|
## 3. Reihenfolge: was muss wann stehen
|
|
|
|
CMake liest von oben nach unten. Manches wird **beim Anlegen eines Targets**
|
|
eingefroren, anderes erst am Ende ausgewertet.
|
|
|
|
**Muss VOR dem ersten Target stehen** (sonst wirkungslos):
|
|
|
|
```cmake
|
|
set(CMAKE_CXX_STANDARD 23) # wird ins Target kopiert
|
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ...)
|
|
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
|
```
|
|
|
|
Deshalb steht das alles in `cmake/ProjectDefaults.cmake`, das ganz oben
|
|
eingebunden wird. Ein `set(CMAKE_CXX_STANDARD 23)` *nach* `add_library()`
|
|
ändert nichts mehr — der häufigste stumme Fehler in CMake.
|
|
|
|
**Darf nach dem Target stehen** (arbeitet auf dem Target):
|
|
|
|
```cmake
|
|
target_link_libraries(...)
|
|
target_include_directories(...)
|
|
set_target_properties(...)
|
|
```
|
|
|
|
**Muss ins oberste `CMakeLists.txt`:**
|
|
|
|
```cmake
|
|
enable_testing() # nur hier legt es die CTestTestfile.cmake an, die ctest sucht
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Generator-Ausdrücke
|
|
|
|
`$<...>` wird **nicht** beim Einlesen ausgewertet, sondern erst beim
|
|
Generieren — wenn CMake schon weiß, welche Konfiguration gebaut wird und ob
|
|
gerade installiert wird. Deshalb kann man damit Dinge sagen, die zur Lesezeit
|
|
noch nicht feststehen.
|
|
|
|
Die zwei wichtigsten stehen in `libpda/CMakeLists.txt`:
|
|
|
|
```cmake
|
|
target_include_directories(pda PUBLIC
|
|
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
|
|
"$<INSTALL_INTERFACE:include>")
|
|
```
|
|
|
|
- **`BUILD_INTERFACE`** gilt, solange im Quellbaum gebaut wird. Include-Root
|
|
ist `<repo>/libpda`, damit `<libpda/contact.hpp>` auf
|
|
`<repo>/libpda/libpda/contact.hpp` zeigt.
|
|
- **`INSTALL_INTERFACE`** gilt nach dem Installieren, relativ zum Präfix.
|
|
|
|
**Ohne diese Trennung zeigt die installierte Bibliothek auf dein
|
|
Build-Verzeichnis.** Beim Benutzer existiert das nicht. Das ist *der*
|
|
klassische Fehler beim Verteilen einer Bibliothek, und er fällt im eigenen
|
|
Build nie auf — `tools/check-install.sh` existiert genau deswegen.
|
|
|
|
Der zweite Fall ist subtiler:
|
|
|
|
```cmake
|
|
target_link_libraries(pda PRIVATE "$<BUILD_INTERFACE:pda_warnings>")
|
|
```
|
|
|
|
Warum nicht einfach `PRIVATE pda_warnings`? Eine **statische** Bibliothek
|
|
linkt ihre Abhängigkeiten nicht selbst — das muss der tun, der sie benutzt.
|
|
CMake trägt `PRIVATE`-Abhängigkeiten deshalb trotzdem als `$<LINK_ONLY:...>`
|
|
ins Interface ein. `install(EXPORT)` sieht dort `pda_warnings`, findet es in
|
|
keinem Export-Set und bricht ab:
|
|
|
|
```
|
|
install(EXPORT "pdaTargets" ...) includes target "pda" which requires
|
|
target "pda_warnings" that is not in any export set.
|
|
```
|
|
|
|
`$<BUILD_INTERFACE:...>` löst im Build zu `pda_warnings` auf und beim
|
|
Installieren zu nichts.
|
|
|
|
---
|
|
|
|
## 5. Superprojekt und Teilprojekte
|
|
|
|
```
|
|
playground/ <- Superprojekt, kein eigener Quellcode
|
|
├── libpda/ <- eigenes project()
|
|
└── pda/ <- eigenes project()
|
|
```
|
|
|
|
P1204R0 verlangt die Trennung von Bibliothek und Programm. Der praktische
|
|
Grund: die Bibliothek muss **ohne** die Anwendung baubar sein, sonst ist sie
|
|
keine Bibliothek.
|
|
|
|
Jedes Teilprojekt hat deshalb diesen Block:
|
|
|
|
```cmake
|
|
if(PROJECT_IS_TOP_LEVEL)
|
|
# Alles holen, was sonst das Superprojekt bereitstellt
|
|
endif()
|
|
```
|
|
|
|
`PROJECT_IS_TOP_LEVEL` ist `TRUE`, wenn dieses `project()` das oberste ist.
|
|
Beide Wege müssen funktionieren:
|
|
|
|
```sh
|
|
cmake --preset debug # Superprojekt: baut beides
|
|
cmake -S libpda -B build/nur-lib # nur die Bibliothek
|
|
cmake -S pda -B build/nur-app \ # nur die Anwendung, gegen installierte libpda
|
|
-DCMAKE_PREFIX_PATH=/pfad/zur/installation
|
|
```
|
|
|
|
Der dritte Fall ist der wertvollste: `pda/CMakeLists.txt` benutzt dann
|
|
`find_package(pda REQUIRED)` — also **genau den Weg, den ein fremdes Projekt
|
|
geht**. Damit ist die Anwendung gleichzeitig der Test für den Export.
|
|
|
|
Weil `cmake/` und `third_party/` im Wurzelverzeichnis liegen, brauchen die
|
|
Teilprojekte im Alleinbetrieb die zweiargumentige Form:
|
|
|
|
```cmake
|
|
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../third_party" third_party)
|
|
```
|
|
|
|
Das zweite Argument ist das *Build*-Verzeichnis. Ohne es bricht CMake ab, weil
|
|
das Quellverzeichnis außerhalb des Projekts liegt und CMake nicht raten will,
|
|
wohin die Artefakte sollen.
|
|
|
|
Damit `cmake/Warnings.cmake` aus beiden Richtungen eingebunden werden kann,
|
|
steht dort `include_guard(GLOBAL)`. Ohne das wäre der zweite
|
|
`add_library(pda_warnings INTERFACE)` ein harter Fehler.
|
|
|
|
---
|
|
|
|
## 6. Targetname ≠ Dateiname
|
|
|
|
```cmake
|
|
add_library(pda ...) # -> libpda.a (CMake setzt "lib" davor)
|
|
add_executable(pda_app pda/main.cpp)
|
|
set_target_properties(pda_app PROPERTIES OUTPUT_NAME pda) # -> bin/pda
|
|
```
|
|
|
|
Zwei Targets dürfen nicht gleich heißen — die Bibliothek belegt schon `pda`.
|
|
Der *Dateiname* darf trotzdem `pda` sein. Ein Target namens `libpda` ergäbe
|
|
übrigens `liblibpda.a`.
|
|
|
|
Dazu der Alias:
|
|
|
|
```cmake
|
|
add_library(pda::pda ALIAS pda)
|
|
```
|
|
|
|
Er kostet nichts und hat einen konkreten Nutzen: ein Tippfehler in einem Namen
|
|
**mit** Doppelpunkt ist ein sofortiger CMake-Fehler. Ohne Doppelpunkt hält
|
|
CMake ihn für eine Systembibliothek und scheitert erst beim Linken, mit einer
|
|
viel schlechteren Meldung.
|
|
|
|
---
|
|
|
|
## 7. Eine Bibliothek zum Verteilen: install und export
|
|
|
|
Vier Schritte, alle in `libpda/CMakeLists.txt`:
|
|
|
|
```cmake
|
|
# 1. Dateien kopieren UND das Target für den Export vormerken
|
|
install(TARGETS pda EXPORT pdaTargets
|
|
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
|
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
|
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
|
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
|
|
|
# 2. Header kopieren -- FILES_MATCHING, sonst kommen die .cpp mit
|
|
install(DIRECTORY libpda/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libpda"
|
|
FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp")
|
|
|
|
# 3. pdaTargets.cmake erzeugen -- die Datei, die das Target beschreibt
|
|
install(EXPORT pdaTargets FILE pdaTargets.cmake NAMESPACE pda::
|
|
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/pda")
|
|
|
|
# 4. pdaConfig.cmake erzeugen -- das, was find_package(pda) sucht
|
|
configure_package_config_file(
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/../cmake/pdaConfig.cmake.in"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/pdaConfig.cmake"
|
|
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/pda")
|
|
```
|
|
|
|
`find_package(pda)` sucht `pdaConfig.cmake`, das lädt `pdaTargets.cmake`, und
|
|
darin steht `pda::pda` mit allen Include-Pfaden und Flags. Der Benutzer
|
|
schreibt nur noch:
|
|
|
|
```cmake
|
|
find_package(pda 0.1 REQUIRED)
|
|
target_link_libraries(consumer PRIVATE pda::pda)
|
|
```
|
|
|
|
Kein `-I`, kein `-lpda`, kein Pfad von Hand. Siehe `examples/consumer/`.
|
|
|
|
Zwei Details, die man leicht übersieht:
|
|
|
|
- **`GNUInstallDirs`** liefert `CMAKE_INSTALL_LIBDIR`. Auf Fedora ist das
|
|
`lib64`, nicht `lib`. Nie selbst hinschreiben.
|
|
- **`configure_package_config_file`** statt `configure_file`: es definiert
|
|
`@PACKAGE_INIT@`, das die Pfade relativ zum tatsächlichen Ort auflöst. Ein
|
|
von Hand geschriebenes Config-File mit absoluten Pfaden funktioniert nur auf
|
|
dem Rechner, auf dem installiert wurde.
|
|
|
|
Hätte libpda externe Abhängigkeiten, müssten sie in `pdaConfig.cmake.in`
|
|
stehen:
|
|
|
|
```cmake
|
|
find_dependency(ZLIB REQUIRED)
|
|
```
|
|
|
|
Das ist Pflicht: `pdaTargets.cmake` verweist auf `ZLIB::ZLIB`, und dieses
|
|
Target existiert beim Benutzer nur, wenn es vorher gefunden wurde.
|
|
|
|
---
|
|
|
|
## 8. Symbolsichtbarkeit
|
|
|
|
`ProjectDefaults.cmake` setzt:
|
|
|
|
```cmake
|
|
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
|
```
|
|
|
|
Damit ist in einer Shared Library **kein** Symbol exportiert, solange es nicht
|
|
ausdrücklich markiert ist. Das ist unter Windows ohnehin das Verhalten — es
|
|
auch unter Unix zu erzwingen, findet Portabilitätsfehler sofort.
|
|
|
|
Markiert wird mit einem generierten Makro:
|
|
|
|
```cmake
|
|
include(GenerateExportHeader)
|
|
generate_export_header(pda BASE_NAME PDA
|
|
EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/generated/libpda/pda_export.h")
|
|
```
|
|
|
|
und im Header:
|
|
|
|
```cpp
|
|
#include <libpda/pda_export.h>
|
|
|
|
class PDA_EXPORT ContactBook { ... };
|
|
[[nodiscard]] PDA_EXPORT std::string format_error(const EvalError&);
|
|
```
|
|
|
|
Dazu gehört zwingend:
|
|
|
|
```cmake
|
|
if(NOT BUILD_SHARED_LIBS)
|
|
target_compile_definitions(pda PUBLIC PDA_STATIC_DEFINE)
|
|
endif()
|
|
```
|
|
|
|
Ohne das löst `PDA_EXPORT` im statischen Bau unter Windows zu
|
|
`__declspec(dllimport)` auf und der Linker sucht eine DLL, die es nicht gibt.
|
|
`PUBLIC`, weil der Benutzer denselben Header inkludiert.
|
|
|
|
**Der Nutzen ist prüfbar:**
|
|
|
|
```sh
|
|
cmake -S . -B build/shared -DBUILD_SHARED_LIBS=ON && cmake --build build/shared
|
|
nm -gU build/shared/lib/libpda.0.1.0.dylib | c++filt | grep -c 'pda::' # 39
|
|
nm -gU build/shared/lib/libpda.0.1.0.dylib | c++filt | grep -c 'Parser' # 0
|
|
```
|
|
|
|
Die interne `Parser`-Klasse aus `calculator.cpp` steht im anonymen Namespace
|
|
und ist nicht exportiert — sie gehört niemandem außer der Übersetzungseinheit.
|
|
|
|
Dazu noch:
|
|
|
|
```cmake
|
|
set_target_properties(pda PROPERTIES
|
|
VERSION 0.1.0 # libpda.dylib.0.1.0
|
|
SOVERSION 0) # libpda.dylib.0 <- der ABI-Stand
|
|
```
|
|
|
|
`SOVERSION` ist die Zusicherung an den Linker: alles mit derselben Zahl ist
|
|
binärkompatibel. Sie wird erhöht, wenn sich das ABI ändert — nicht bei jedem
|
|
Release.
|
|
|
|
---
|
|
|
|
## 9. Tests
|
|
|
|
```cmake
|
|
enable_testing() # nur im obersten CMakeLists.txt
|
|
add_test(NAME "unity.textfile" COMMAND textfile.test)
|
|
```
|
|
|
|
Für C++ läuft das nicht von Hand, sondern über doctest:
|
|
|
|
```cmake
|
|
doctest_discover_tests(calculator.test TEST_PREFIX "doctest.")
|
|
```
|
|
|
|
Das ruft die Test-Executable **beim Build** mit `--list-test-cases` auf und
|
|
legt einen CTest-Eintrag pro `TEST_CASE` an. Ohne das sähe CTest nur einen
|
|
groben Eintrag pro Datei, und neotest könnte Testnamen nicht auf Quellzeilen
|
|
abbilden.
|
|
|
|
> Nach einem frischen Clone ist der Testbaum deshalb leer, bis einmal gebaut
|
|
> wurde. Das ist kein Fehler.
|
|
|
|
Die Anwendung braucht einen Kniff, der weit über CMake hinaus gilt:
|
|
|
|
```cmake
|
|
add_library(pda_shell STATIC pda/shell.cpp) # alles Testbare
|
|
add_executable(pda_app pda/main.cpp) # nur Ein-/Ausgabe
|
|
target_link_libraries(pda_app PRIVATE pda_shell)
|
|
```
|
|
|
|
Ein Unit-Test kann `pda_shell` linken. Würde die Logik direkt in `pda_app`
|
|
stecken, müsste der Test `shell.cpp` erneut übersetzen **und** würde
|
|
`main.cpp` mitziehen — zwei `main()` in einer Executable sind ein
|
|
Linkerfehler.
|
|
|
|
---
|
|
|
|
## 10. Presets
|
|
|
|
`CMakePresets.json` ersetzt handgeschriebene Kommandozeilen:
|
|
|
|
```sh
|
|
cmake --preset debug # konfigurieren
|
|
cmake --build --preset debug # bauen
|
|
ctest --preset debug # testen
|
|
```
|
|
|
|
| Preset | Zweck |
|
|
|---|---|
|
|
| `debug` | Alltag |
|
|
| `release` | RelWithDebInfo |
|
|
| `asan-ubsan` | AddressSanitizer + UBSan |
|
|
| `apple-clang` | Portabilitätsprobe, macOS-Systemcompiler |
|
|
| `homebrew-gcc` | Portabilitätsprobe, GCC-Frontend |
|
|
| `linux-gcc` | nur auf Linux sichtbar (`condition`) |
|
|
|
|
`binaryDir` ist `build/<preset>` und die **Tiefe ist bindend**: der
|
|
neotest-CTest-Adapter sucht nur drei Ebenen tief, und der Neovim-Target-Picker
|
|
leitet den File-API-Pfad daraus ab.
|
|
|
|
Optionen setzt man ohne neuen Preset dazu:
|
|
|
|
```sh
|
|
cmake --preset debug -DBUILD_SHARED_LIBS=ON -DPDA_BUILD_TESTS=OFF
|
|
```
|
|
|
|
`BUILD_SHARED_LIBS` ist ein CMake-Konventionsname — `add_library()` ohne
|
|
`STATIC`/`SHARED` richtet sich danach. Deshalb nie selbst erfinden.
|
|
|
|
---
|
|
|
|
## 11. Wann mache ich was?
|
|
|
|
**Eine neue Datei zu einem bestehenden Modul**
|
|
|
|
Nichts. Header werden über `#include` gefunden.
|
|
|
|
**Ein neues Modul in der Bibliothek**
|
|
|
|
1. `libpda/libpda/<name>.hpp` + `.cpp` anlegen
|
|
2. Beide in `LIBPDA_SOURCES` / `LIBPDA_HEADERS` eintragen — **kein `file(GLOB)`**
|
|
3. Öffentliche Deklarationen mit `PDA_EXPORT` markieren
|
|
4. `libpda/libpda/<name>.test.cpp` daneben, `libpda_add_doctest_test(<name>)`
|
|
|
|
**Warum kein `file(GLOB)`:** CMake wertet ihn beim *Konfigurieren* aus. Kommt
|
|
später eine Datei dazu, merkt der Build es nicht — er bleibt grün, und die
|
|
Datei ist einfach nicht dabei. `CONFIGURE_DEPENDS` mildert das, kostet aber bei
|
|
jedem Build einen Verzeichnis-Scan und ist laut CMake-Doku nicht zuverlässig.
|
|
|
|
**Ein neues Kommando in der Anwendung**
|
|
|
|
Nur `pda/pda/shell.cpp` ändern. `shell.cpp` steht schon in `pda_shell`.
|
|
|
|
**Eine externe Abhängigkeit**
|
|
|
|
```cmake
|
|
find_package(fmt REQUIRED)
|
|
target_link_libraries(pda PRIVATE fmt::fmt)
|
|
```
|
|
|
|
Steht sie in einem **öffentlichen Header**, muss sie `PUBLIC` sein **und** in
|
|
`cmake/pdaConfig.cmake.in` als `find_dependency(fmt REQUIRED)` auftauchen.
|
|
Sonst scheitert `find_package(pda)` beim Benutzer mit „target fmt::fmt not
|
|
found".
|
|
|
|
**Etwas soll nur im Debug-Bau passieren**
|
|
|
|
```cmake
|
|
target_compile_definitions(pda PRIVATE "$<$<CONFIG:Debug>:PDA_DEBUG>")
|
|
```
|
|
|
|
Nicht `if(CMAKE_BUILD_TYPE STREQUAL Debug)` — das bricht bei
|
|
Multi-Config-Generatoren (Xcode, Visual Studio), wo die Konfiguration erst
|
|
beim Bauen feststeht.
|
|
|
|
---
|
|
|
|
## 12. Fehler, die dieses Projekt schon eingebaut hat
|
|
|
|
| Symptom | Ursache | Wo nachlesen |
|
|
|---|---|---|
|
|
| `requires target "pda_warnings" that is not in any export set` | `PRIVATE` reicht bei statischen Bibliotheken nicht | Abschnitt 4 |
|
|
| Shared-Bau linkt nicht, alle Symbole fehlen | `hidden` gesetzt, aber `PDA_EXPORT` nirgends benutzt | Abschnitt 8 |
|
|
| `set(CMAKE_CXX_STANDARD)` wirkt nicht | steht nach `add_library()` | Abschnitt 3 |
|
|
| Benutzer bekommt Pfad ins Build-Verzeichnis | `BUILD_INTERFACE`/`INSTALL_INTERFACE` fehlt | Abschnitt 4 |
|
|
| Neue Datei wird nicht gebaut | `file(GLOB)` | Abschnitt 11 |
|
|
|
|
Die ersten beiden sind beim Bau *dieses* Projekts tatsächlich aufgetreten.
|
|
|
|
---
|
|
|
|
## 13. CLion
|
|
|
|
CLion liest **dieselbe** CMake File API wie jedes andere Werkzeug — es hat
|
|
keine eigene Projektdatei. Die Meldung
|
|
|
|
> This file does not belong to any project target
|
|
|
|
heisst deshalb fast nie, dass mit dem `CMakeLists.txt` etwas nicht stimmt. Sie
|
|
heisst: *CLion hat (noch) kein CMake-Modell.* Nachsehen kann man das selbst:
|
|
|
|
```sh
|
|
mkdir -p build/debug/.cmake/api/v1/query
|
|
touch build/debug/.cmake/api/v1/query/codemodel-v2
|
|
cmake --preset debug
|
|
python3 -c "
|
|
import json,glob
|
|
d=json.load(open(glob.glob('build/debug/.cmake/api/v1/reply/target-pda-*')[0]))
|
|
print(*[s['path'] for s in d['sources']], sep='\n')"
|
|
```
|
|
|
|
Stehen die Header dort — und sie stehen dort, weil `LIBPDA_HEADERS` an
|
|
`add_library()` geht — dann ist das Modell korrekt und CLion muss nur neu
|
|
laden: **File → Reload CMake Project**.
|
|
|
|
### Die Falle: zwei Compiler, ein Build-Verzeichnis
|
|
|
|
Der Preset `debug` setzt `"CMAKE_C_COMPILER": "clang"`. Das wird über `PATH`
|
|
aufgelöst — und eine aus dem Dock gestartete GUI-Anwendung erbt die `PATH` aus
|
|
`.zprofile` **nicht**. In CLion löst `clang` deshalb auf `/usr/bin/clang`
|
|
(Apple clang) auf, im Terminal auf `/opt/homebrew/opt/llvm/bin/clang`.
|
|
|
|
Weil `binaryDir` in beiden Fällen `build/debug` ist, gewinnt schlicht, wer
|
|
zuerst konfiguriert — CMake speichert den **vollen Pfad** im Cache und löst
|
|
`clang` danach nie wieder auf. Es gibt keine Fehlermeldung, nur einen anderen
|
|
Compiler als gedacht.
|
|
|
|
Zwei saubere Auswege:
|
|
|
|
1. **CLion ein eigenes Build-Verzeichnis geben** (Default `cmake-build-debug`,
|
|
in `.gitignore` eingetragen). Terminal und IDE kommen sich nie in die Quere.
|
|
2. **Denselben Compiler erzwingen:** in CLion unter *Settings → Build,
|
|
Execution, Deployment → Toolchains* C/C++-Compiler auf
|
|
`/opt/homebrew/opt/llvm/bin/clang` bzw. `clang++` setzen.
|
|
|
|
Weg 1 ist der bequemere, Weg 2 der ehrlichere -- dann sieht clangd in CLion
|
|
dieselben Diagnosen wie im Terminal.
|
|
|
|
## 14. Werkzeuge zum Nachsehen
|
|
|
|
```sh
|
|
cmake --build build/debug --target help # alle Targets
|
|
cmake -S . -B build/debug --graphviz=g.dot # Abhängigkeitsgraph
|
|
cmake --install build/debug --prefix /tmp/p # ohne echte Installation ausprobieren
|
|
./tools/check-install.sh # Export-Kette Ende-zu-Ende
|
|
```
|
|
|
|
Um zu sehen, was ein Target tatsächlich erbt:
|
|
|
|
```cmake
|
|
get_target_property(dirs pda INTERFACE_INCLUDE_DIRECTORIES)
|
|
message(STATUS "pda erbt: ${dirs}")
|
|
```
|
|
|
|
Und für die echten Compiler-Kommandos: `build/debug/compile_commands.json` —
|
|
dieselbe Datei, die clangd liest.
|