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>
2.8 KiB
ADR 0002 — Aufteilung in libpda und pda
- Status: akzeptiert
- Datum: 2026-08-30
Kontext
Das Projekt soll einen kleinen PDA umsetzen (Kontakte, Rechner, Notizen, Dateiexplorer) und dabei ausdrücklich als Bibliothek benutzbar sein — man soll damit eigene Sachen bauen können.
P1204R0 sagt dazu: „If a project consists of a library and an executable, then they should be split into separate projects."
Die naheliegende Alternative wäre ein Projekt mit einem pda_core-Target und
einer Executable daneben — so machen es mydb und cxx_scaffold_cli.
Entscheidung
Zwei eigenständige Projekte unter einem Superprojekt:
libpda/mit eigenemproject(libpda), exportiertpda::pdapda/mit eigenemproject(pda), linktpda::pda
Beide sind einzeln konfigurierbar. pda/CMakeLists.txt benutzt im
Alleinbetrieb find_package(pda REQUIRED).
Konsequenzen
Die Bibliothek ist nachweislich unabhängig: cmake -S libpda -B build/nur-lib
baut und testet sie ohne die Anwendung (47 der 71 Tests).
Die Anwendung ist damit gleichzeitig der Integrationstest für den Export-Mechanismus. Was sie im Alleinbetrieb tut, tut auch jedes fremde Projekt.
examples/consumer/ und tools/check-install.sh machen das explizit: das
Skript installiert libpda in ein temporäres Präfix und baut ein fremdes
Programm dagegen. Genau diese Kette fängt den häufigsten Verteilungsfehler —
ein target_include_directories ohne BUILD_INTERFACE/INSTALL_INTERFACE
zeigt beim Benutzer ins Leere und fällt im eigenen Build nie auf.
Der Preis ist eine Ebene mehr im Pfad (libpda/libpda/contact.hpp) und der
PROJECT_IS_TOP_LEVEL-Block in beiden Teilprojekten. Beides ist der Preis
dafür, dass „Bibliothek" hier nicht nur ein Wort ist.
Nebenentscheidungen
Ein Namespace für beide Projekte (namespace pda), unterschieden über den
Include-Pfad — so wie libhello/hello in P1204R0. Das lib-Präfix steht im
Projekt- und Verzeichnisnamen, nicht im Namespace.
Die C-Schicht ist echt, nicht dekorativ. textfile.c macht Datei-I/O und
Feld-Escaping — die Stelle, an der Besitzverhältnisse und rohe Puffer
sichtbar sind. Alles darüber ist C++ und verpackt das in RAII. Die Naht ist in
contact.cpp zu sehen: unique_ptr mit eigenem Deleter um die char*, die
textfile_escape liefert.
std::expected statt Exceptions für Benutzerfehler (Tippfehler im
Ausdruck, fehlende Datei). Ein Parser-Fehler ist der Normalfall dieser
Funktionen, kein Ausnahmezustand. Exceptions bleiben für das, was wirklich
nicht vorgesehen ist.
Die Shell kennt keine Ein-/Ausgabe. Shell::execute() bekommt eine Zeile
und gibt Text zurück; wer ihn anzeigt, ist ihre Sache nicht. Deshalb braucht
kein einziger der Shell-Tests ein Terminal, und eine GUI ließe sich ohne
Änderung an shell.cpp davorsetzen.