Ersetzt das Geruest (counter/notebook) durch ein echtes Beispiel und teilt es in zwei eigenstaendige Projekte, wie P1204R0 es fuer Bibliothek plus Programm verlangt. libpda: textfile (C, Datei-I/O und Feld-Escaping), calculator (Recursive-Descent-Parser mit std::expected), contact, editor, explorer. Exportiert pda::pda ueber install(EXPORT) und ist per find_package(pda) benutzbar. pda: Shell ohne Ein-/Ausgabe (execute() gibt Text zurueck, deshalb ohne Terminal testbar), REPL und Stapelbetrieb in main.cpp. examples/consumer und tools/check-install.sh pruefen die Export-Kette Ende-zu-Ende: installieren, dann ein fremdes Projekt dagegen bauen. docs/CMAKE.md erklaert das Target-Modell, PUBLIC/PRIVATE/INTERFACE, Generator-Ausdruecke, install/export und Symbolsichtbarkeit an diesem Projekt. 71 Tests gruen unter Homebrew-clang 22, Apple clang und GCC 16, statisch und shared, mit und ohne ASan/UBSan. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.1 KiB
Konventionen
Was clang-format und clang-tidy nicht erzwingen können, steht hier.
Mechanisch erzwungen wird bisher nur, was clang-format und clang-tidy
abdecken. Ein check_style.sh gibt es in diesem Repo nicht (die
CONVENTIONS.md von mydb verweist darauf, aber die Datei existiert dort
ebenfalls nicht) — die folgenden Regeln gelten also per Konvention.
Projektstruktur (P1204R0)
Zwei eigenständige Projekte unter einem Superprojekt:
playground/ Superprojekt, kein eigener Quellcode
├── libpda/ project(libpda) -> libpda.a
│ ├── libpda/ Quellcode UND Header zusammen
│ │ ├── textfile.h public API (C)
│ │ ├── textfile.c
│ │ ├── textfile.test.c Unit-Test, direkt neben dem Modul
│ │ ├── contact.hpp public API (C++)
│ │ ├── contact.cpp
│ │ ├── contact.test.cpp
│ │ └── details/ Implementation Details, nicht public API
│ └── tests/basics/ Integrationstests gegen die öffentliche API
├── pda/ project(pda) -> bin/pda
│ ├── pda/ shell.hpp/.cpp, main.cpp
│ └── tests/session/
├── examples/consumer/ fremdes Projekt, find_package(pda)
├── third_party/ vendorter Fremdcode, nie editieren
├── cmake/ ProjectDefaults, Warnings, Sanitizers, pdaConfig.cmake.in
└── docs/ CMAKE.md, diese Datei, ADRs
Kein include/ + src/. Header und Implementierung liegen nebeneinander.
Bei Templates, inline und Modulen ist die Trennung ohnehin nicht sauber zu
ziehen, und der Verzeichnisname übernimmt die Rolle des Namespace im
Dateisystem.
Bibliothek und Programm sind getrennte Projekte. P1204R0 verlangt das; der
praktische Grund ist, dass libpda ohne pda baubar sein muss. Die Probe:
cmake -S libpda -B build/nur-lib && cmake --build build/nur-lib
Ein Namespace, zwei Include-Wurzeln. Beide Projekte benutzen
namespace pda — auseinandergehalten wird über den Pfad:
#include <libpda/contact.hpp> // Bibliothek
#include <pda/shell.hpp> // Anwendung
Das ist genau das Schema aus P1204R0 (libhello / hello): das lib-Präfix
steht im Projekt- und Verzeichnisnamen, nicht im Namespace.
Includes
Eigene Header immer mit spitzen Klammern und Projektpräfix:
#include <libpda/textfile.h> /* richtig */
#include "textfile.h" /* falsch */
Das ist P1204R0s wichtigste Einzelregel. Spitze Klammern durchsuchen nur die
-I-Pfade, niemals das Verzeichnis der inkludierenden Datei — ein fehlender
oder falsch installierter Header fällt damit sofort auf, statt dass zufällig
ein gleichnamiger Nachbar gefunden wird.
Reihenfolge macht clang-format (IncludeBlocks: Regroup): eigener Header,
Projekt-Header, Test-Framework, C-Standardbibliothek, C++-Standardbibliothek,
Rest.
Extensions
| Extension | Bedeutung |
|---|---|
.h / .c |
C |
.hpp / .cpp |
C++ |
.test.c / .test.cpp |
Unit-Test neben dem getesteten Modul |
Die Extension sagt die Sprache. In einem gemischten Projekt ist das der Punkt —
ein .h ist aus C und C++ benutzbar (der extern "C"-Block im Header),
ein .hpp nur aus C++.
Namen
| Was | Form | Beispiel |
|---|---|---|
| Typen | PascalCase |
ContactBook, TextBuffer, Explorer |
| Funktionen, Variablen | snake_case |
human_size, line_count |
| Makros | UPPER_CASE mit Projektpräfix |
LIBPDA_TEXTFILE_H, PDA_EXPORT |
| C-API | Modulpräfix | textfile_read, textfile_write, textfile_escape |
| Out-Parameter | out_-Präfix |
size_t* out_len |
| Member | blank, kein m_, kein _ |
entries, rows, here |
| Namespace | Projektname ohne lib |
namespace pda |
Kommentare
Ausschließlich /* */, niemals // — auch nicht einzeilig. Abschnitte werden
so getrennt:
/* ---- API ---- */
Kommentare sind auf Deutsch, Umlaute transliteriert (ae oe ue ss), damit
die Quellen reines ASCII bleiben. README und öffentliche Header auf Englisch.
Prüfen lässt sich das mit:
grep -rlP '[^\x00-\x7F]' playground tests
Header
#ifndef LIBPDA_TEXTFILE_H — Include-Guards, kein #pragma once.
Öffentliche C-Header bekommen einen extern "C"-Block, damit die C++-Seite sie
benutzen kann.
Structs, deren Layout niemanden angeht, sind opak: typedef struct Page Page;
im Header, die Definition in der .c.
Tests
Unit-Test (<modul>.test.c / .test.cpp) liegt neben dem Modul, kennt
dessen Interna und ist eine eigenständige Executable.
- C → Unity, registriert als CTest
unity.<modul>(hier:unity.textfile) - C++ → doctest,
doctest_discover_tests()legt einen CTest-Eintrag proTEST_CASEan
Integrationstest (tests/<name>/driver.c oder .cpp) benutzt nur die öffentliche API,
kein Framework, Ergebnis über den Exit-Code. Er würde genauso gegen eine
installierte Bibliothek laufen.
TDD-Zyklus: RED (Test schlägt aus dem richtigen Grund fehl) → GREEN (kleinste Implementierung) → REFACTOR.
Build
Die ausführliche Erklärung steht in CMAKE.md. Das Wichtigste:
- Öffentliche Deklarationen der Bibliothek brauchen
PDA_EXPORT, sonst exportiert der Shared-Bau sie nicht. - Neue Dateien immer von Hand in
LIBPDA_SOURCES/LIBPDA_HEADERS.
Kein file(GLOB). Neue Dateien werden in CMakeLists.txt eingetragen — CMake
merkt sonst nicht, dass eine Datei dazugekommen ist, der Build bleibt grün und
die Datei fehlt einfach.
| Preset | Zweck |
|---|---|
debug |
Alltag |
release |
RelWithDebInfo |
asan-ubsan |
AddressSanitizer + UBSan |
apple-clang |
Portabilitätsprobe (macOS-System-Compiler) |
homebrew-gcc |
Portabilitätsprobe (GCC-Frontend) |
linux-gcc |
nur auf Linux sichtbar |
build/<preset>/ — 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.
Commits
Conventional Commits: feat:, fix:, test:, refactor:, chore:, docs:.
Aktuell ohne Hook — es gibt kein check_style.sh, das einen installieren
könnte.
Zwei Dinge, die wie Fehler aussehen, aber keine sind
Nach einem frischen Clone zeigt clangd nichts. Es gibt noch kein
compile_commands.json. Einmal <leader>oc (configure), dann ist alles da.
Neovim weist beim Öffnen einer C/C++-Datei darauf hin.
Der Testbaum (<leader>ts) ist nach einem frischen Clone leer.
doctest_discover_tests() ruft die Test-Executable beim Build auf, um die
TEST_CASEs aufzulisten. Vor dem ersten Build kennt CTest sie also nicht. Einmal
<leader>b, dann füllt sich der Baum.
Rückfallebene für die C-Tests
Der neotest-Adapter für Unity (nvim-local/neotest-unity) ist selbst
geschrieben, weil neotest-ctest .c-Dateien grundsätzlich nicht sieht. Wenn er
je zur Last wird: aus der adapters-Liste in nvim/lua/plugins/neotest.lua
entfernen, neotest bleibt für C++ zuständig, und die C-Tests laufen über
<leader>ot (overseer → ctest → Quickfix). Dann fehlt der Rot/Grün-Baum für C,
aber ein Tastendruck und Sprung zur Fehlerstelle bleiben.