Files
paulhorn 5efc5fccff docs: CLion-Abschnitt um die tatsaechliche Loesungsreihenfolge ergaenzen
Der Einzeldatei-Modus (CppFileRunConfiguration) war der zaeheste Teil: er
uebersetzt ohne -I und -std und sieht wie ein Projektfehler aus.

Ausserdem als beobachtete Tatsache statt Theorie: CLions 'Reset Cache and
Reload Project' hat die CMakeCache.txt in build/debug geloescht, weil die
angelegten Preset-Profile dorthin zeigen -- auch deaktiviert. ctest lief
danach weiter, nur cmake --build brach ab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 18:47:34 +02:00

605 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 der Fehler liegt bei
CLion.
### Die Reihenfolge, die tatsächlich hilft
Beim ersten Einrichten dieses Projekts war es genau diese:
1. **Projekt über die `CMakeLists.txt` öffnen**, nicht über den Ordner
(*File → Open…* → die Datei im Wurzelverzeichnis wählen → „Open as
Project"). Ein als Ordner geöffnetes Projekt bekommt kein CMake-Modell,
und CLion bietet stattdessen den **Einzeldatei-Modus** an.
2. **Toolchain setzen** (siehe unten), dann *Tools → CMake → Reset Cache and
Reload Project* — in der **Menüleiste**, nicht in den Einstellungen.
3. **Die Einzeldatei-Konfiguration löschen**: *Run → Edit Configurations…* →
unter „C/C++ File" den Eintrag mit dem Dateinamen → **−**.
Schritt 3 ist der, den man am ehesten übersieht. Diese Konfiguration
(`CppFileRunConfiguration`) uebersetzt eine einzelne Datei **ohne jede
Projekteinstellung**:
```
clang++ .../pda/pda/main.cpp -o main # kein -I, kein -std
```
Solange sie oben rechts ausgewählt ist, scheitert jeder Lauf an
`'libpda/details/version.hpp' file not found` — obwohl das Projekt völlig in
Ordnung ist. Danach steht in derselben Liste `pda_app`, `calculator.test`,
`integration_basics` und so weiter.
### 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.
Und es bleibt nicht bei einem anderen Compiler. Beim Einrichten hier hat
CLions *Reset Cache and Reload Project* die **`CMakeCache.txt` im
Terminal-Build-Verzeichnis geloescht** -- `build/debug` behielt `build.ninja`,
`compile_commands.json` und alle Binaries, nur der Cache war weg:
```
$ cmake --build --preset debug
Error: not a CMake build directory (missing CMakeCache.txt)
```
Kurios dabei: `ctest --preset debug` lief weiter durch, weil
`CTestTestfile.cmake` und die gebauten Testprogramme noch dalagen. Der Schaden
faellt also erst beim naechsten Bauen auf. Behoben mit einem einfachen
`cmake --preset debug`.
Ausgeloest hat das ein **deaktiviertes** Preset-Profil -- CLion legt beim Laden
fuer jeden Preset eines an (hier zehn, teils doppelt als `debug` und
`debug - debug`), und deren `GENERATION_DIR` zeigt auf `build/<preset>`.
Zwei saubere Auswege:
1. **CLion ein eigenes Build-Verzeichnis geben** (Default `cmake-build-debug`,
in `.gitignore` eingetragen) und die Preset-Profile unter *Settings →
Build, Execution, Deployment → CMake* **loeschen**, nicht nur deaktivieren.
2. **Denselben Compiler erzwingen:** unter *Settings → Build, Execution,
Deployment → Toolchains* C/C++-Compiler auf
`/opt/homebrew/opt/llvm/bin/clang` bzw. `clang++` setzen. Die Toolchain muss
dabei **ganz oben** in der Liste stehen (fett = Standard), sonst benutzt das
CMake-Profil weiter die alte. Danach ist ein Cache-Reset Pflicht -- CMake
verweigert den Compilerwechsel sonst:
`You have changed variables that require your cache to be deleted.`
Am besten beides: eigenes Verzeichnis **und** derselbe Compiler. Dann sieht die
IDE dieselben Diagnosen wie das Terminal, ohne sich mit ihm zu schlagen.
## 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.