Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 23 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
SCCACHE_DIR: /home/runner/.cache/sccache
CLANG_VERSION: "20"
CLANG_VERSION: "22"

jobs:
# ── Windows: MSVC + clang-cl ──────────────────────────────────────────
Expand Down Expand Up @@ -65,13 +65,16 @@ jobs:
key: apt-compilers-${{ matrix.preset }}-${{ hashFiles('.github/workflows/ci.yml') }}
restore-keys: apt-compilers-${{ matrix.preset }}-

- name: Install GCC 14 and ninja
- name: Install GCC 15 and ninja
if: startsWith(matrix.preset, 'gcc-')
run: |
sudo apt-get update -q
sudo apt-get install -y gcc-14 g++-14 ninja-build catch2
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 14
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 14
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update -q
sudo apt-get install -y gcc-15 g++-15 ninja-build catch2
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 15
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-15 15

- name: Install Clang ${{ env.CLANG_VERSION }} from apt.llvm.org
if: startsWith(matrix.preset, 'clang-')
Expand Down Expand Up @@ -202,13 +205,16 @@ jobs:
key: apt-qt-${{ hashFiles('.github/workflows/ci.yml') }}
restore-keys: apt-qt-

- name: Install GCC 14, ninja, catch2, Qt6 WebSockets
- name: Install GCC 15, ninja, catch2, Qt6 WebSockets
run: |
sudo apt-get update -q
sudo apt-get install -y gcc-14 g++-14 ninja-build catch2 \
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update -q
sudo apt-get install -y gcc-15 g++-15 ninja-build catch2 \
qt6-base-dev qt6-websockets-dev qt6-tools-dev libgl1-mesa-dev
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 14
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 14
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 15
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-15 15

- name: Cache sccache
uses: actions/cache@v4
Expand Down Expand Up @@ -251,12 +257,15 @@ jobs:
key: apt-valgrind-${{ hashFiles('.github/workflows/ci.yml') }}
restore-keys: apt-valgrind-

- name: Install GCC 14, ninja, catch2, valgrind
- name: Install GCC 15, ninja, catch2, valgrind
run: |
sudo apt-get update -q
sudo apt-get install -y gcc-14 g++-14 ninja-build catch2 valgrind
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 14
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 14
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update -q
sudo apt-get install -y gcc-15 g++-15 ninja-build catch2 valgrind
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 15
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-15 15

- name: Cache sccache
uses: actions/cache@v4
Expand Down Expand Up @@ -355,7 +364,7 @@ jobs:
-p1 \
-j "$(nproc)" \
-extra-arg=-std=c++23 \
-warnings-as-errors='*' \
-extra-arg=-warnings-as-errors=* \
-quiet \
2>&1 | tee clang-tidy-report.txt

Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update -q
sudo apt-get install -y cmake ninja-build doxygen g++-14
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 14
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 14
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt-get update -q
sudo apt-get install -y cmake ninja-build doxygen g++-15
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-15 15
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-15 15

- name: Configure (docs target only)
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
*.user
*.suo
.vs/
bv-clang/
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ endif()
option(MORPH_BUILD_QT "Build Qt6 WebSocket backend and tests" OFF)
option(MORPH_BUILD_DOCUMENTATION "Build doxygen docs" OFF)
option(MORPH_BUILD_CLANG_TIDY "Enable clang-tidy checks (warnings-as-errors)" OFF)
option(MORPH_ENABLE_STRICT_COMPILATION
"Enable strict compilation: treat all warnings as errors with extra diagnostics"
ON
)

# Both Qt consumers (the WebSocket backend and the forms QML renderer) need
# Qt6 auto-detected on Windows before their find_package(Qt6 ...) calls run.
Expand Down
146 changes: 130 additions & 16 deletions cmake/compiler_options.cmake
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
function(apply_warnings target)
target_compile_options(${target} PRIVATE
# ── MSVC ──────────────────────────────────────────────────────────────
$<$<CXX_COMPILER_ID:MSVC>:
/W4
/permissive-
Expand All @@ -25,23 +26,16 @@ function(apply_warnings target)
/w14928 # illegal copy-initialization
/wd4068 # suppress: unknown pragma (e.g. clang pragmas in shared headers)
>
$<$<STREQUAL:${CMAKE_CXX_COMPILER_ID},Clang>:$<$<BOOL:${MSVC}>:
-Wno-c++98-compat
-Wno-c++98-compat-pedantic
-Wno-pre-c++17-compat
-Wno-ctad-maybe-unsupported
-Wno-exit-time-destructors
-Wno-global-constructors
-Wno-shadow-uncaptured-local
-Wno-unused-command-line-argument
>>

$<$<CXX_COMPILER_ID:GNU,Clang>:
# ── Clang: -Weverything, then subtract the noise ─────────────────────
# Enable every warning Clang has (Linux clang and clang-cl alike), so a
# compiler upgrade opts us into new diagnostics automatically. The
# suppressions below are the *only* categories we turn back off; anything
# not listed stays an error under -Werror.
$<$<CXX_COMPILER_ID:Clang>:-Weverything>
$<$<CXX_COMPILER_ID:GNU>:
-Wall
-Wextra
-Wpedantic
>
$<$<CXX_COMPILER_ID:Clang>:
-Wshadow
-Wnon-virtual-dtor
-Wold-style-cast
Expand All @@ -51,9 +45,129 @@ function(apply_warnings target)
-Wconversion
-Wsign-conversion
-Wmisleading-indentation
-Wnull-dereference
-Wimplicit-fallthrough
>
# -Wnull-dereference: Clang gets it free via -Weverything. On GCC the
# optimizer emits false positives from inside libstdc++ (std::function)
# at -O2+, and the diagnostic leaks out of the system header (it runs
# post-inlining) where -isystem cannot suppress it, and it is a no-op at
# -O0 — so it is left off for GCC entirely.
)

# ── Clang -Weverything suppressions (must come *after* -Weverything) ─────
target_compile_options(${target} PRIVATE
$<$<CXX_COMPILER_ID:Clang>:
# (a) Back-compat warnings — irrelevant: we target C++23, so being
# "incompatible with C++98/14/17/20" is the whole point.
-Wno-c++98-compat
-Wno-c++98-compat-pedantic
-Wno-pre-c++14-compat
-Wno-pre-c++17-compat
-Wno-pre-c++17-compat-pedantic
-Wno-pre-c++20-compat
-Wno-pre-c++20-compat-pedantic
# (b) Inherent to a header-only, templated library.
-Wno-weak-vtables # vtable emitted per TU for inline-virtual classes
-Wno-ctad-maybe-unsupported # CTAD on types without explicit deduction guides
-Wno-padded # struct tail/inter-member padding
-Wno-exit-time-destructors # function-local statics with non-trivial dtors
-Wno-global-constructors # non-trivial namespace-scope initializers
# (c) Stylistic / opinionated noise, not defects.
-Wno-missing-noreturn
-Wno-nrvo # not eliding a trivial-type copy on return
-Wno-shadow-uncaptured-local # lambda param shadowing an uncaptured local
-Wno-documentation-unknown-command
-Wno-unsafe-buffer-usage # flags all pointer arithmetic; needs a hardened API
-Wno-unsafe-buffer-usage-in-libc-call
-Wno-float-equal # exact == is intentional in the value/rational tests
# (d) Conflicts with a warning we deliberately keep.
-Wno-covered-switch-default # collides with -Wswitch-enum + -Wswitch-default
# (e) Third-party test macros.
-Wno-c2y-extensions # Catch2 TEST_CASE expands __COUNTER__
-Wno-unused-member-function # Catch2/test-fixture helper members
-Wno-unneeded-member-function
>
# clang-cl only: driver noise from the MSVC-mode command line.
$<$<STREQUAL:${CMAKE_CXX_COMPILER_ID},Clang>:$<$<BOOL:${MSVC}>:
-Wno-unique-object-duplication
-Wno-unused-command-line-argument
>>
)

# ── Strict mode: warnings-as-errors + maximum diagnostics ──────────────
# Controlled by MORPH_ENABLE_STRICT_COMPILATION (default ON in CI).
if(MORPH_ENABLE_STRICT_COMPILATION)
target_compile_options(${target} PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Werror>
$<$<CXX_COMPILER_ID:MSVC>:/WX>
# Clang needs no extra named flags here — -Weverything (above) already
# supersets every one of these. This block is the GCC equivalent of
# "-Weverything": the widest practical set, since GCC has no such flag.
$<$<CXX_COMPILER_ID:GNU>:
# Diagnostics also carried on Clang via -Weverything.
-Wcast-qual
-Wformat=2
-Wredundant-decls
-Winit-self
-Wmissing-include-dirs
-Wundef
-Wswitch-default
-Wswitch-enum
-Wctor-dtor-privacy
-Wpacked
-Wdouble-promotion
-Wformat-security
-Wformat-nonliteral
-Wmissing-declarations
-Warray-bounds
# GCC-specific analyses.
-Wduplicated-cond
-Wduplicated-branches
-Wlogical-op
-Wuseless-cast
-Walloc-zero
-Wstringop-truncation
-Wsuggest-override
-Wsubobject-linkage
-Wtrampolines
-Wconditionally-supported
# Widened set (max diagnostics).
-Wstrict-overflow=2
-Wformat-overflow=2
-Wformat-truncation=2
-Wformat-signedness
-Wshift-overflow=2
-Wstringop-overflow=4
# -Wnoexcept is intentionally omitted: it leaks out of libstdc++
# <type_traits> when Catch2's (potentially-throwing) test lambdas
# are passed to std::is_nothrow_invocable, which is noise from
# third-party code, not a defect in ours.
-Wnoexcept-type
-Wredundant-tags
-Wmismatched-tags
-Wvolatile
-Wzero-as-null-pointer-constant
-Wextra-semi
-Wsign-promo
-Wcatch-value=3
-Wplacement-new=2
-Wunused-const-variable=2
-Wdisabled-optimization
-Wenum-conversion
-Warith-conversion
-Wdate-time
-Wattribute-alias=2
-Wcast-align=strict
-Wunsafe-loop-optimizations
>
)
endif()
# ${MSVC} is true for both cl and clang-cl (any compiler targeting the MSVC
# runtime), so clang-cl also gets _CRT_SECURE_NO_WARNINGS — its CXX_COMPILER_ID
# is "Clang", which the CXX_COMPILER_ID:MSVC genex would miss, leaving CRT
# functions like fopen flagged as deprecated under -Werror.
target_compile_definitions(${target} PRIVATE
$<$<BOOL:${MSVC}>:_CRT_SECURE_NO_WARNINGS>
)
endfunction()

Expand Down Expand Up @@ -81,4 +195,4 @@ function(apply_coverage target)
-fprofile-instr-generate -fcoverage-mapping -g -O0)
target_link_options(${target} PRIVATE
-fprofile-instr-generate)
endfunction()
endfunction()
2 changes: 1 addition & 1 deletion examples/forms/lab_units.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ struct morph::units::UnitTraits<lab::Unit> {
case lab::Unit::g: return {"g", "g", 1};
case lab::Unit::t: return {"t", "t", 4};
case lab::Unit::l: return {"l", "L", 1};
default: return {"?", "?", 3};
}
return {"?", "?", 3};
}

/// @brief Within-dimension conversion ratios (mass g/t ↔ kg, volume L ↔ m³).
Expand Down
1 change: 0 additions & 1 deletion include/morph/action_log.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ inline void setActionLog(std::shared_ptr<IActionLog> log) {
/// case's sink never leaks into the next) and for applications that need to
/// temporarily redirect auto-attached logging within a scope.
///
/// @par Example
/// @code
/// {
/// morph::journal::ScopedActionLog guard{std::make_shared<morph::journal::InMemoryActionLog>()};
Expand Down
4 changes: 2 additions & 2 deletions include/morph/backend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ class LocalBackend : public detail::IBackend {
}
}
for (auto& [modelId, holder] : sinks) {
_strand.post(modelId, [holder = std::move(holder)]() mutable {
auto* sink = dynamic_cast<::morph::model::detail::IBackendChangedSink*>(holder.get());
_strand.post(modelId, [h = std::move(holder)]() mutable {
auto* sink = dynamic_cast<::morph::model::detail::IBackendChangedSink*>(h.get());
if (sink != nullptr) {
sink->onBackendChanged();
}
Expand Down
6 changes: 3 additions & 3 deletions include/morph/datetime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,12 @@ template <>
struct from<JSON, morph::time::DateTime> {
template <auto Opts>
static void op(auto&& value, is_context auto&& ctx, auto&& iter, auto end) {
std::string text{};
parse<JSON>::op<Opts>(text, ctx, iter, end);
std::string str{};
parse<JSON>::op<Opts>(str, ctx, iter, end);
if (bool(ctx.error)) [[unlikely]] {
return;
}
if (auto parsed = morph::time::DateTime::fromIso8601(text); parsed.has_value()) {
if (auto parsed = morph::time::DateTime::fromIso8601(str); parsed.has_value()) {
value = *parsed;
return;
}
Expand Down
2 changes: 1 addition & 1 deletion include/morph/forms.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ constexpr void reconcileDeclaredPrecision(A& action) {
constexpr auto memberCount = glz::reflect<Plain>::size;
auto memberTie = glz::to_tie(action);
[&]<std::size_t... I>(std::index_sequence<I...>) {
auto retag = [&]<std::size_t Idx>() {
[[maybe_unused]] auto retag = [&]<std::size_t Idx>() {
auto& member = glz::get_member(action, get<Idx>(memberTie));
using Member = std::remove_cvref_t<decltype(member)>;
if constexpr (units::isQuantity<Member>) {
Expand Down
10 changes: 5 additions & 5 deletions include/morph/logger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ constexpr std::string_view levelName(LogLevel level) noexcept {
return "ERROR";
case LogLevel::off:
return "OFF ";
default:
return "? ";
}
return "? ";
}

/// @brief Sink function type used internally for log output.
Expand All @@ -52,11 +53,11 @@ using Logger = std::function<void(LogLevel, std::string_view)>;
/// lines (log injection).
///
/// A message is a single logical record; the default sink emits it as one line.
/// User-controlled text containing `\n`, `\r`, or other C0 control bytes could
/// User-controlled text containing \n, \r, or other C0 control bytes could
/// otherwise splice a forged `[ERROR] ...` line into the stream or corrupt a
/// line-oriented log parser. This replaces CR/LF/TAB with their C-style escapes
/// (`\n`, `\r`, `\t`) and any remaining control byte (`< 0x20`, or `0x7f` DEL)
/// with a `\xHH` escape, leaving printable text (including non-ASCII UTF-8
/// (\n, \r, \t) and any remaining control byte (< 0x20, or 0x7f DEL)
/// with a \xHH escape, leaving printable text (including non-ASCII UTF-8
/// continuation bytes `>= 0x80`) untouched. It is a cheap single pass with no
/// per-byte escaping work on the common (clean) path (just one string copy).
/// @param msg Raw message to sanitize.
Expand Down Expand Up @@ -220,7 +221,6 @@ void logError(std::format_string<Args...> fmt, Args&&... args) {
/// custom sink into other tests. Thread-safe construction and destruction —
/// the global mutex is acquired briefly during each.
///
/// @par Example
/// @code
/// {
/// std::vector<std::string> captured;
Expand Down
3 changes: 3 additions & 0 deletions include/morph/offline_queue.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,14 @@ struct IOfflineQueue {
/// @param payload Serialised action to persist.
/// @param idempotencyKey Stable dedup token for the logical op; may be empty.
/// @return A stable id that can be passed to `markDone()`.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
virtual uint64_t enqueue(std::string payload, std::string idempotencyKey) {
const uint64_t itemId = enqueue(std::move(payload));
setIdempotencyKey(itemId, std::move(idempotencyKey));
return itemId;
}
#pragma GCC diagnostic pop

/// @brief Returns all pending items in enqueue order without removing them.
///
Expand Down
Loading
Loading