diff --git a/flake.nix b/flake.nix index f639a4a..4f38fee 100644 --- a/flake.nix +++ b/flake.nix @@ -66,6 +66,11 @@ inherit pkgs; backendCommon = rawLib.common; }; + # A failing logos-cpp-generator must fail the headers build; a module + # with no public API must not. + header-generator-guard = import ./tests/test-header-generator-guard.nix { + inherit pkgs; + }; }); # Dev shell for working on the backend itself diff --git a/lib/buildHeaders.nix b/lib/buildHeaders.nix index d89d527..c2c80ca 100644 --- a/lib/buildHeaders.nix +++ b/lib/buildHeaders.nix @@ -84,18 +84,19 @@ # buildPlugin.nix's installPhase from `logos_events:` blocks in # the impl header). Absent for handcrafted Qt modules — that's # fine, we skip the flag in that case. - EVENTS_FROM_FLAG="" + EVENTS_SIDECAR="" if [ -f "${lib}/share/logos/${config.name}.lidl" ]; then - EVENTS_FROM_FLAG="--events-from ${lib}/share/logos/${config.name}.lidl" - echo "Using events sidecar: ${lib}/share/logos/${config.name}.lidl" + EVENTS_SIDECAR="${lib}/share/logos/${config.name}.lidl" + echo "Using events sidecar: $EVENTS_SIDECAR" fi - logos-cpp-generator "$PLUGIN_FILE" --output-dir ./generated_headers \ - --module-only --api-style ${apiStyle} $EVENTS_FROM_FLAG || { - echo "Warning: logos-cpp-generator failed, this may be expected if the module has no public API" - # Create a marker file to indicate attempt was made - touch ./generated_headers/.no-api - } + # The generator's exit status is FATAL — see the header comment of + # generate-module-headers.sh for why "the module has no public API" is + # not a reason to swallow it (that case exits 0 and generates a wrapper + # with zero methods). The script also refuses a zero exit that produced + # no wrapper, so this derivation can never install an empty include/. + bash ${./generate-module-headers.sh} \ + "$PLUGIN_FILE" ./generated_headers "${apiStyle}" "$EVENTS_SIDECAR" runHook postBuild ''; @@ -109,15 +110,19 @@ # Copy all generated files (.h and .cpp) to include/ if they exist. # Both are needed: downstream modules #include the _api.cpp files from # the umbrella logos_sdk.cpp generated by --general-only. + # buildPhase already guarantees the wrapper pair exists; this is the last + # line of defence, and it is an ERROR rather than an empty include/ — + # publishing "no headers" is what made an SDK-pin mismatch look like a + # successful build and blow up later in a downstream link. gen_count=$(find ./generated_headers -maxdepth 1 \( -name '*.h' -o -name '*.cpp' \) 2>/dev/null | wc -l) - if [ "$gen_count" -gt 0 ]; then - echo "Copying generated headers and API files..." - ls -la ./generated_headers - find ./generated_headers -maxdepth 1 \( -name '*.h' -o -name '*.cpp' \) -exec cp {} $out/include/ \; - else - echo "Warning: No generated headers found, creating empty include directory" - echo "# No headers generated by logos-cpp-generator" > $out/include/.generated + if [ "$gen_count" -eq 0 ]; then + echo "Error: no generated headers to install from ./generated_headers" >&2 + ls -la ./generated_headers >&2 || true + exit 1 fi + echo "Copying generated headers and API files..." + ls -la ./generated_headers + find ./generated_headers -maxdepth 1 \( -name '*.h' -o -name '*.cpp' \) -exec cp {} $out/include/ \; runHook postInstall ''; diff --git a/lib/generate-module-headers.sh b/lib/generate-module-headers.sh new file mode 100755 index 0000000..247ef31 --- /dev/null +++ b/lib/generate-module-headers.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# +# Generate a module's SDK client wrapper by introspecting its built Qt plugin. +# +# generate-module-headers.sh [events-sidecar] +# +# Called from lib/buildHeaders.nix. It lives in its own file (rather than +# inline in the buildPhase) because the exit-status handling below is the +# thing that keeps a BROKEN build from looking like a successful one, and +# tests/test-header-generator-guard.nix drives THIS file — so the guard the +# tests check is literally the guard the build runs. +# +# --------------------------------------------------------------------------- +# Why every non-zero status is fatal here +# --------------------------------------------------------------------------- +# "The module has no public API" is a legitimate, non-failing case — but it is +# NOT signalled by a non-zero exit. logos-cpp-generator introspects the +# plugin's QMetaObject: a module that exposes no Q_INVOKABLE method still +# yields a wrapper class (with zero methods) and exits 0. Measured against +# logos-cpp-sdk's generator (plugin-introspection mode, the mode used here): +# +# plugin with a Q_INVOKABLE method -> exit 0, wrote _api.{h,cpp} +# plugin with NO Q_INVOKABLE method -> exit 0, wrote _api.{h,cpp} +# plugin that fails to dlopen -> exit 3, wrote nothing (SDK/ABI skew) +# unknown / retired --api-style -> exit 1, wrote nothing (SDK-pin skew) +# plugin file missing -> exit 2, wrote nothing +# write failure in the output dir -> exit 5/6 +# +# So there is no "expected" failure to swallow. The historical +# `|| { echo Warning...; }` turned an SDK-pin mismatch into a SILENTLY EMPTY +# header set: `nix build .#headers-