feat: PDA als libpda + pda nach P1204R0
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>
This commit is contained in:
+40
-153
@@ -1,180 +1,67 @@
|
||||
# ---------------------------------------------------------------------------
|
||||
# playground
|
||||
# playground - Superprojekt
|
||||
#
|
||||
# Struktur nach P1204R0 "Canonical Project Structure":
|
||||
# playground/playground/ Quellcode UND Header nebeneinander (kein include/ + src/)
|
||||
# playground/tests/ nur Integrationstests gegen die oeffentliche API
|
||||
# *.test.c/.cpp Unit-Tests direkt neben dem getesteten Modul
|
||||
# Dieses Verzeichnis ist KEIN Projekt mit eigenem Quellcode. Es haelt nur zwei
|
||||
# eigenstaendige Projekte zusammen:
|
||||
#
|
||||
# Include-Konvention: eigene Header immer <playground/foo.h>, nie "foo.h".
|
||||
# Spitze Klammern durchsuchen nur die -I-Pfade, niemals das Verzeichnis der
|
||||
# inkludierenden Datei -- ein fehlender Header fliegt damit sofort auf, statt
|
||||
# dass zufaellig ein gleichnamiger lokaler Header gefunden wird.
|
||||
# libpda/ die Bibliothek -> libpda.a bzw. libpda.dylib
|
||||
# pda/ die Anwendung -> bin/pda
|
||||
#
|
||||
# Extensions: .h/.c fuer C, .hpp/.cpp fuer C++. Die Extension sagt also die
|
||||
# Sprache -- in einem gemischten Projekt ist das der Punkt.
|
||||
# P1204R0 verlangt genau diese Trennung ("If a project consists of a library
|
||||
# and an executable, then they should be split into separate projects"). Der
|
||||
# Grund ist nicht Ordnungsliebe: die Bibliothek muss OHNE die Anwendung
|
||||
# baubar, testbar und installierbar sein, sonst ist sie keine Bibliothek,
|
||||
# sondern ein Unterverzeichnis.
|
||||
#
|
||||
# Beide Teilprojekte lassen sich einzeln konfigurieren:
|
||||
#
|
||||
# cmake -S libpda -B build/nur-lib && cmake --build build/nur-lib
|
||||
#
|
||||
# Das ist die Probe aufs Exempel. Wenn das bricht, hat die Bibliothek eine
|
||||
# versteckte Abhaengigkeit zur Anwendung.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# 3.28 = die CMake-Version aus Ubuntu 24.04 LTS. NICHT auf die lokale 4.4
|
||||
# hochziehen: genau das macht hello-cpp (4.4), und das laesst sich auf keinem
|
||||
# LTS-Server mehr konfigurieren.
|
||||
# hochziehen: das laesst sich auf keinem LTS-Server mehr konfigurieren.
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
|
||||
project(playground
|
||||
VERSION 0.1.0
|
||||
DESCRIPTION "Experimentierprojekt nach P1204R0"
|
||||
DESCRIPTION "PDA: Bibliothek und Anwendung"
|
||||
LANGUAGES C CXX)
|
||||
|
||||
# CMAKE_MODULE_PATH ist die Suchliste fuer include(<name>) und
|
||||
# find_package(<name> MODULE). Ohne diese Zeile findet include(Warnings)
|
||||
# nichts.
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Sprachstandards: C23 + C++23, keine GNU-Extensions
|
||||
# ---------------------------------------------------------------------------
|
||||
set(CMAKE_C_STANDARD 23)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# compile_commands.json: HIER, nicht auf der Kommandozeile.
|
||||
# Muss vor dem ersten Target stehen, sonst bekommt clangd eine veraltete Datei.
|
||||
# ---------------------------------------------------------------------------
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# C++20-Modul-Scanning aus. Sonst schreibt Ninja bei CXX_STANDARD 23 ein
|
||||
# "@CMakeFiles/<target>.dir/<datei>.o.modmap" in jeden Compile-Command, und
|
||||
# clangd stolpert ueber die Response-Datei, sobald build/ geleert wurde.
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
|
||||
|
||||
# Ohne explizites CMAKE_BUILD_TYPE waeren alle -O/-g-Flags leer.
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
# Binaries an einen vorhersagbaren Ort, damit die File-API-Artefakte und der
|
||||
# Neovim-Target-Picker stabile Pfade sehen.
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ccache, wenn vorhanden. Ueber COMPILER_LAUNCHER, NICHT ueber die
|
||||
# Compiler-Shims in ccaches libexec -- die faengt jeden Compiler-Aufruf des
|
||||
# gesamten Systems ab.
|
||||
# ---------------------------------------------------------------------------
|
||||
if(NOT CMAKE_C_COMPILER_LAUNCHER AND NOT CMAKE_CXX_COMPILER_LAUNCHER)
|
||||
find_program(CCACHE_PROGRAM ccache)
|
||||
if(CCACHE_PROGRAM)
|
||||
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
message(STATUS "ccache: ${CCACHE_PROGRAM}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(ProjectDefaults)
|
||||
include(Warnings)
|
||||
include(Sanitizers)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kernbibliothek - neue Dateien hier manuell eintragen (kein GLOB).
|
||||
# Optionen. option() legt einen Cache-Eintrag an, den man mit -D ueberschreibt:
|
||||
#
|
||||
# GLOB waere bequem, aber CMake merkt nicht, wenn eine Datei dazukommt: der
|
||||
# Build bleibt gruen und die neue Datei ist einfach nicht dabei.
|
||||
# cmake --preset debug -DPDA_BUILD_TESTS=OFF
|
||||
#
|
||||
# *.test.c und *.test.cpp gehoeren NICHT hierher (P1204R0 Regel 7.1) -- die
|
||||
# werden unten zu eigenen Executables.
|
||||
# BUILD_SHARED_LIBS ist eine CMake-Konvention: add_library() ohne STATIC/SHARED
|
||||
# richtet sich danach. Deshalb hier NICHT selbst erfinden, sondern genau diesen
|
||||
# Namen benutzen -- jedes Werkzeug da draussen kennt ihn.
|
||||
# ---------------------------------------------------------------------------
|
||||
set(PLAYGROUND_SOURCES
|
||||
playground/counter.c
|
||||
playground/notebook.cpp
|
||||
)
|
||||
option(BUILD_SHARED_LIBS "Bibliothek als Shared Library bauen" OFF)
|
||||
option(PDA_BUILD_TESTS "Unit- und Integrationstests bauen" ON)
|
||||
|
||||
set(PLAYGROUND_HEADERS
|
||||
playground/counter.h
|
||||
playground/notebook.hpp
|
||||
playground/details/bits.h
|
||||
)
|
||||
|
||||
add_library(playground_core STATIC ${PLAYGROUND_SOURCES} ${PLAYGROUND_HEADERS})
|
||||
|
||||
# Der Include-Root ist das PROJEKTWURZELVERZEICHNIS, nicht playground/playground.
|
||||
# Nur so loest <playground/counter.h> auf playground/playground/counter.h auf.
|
||||
target_include_directories(playground_core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
target_link_libraries(playground_core PRIVATE playground_warnings)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Programm
|
||||
# ---------------------------------------------------------------------------
|
||||
add_executable(playground playground/main.cpp)
|
||||
target_link_libraries(playground PRIVATE playground_core playground_warnings)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests
|
||||
# ---------------------------------------------------------------------------
|
||||
enable_testing()
|
||||
|
||||
# Fehlt third_party/, bleibt das Projekt konfigurierbar (clangd funktioniert)
|
||||
# und nur die Unit-Test-Targets entfallen.
|
||||
set(PLAYGROUND_HAVE_UNITY FALSE)
|
||||
set(PLAYGROUND_HAVE_DOCTEST FALSE)
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/unity/unity.c")
|
||||
set(PLAYGROUND_HAVE_UNITY TRUE)
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/doctest/doctest.h")
|
||||
set(PLAYGROUND_HAVE_DOCTEST TRUE)
|
||||
# enable_testing() gehoert ins OBERSTE CMakeLists.txt und nirgendwo sonst.
|
||||
# Nur hier legt es die CTestTestfile.cmake an, die ctest im Wurzel-Build-
|
||||
# verzeichnis sucht.
|
||||
if(PDA_BUILD_TESTS)
|
||||
enable_testing()
|
||||
endif()
|
||||
|
||||
if(PLAYGROUND_HAVE_UNITY OR PLAYGROUND_HAVE_DOCTEST)
|
||||
add_subdirectory(third_party)
|
||||
else()
|
||||
message(WARNING "third_party/ ist leer -- keine Unit-Test-Targets.")
|
||||
endif()
|
||||
|
||||
# Unit-Test in C: <modul>.test.c -> Executable <modul>.test -> CTest
|
||||
# "unity.<modul>". Der neotest-Adapter in Neovim verlaesst sich auf genau
|
||||
# diesen Namen.
|
||||
function(playground_add_unity_test module)
|
||||
if(NOT PLAYGROUND_HAVE_UNITY)
|
||||
return()
|
||||
endif()
|
||||
set(target "${module}.test")
|
||||
add_executable(${target} "playground/${module}.test.c")
|
||||
target_link_libraries(${target} PRIVATE playground_core unity playground_warnings)
|
||||
add_test(NAME "unity.${module}" COMMAND ${target})
|
||||
endfunction()
|
||||
|
||||
# Unit-Test in C++: <modul>.test.cpp. doctest_discover_tests() ruft die
|
||||
# Executable beim Build mit --list-test-cases auf und legt EINEN CTest-Eintrag
|
||||
# pro TEST_CASE an. Das ist Pflicht, nicht Kosmetik: neotest-ctest kann
|
||||
# Testnamen nur so auf Quellpositionen abbilden.
|
||||
function(playground_add_doctest_test module)
|
||||
if(NOT PLAYGROUND_HAVE_DOCTEST)
|
||||
return()
|
||||
endif()
|
||||
set(target "${module}.test")
|
||||
add_executable(${target} "playground/${module}.test.cpp")
|
||||
target_link_libraries(${target} PRIVATE playground_core doctest playground_warnings)
|
||||
doctest_discover_tests(${target} TEST_PREFIX "doctest.")
|
||||
endfunction()
|
||||
|
||||
playground_add_unity_test(counter)
|
||||
playground_add_doctest_test(notebook)
|
||||
|
||||
# Integrationstests (P1204R0 Regel 7.2): eigenes tests/-Verzeichnis, laufen
|
||||
# gegen die oeffentliche API -- also genau gegen das, was ein Benutzer bekommt.
|
||||
add_subdirectory(tests)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Install
|
||||
# ---------------------------------------------------------------------------
|
||||
install(TARGETS playground RUNTIME DESTINATION bin)
|
||||
install(DIRECTORY playground/
|
||||
DESTINATION "include/playground"
|
||||
FILES_MATCHING
|
||||
PATTERN "*.h"
|
||||
PATTERN "*.hpp"
|
||||
PATTERN "private" EXCLUDE)
|
||||
add_subdirectory(third_party)
|
||||
add_subdirectory(libpda)
|
||||
add_subdirectory(pda)
|
||||
|
||||
message(STATUS "playground ${PROJECT_VERSION} | C${CMAKE_C_STANDARD} C++${CMAKE_CXX_STANDARD} | "
|
||||
"${CMAKE_BUILD_TYPE} | ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
"${CMAKE_BUILD_TYPE} | ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} | "
|
||||
"shared=${BUILD_SHARED_LIBS} tests=${PDA_BUILD_TESTS}")
|
||||
|
||||
Reference in New Issue
Block a user