Files
Dario LipicarandClaude Opus 4.8 24cec3569b Cdylib module interface: uniform Qt glue over the common module-impl C ABI (#119)
* Wire the Qt-split SDK stack + stamp the protocol version into modules

- New inputs logos-protocol and logos-qt-sdk, injected into both build
  pipelines (mkLogosModule + buildCppPlugin): modules now link
  logos-qt-sdk (which chains logos-protocol) while the Qt-free
  logos-cpp-sdk provides the std headers + code generator.
- cmake/LogosModule.cmake synced with logos-plugin-qt's (it had
  drifted): three-root resolution (LOGOS_CPP_SDK_ROOT /
  LOGOS_QT_SDK_ROOT / LOGOS_PROTOCOL_ROOT), links
  logos-qt-sdk::logos_qt_sdk + logos-cpp-sdk::logos_headers.
- Protocol-version stamp: the logos-protocol semver (parsed from
  logos_protocol.h) is injected into each module's metadata.json before
  moc embeds it (modulePreConfigure.stampProtocolVersion, jq). liblogos
  reads it pre-load to decide compatibility; modules from older
  builders lack the field and load as legacy.

Builder-based modules need only a flake.lock bump — no metadata.json or
source changes.

* fix: rebase LogosModule.cmake qt-split patch onto master's copy

The previous commit synced this file from logos-plugin-qt's lineage, which
silently dropped master-only blocks the universal-module path depends on:
the generated_code/ glob (compiles the generated glue + provider dispatch
into the plugin), the metadata.json configure_file for AUTOMOC, the
LOGOS_API_STYLE selection, static-archive external libs, and the Go/cgo
whole-archive block. Universal modules built without any Qt-plugin glue
(no QTMETADATA) and capability_module dlopen-failed on a missing impl
vtable, killing the token flow and with it all cross-module IPC.

This restores master's file and re-applies only the qt-split changes:
logos_module_context.h detection for the Qt-free base SDK, qt-sdk/protocol
root resolution, qt-sdk source-layout sources, the split include dirs, and
exported-target linking (logos-qt-sdk::logos_qt_sdk / logos-protocol /
logos-cpp-sdk::logos_headers).

Verified: logos-test-modules--tests check passes end-to-end with this
builder (universal modules load, capability token flow works, IPC green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* lock: pin extraction-chain branch revs for standalone CI

Temporary — drop when the chain PRs merge (re-lock against masters).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tests): thread qt-sdk/protocol roots into mkLogosModuleTests

logos-test-framework's LogosTest.cmake now resolves three SDK roots
(qt split); the test builder passes -DLOGOS_QT_SDK_ROOT /
-DLOGOS_PROTOCOL_ROOT and the packages on buildInputs. flake.lock pins
logos-test-framework to its qt-split branch (logos-co/logos-test-framework#4)
— temporary, drop when the chain merges.

Verified: the test-framework-integration check passes locally with these
changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: forward logos-protocol/logos-qt-sdk to the inner mkLogosModuleTests import

mkLogosModuleTests.nix gained required logos-protocol/logos-qt-sdk args
in b24c2b0, but only lib/default.nix's import was updated — the second
import inside mkLogosModule.nix (the per-module auto-tests path) still
passed the old arg set, failing evaluation for any module flake whose
checks go through mkLogosModule with this builder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: forward logos-protocol/logos-qt-sdk through the QML module path

Same class as the mkLogosModuleTests fix: mkLogosQmlModule's own
buildCppPlugin import (and default.nix's pass-through to it) dropped the
new args, so every ui_qml module evaluated with logos-protocol = null
('expected a set but found null' at buildCppPlugin.nix:124) — breaking
basecamp's plugins and the basecamp/tutorial doctests under the chain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: cdylib module interface — uniform Qt glue over the module-impl C ABI

interface="cdylib" + codegen.lidl in metadata.json generates the uniform
Qt-plugin glue (logos-cpp-generator --lidl --backend cdylib) into
generated_code/, where logos_module() compiles it into the plugin. The
module's C exports (logos_module_impl.h) come from its own language
backend: the C++ SDK's cdylib wrapper or the Rust SDK's lidl-gen
--provider. One seam, every language.

* lock: cpp-sdk at the cdylib-authoring head — the cdylib glue mode lives there

* lock: cpp-sdk PluginInterface glue fix

* lock: cpp-sdk glue seeds the cdylib stack with the host auth token

* lock: cpp-sdk glue saves tokens into both stacks

* lock: protocol integer fidelity through cpp-sdk/qt-sdk

* lock: protocol call-error channel + CallError out-param typed wrappers

* cdylib interface: contract-first C++ impl-class flavor

codegen.impl_class (+ optional impl_header) on a cdylib module makes the
generator emit the C-ABI export wrapper and typed events around the
hand-written Qt-free class, alongside the uniform glue — the C++ mirror
of the Rust trait-impl flow. Lock at the generator head carrying the
mode.

* cdylib modules publish their .lidl contract

The committed .lidl IS the interface of a cdylib module (Rust or C++
impl alike) — expose it as packages.<sys>.lidl so consumers generate
typed bindings from it exactly like for universal deps, regardless of
the dependency's implementation language.

* lock: onContextReady ordering fix

* doctests: cross-language composition showcases + lock bump for the load-time context hook

Two new executable doc-tests join the external-library set — the
cpp-sdk ↔ rust-sdk feature-parity matrix as runnable tutorials, moved
here from logos-rust-sdk because they exercise the BUILDER's authoring
surface across both SDKs:

- cross-language-composition: contract-first C++ cdylib (committed .lidl
  + codegen.impl_class), Rust-first Rust (trait → .lidl via
  lidl-gen --from-rust; module context, typed event emission, typed dep
  client into the C++ module), universal C++ consumer (typed wrappers +
  typed event subscription against the Rust module). Typed calls cross
  the language boundary in both directions; a typed event flows
  Rust → C++.

- cross-language-composition-reverse: the mirror — LIDL-first Rust
  (generated trait), pure-C++ universal middle (typed deps into the Rust
  ledger + logos_events: emission, its contract DERIVED and consumed via
  the published packages.<system>.lidl flake output), Rust-first consumer
  (typed calls + typed event subscription via the generated
  on_total_changed/decode_total_changed). A typed event flows C++ → Rust.

Both pin {release} for logos-module-builder, so they build against the
commit under test, like the extlib specs. The Doc-Tests workflow runs all
six specs in one invocation (one combined report; the workspace
tests-and-doctests pipeline parses that block for spec discovery).

flake.lock: logos-cpp-sdk → 2642133 — the cdylib glue/impl-exports
ready-latch (context hook fires at module load once context + emit
callback are wired; tokens seeded before context in onInit). The specs'
rust modules pin the matching lidl-gen latch rev (34fffa8).

* ci: run workflows on stacked PRs + workflow_dispatch

Both workflows filtered pull_request to master-based PRs, so stacked PRs
(like feat/cdylib-interface -> feat/qt-sdk-split) ran NO checks at all.
Drop the base-branch filter for pull_request, add workflow_dispatch for
manual runs, and give the doc-tests job more headroom now that the suite
is six specs (the two cross-language compositions build Rust crates and
three modules each).

* codegen: Qt glue now comes from logos-qt-sdk's logos-qt-generator

Per the Qt-confinement invariant, generated Qt code is the Qt layer's
product. The builder's glue invocations switch to logos-qt-generator
(byte-identical output, verified):

- universalCodegen: glue trio from logos-qt-generator --backend qt; the
  .lidl sidecar (consumed by dependents' typed-event codegen) from
  logos-cpp-generator --header-to-lidl.
- cdylibCodegen: the uniform Qt glue from logos-qt-generator --lidl
  --backend cdylib; the Qt-FREE C-ABI export wrapper (contract-first C++
  flavor) stays with logos-cpp-generator.
- NEW uiCodegen: type=ui_qml + interface=universal — the universal
  authoring model for UI plugin backends. The qt generator derives the
  .rep from the impl header and emits the glue plugin that forwards repc
  slots to the impl and wires LogosModules + context (typed dependency
  callers, typed event subscriptions, bind_<interface> binders, and an
  onContextReady that fires at load) into it on initLogos. Existing
  LogosAPI*-based UI plugins are untouched (interface defaults to legacy).

logos-qt-generator rides the existing logos-qt-sdk input (relocked to
3b37474), injected next to logos-cpp-generator in every build path.

* lock: cpp-sdk at the lidl-frontend distribution rev

The follows wiring resolves logos-qt-sdk's cpp-sdk to THIS lock, and
logos-qt-generator compiles the LIDL frontend from cpp-sdk's
share/lidl-frontend — absent before 7147784c, so the generator build
failed with 'Cannot find source file'.

* fix: cdylib exports invocation lost its lidl path (nix escape)

''${lidlFile} inside the nested indented string is the ESCAPE for a
literal ${...} — the emitted bash expanded an unset shell variable and
logos-cpp-generator got --lidl "". Plain interpolation now; the emitted
script is eval-verified.

flake.lock: cpp-sdk -> 6ecef82 (post-deletion) — the builder now runs the
fully split pipeline: Qt glue exclusively from logos-qt-generator, the
Qt-free outputs (umbrella, sidecar, cdylib exports) from the slimmed
logos-cpp-generator.

* lock: protocol typed-requestModule port heads (cpp-sdk + qt-sdk)

* lock: plugin-qt compiles the generated universal-UI glue (9146087)

* lock: qt-sdk ui glue emits name/version literals

* lock: qt-sdk ui glue out-of-line ctor

* doctest: the universal UI backend, end to end

New tutorial doctest (ui-typed-backend, 4/4): tick_module (universal core
with a typed event) + ticker_panel (type=ui_qml + interface=universal) —
the backend is ONE clean impl class deriving LogosModuleContext; the
.rep, the plugin glue, and the typed-deps wiring are generated. The QML
view drives a typed call (Bump → modules().tick_module.bump()) and shows
the typed  event captured by the subscription armed in
onContextReady(). Drove out three integration bugs on the way (glue
compilation in plugin-qt's LogosModule.cmake, module_config.h coupling,
inline-ctor/incomplete-type in moc TUs) — all fixed at their layers.

* ui backend: user-authored .rep + Backend class (authoring split rework)

Per design review: the user writes the .rep (the view contract, with the
full QtRO surface - SLOTs, PROPs, SIGNALs) and the *Backend.{h,cpp}
deriving <RepClass>SimpleSource + LogosModuleContext; ONLY the *Plugin
and *Interface classes are generated (qt-sdk b2209e6). uiCodegen now
passes --metadata + --rep (+ optional codegen.backend_class /
backend_header). The doctest gains the nicest consequence of an authored
.rep: lastTick becomes a READONLY PROP fed straight from the typed
module-event subscription - the QML label auto-syncs, no polling Timer.

* docs: ui-typed-backend bullet matches the .rep+Backend authoring split

* lock: protocol+cpp-sdk merged to master — pins advance (protocol 9de4165, cpp-sdk f0fe8cb, qt-sdk 722e590)

* lock: parents advanced — protocol/cpp-sdk masters in via qt-sdk-split merge; cpp-sdk cdylib head f065223

* lock: qt-sdk#1 merged — pin advances to qt-sdk master

* lock: plugin-qt#10 merged — plugin-qt/plugin-core pins to master (650ecfa)

* chore: untrack result nix out-link; add .gitignore (repo had none)

* locks to masters: cpp-sdk 87abcd8 (cdylib merged), logos-module a3e288a; specs cargo-pin rust-sdk master

Composition doctests now build their Rust fixtures against rust-sdk master
(7ce12b9, the #11 squash) instead of the mid-chain 34fffa8 rev.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 22:32:07 -03:00

3 lines
16 B
Plaintext