Add a contended SPSC stress suite (tests/test_channel_stress.cpp) that actually exercises the ring's memory-ordering pairing and spin/futex/ lost-wakeup logic, plus the CMake and CI plumbing to run it under TSan: - KPN_SANITIZER cache var + kpn_sanitizer_flags() helper (no-op when unset) - kpn_tests_stress executable, labelled "stress" for CTest - reusable tsan.yaml workflow (gcc:14 builder image, already ships libtsan) - ci.yaml gains a tsan job on the same code/dockerfile triggers as test Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
113 lines
4.9 KiB
CMake
113 lines
4.9 KiB
CMake
cmake_minimum_required(VERSION 3.21)
|
|
project(kpnpp VERSION 0.1.0 LANGUAGES CXX)
|
|
|
|
set(CMAKE_CXX_STANDARD 20)
|
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
|
|
option(KPN_BUILD_TESTS "Build tests" ON)
|
|
option(KPN_BUILD_PYTHON "Build Python bindings (requires nanobind)" ON)
|
|
option(KPN_BUILD_EXAMPLES "Build examples" ON)
|
|
option(KPN_WEB_DEBUG "Enable web debug UI (cpp-httplib)" OFF)
|
|
|
|
# Sanitizer build. Empty = off. Accepts "thread", "address", "undefined",
|
|
# or a combination like "address,undefined". Applied to all kpn targets via
|
|
# the kpn_sanitizer_flags() helper below.
|
|
#
|
|
# The lock-free SPSC Channel<T> (include/kpn/channel.hpp) has hand-reasoned
|
|
# acquire/release ordering; -DKPN_SANITIZER=thread + the channel stress test
|
|
# (tests/test_channel_stress.cpp) is the dynamic half of verifying it. The
|
|
# static half is the CDSChecker model-check harness (see verify/).
|
|
set(KPN_SANITIZER "" CACHE STRING
|
|
"Build with sanitizer: thread | address | undefined | <combo> (empty = off)")
|
|
|
|
# Translate KPN_SANITIZER into compile/link flags. No-op when empty.
|
|
function(kpn_sanitizer_flags out_var)
|
|
if(KPN_SANITIZER)
|
|
set(${out_var}
|
|
-fsanitize=${KPN_SANITIZER}
|
|
-fno-omit-frame-pointer
|
|
-g
|
|
PARENT_SCOPE)
|
|
else()
|
|
set(${out_var} "" PARENT_SCOPE)
|
|
endif()
|
|
endfunction()
|
|
|
|
# ── Core library (header-only) ────────────────────────────────────────────────
|
|
add_library(kpn INTERFACE)
|
|
target_include_directories(kpn INTERFACE
|
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
$<INSTALL_INTERFACE:include>
|
|
)
|
|
target_compile_features(kpn INTERFACE cxx_std_20)
|
|
|
|
# Threads required by node/channel implementation
|
|
find_package(Threads REQUIRED)
|
|
target_link_libraries(kpn INTERFACE Threads::Threads)
|
|
|
|
# ── Web debug UI (optional) ───────────────────────────────────────────────────
|
|
if(KPN_WEB_DEBUG)
|
|
include(FetchContent)
|
|
FetchContent_Declare(
|
|
cpp-httplib
|
|
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
|
|
GIT_TAG v0.18.0
|
|
)
|
|
FetchContent_MakeAvailable(cpp-httplib)
|
|
# httplib made available but NOT forced onto kpn interface — targets opt in
|
|
# by defining KPN_WEB_DEBUG=1 and linking httplib::httplib themselves.
|
|
# This prevents tests and other examples from pulling in the HTTP server.
|
|
endif()
|
|
|
|
# Convenience function for targets that want web debug
|
|
function(kpn_target_enable_web_debug target)
|
|
target_compile_definitions(${target} PRIVATE KPN_WEB_DEBUG=1)
|
|
target_link_libraries(${target} PRIVATE httplib::httplib)
|
|
endfunction()
|
|
|
|
# ── Tests ─────────────────────────────────────────────────────────────────────
|
|
if(KPN_BUILD_TESTS)
|
|
enable_testing()
|
|
add_subdirectory(tests)
|
|
endif()
|
|
|
|
# ── Benchmarks ────────────────────────────────────────────────────────────────
|
|
option(KPN_BUILD_BENCHMARKS "Build benchmarks" OFF)
|
|
if(KPN_BUILD_BENCHMARKS)
|
|
add_subdirectory(benchmarks)
|
|
endif()
|
|
|
|
# ── Python bindings ───────────────────────────────────────────────────────────
|
|
if(KPN_BUILD_PYTHON)
|
|
find_package(Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED)
|
|
find_package(nanobind CONFIG QUIET)
|
|
if(NOT nanobind_FOUND)
|
|
# Fall back to FetchContent if nanobind not installed system-wide
|
|
include(FetchContent)
|
|
FetchContent_Declare(
|
|
nanobind
|
|
GIT_REPOSITORY https://github.com/wjakob/nanobind.git
|
|
GIT_TAG v2.12.0
|
|
)
|
|
FetchContent_MakeAvailable(nanobind)
|
|
endif()
|
|
add_subdirectory(python)
|
|
endif()
|
|
|
|
# ── Examples ──────────────────────────────────────────────────────────────────
|
|
if(KPN_BUILD_EXAMPLES)
|
|
add_subdirectory(examples)
|
|
endif()
|
|
|
|
# ── Docs (README generation) ──────────────────────────────────────────────────
|
|
find_package(Python3 QUIET COMPONENTS Interpreter)
|
|
if(Python3_FOUND)
|
|
add_custom_target(docs
|
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/render_readme.py
|
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
|
COMMENT "Rendering README.md from README.md.in"
|
|
VERBATIM
|
|
)
|
|
endif()
|