#28 fixed one of two. The single-concurrency path returns early -- `if (kVoidMethods.contains(methodName)) return QVariant(true);` -- before reaching the branch that patch changed, and its own comment said the quiet part out loud: "void methods answer QVariant(true) WHATEVER the cdylib put on the C ABI". That is the one case where the cdylib has something to say. Measured, which is the only reason this was caught: with #28 merged, on plugin-qt master, reached through module-builder b06c287 and pinned by logos-test-modules -- verified by following the lock root-to-leaf, and by content (`__rejected` present in the emitter) -- the six failure/B/arity/too-many-zero-parameter cells were STILL xfail. The fix was in the build and the cells had not moved, so the fix was not on their path. THE TEST IS WHY IT LOOKED DONE, twice over, and both are fixed here: * It asserted `grep -q '__rejected'`, which passes on one site as happily as on two. * The first attempt to strengthen that counted occurrences in $cm -- the MULTI glue file -- while the site #28 missed emits into $c, the SINGLE one. Checking the right property in the wrong file is the same mistake one step over, and the suite caught it. Each site is now asserted against the file it lives in, so patching one path again fails here rather than at the far end of a matrix run. An ordinary void reply still answers QVariant(true) on both paths; only a provider refusal -- the closed set dispatch_failed / invalid_args / unknown_method, matched on an object with a string `code` -- is propagated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
logos-plugin-qt
Everything specific to running a Logos module as a Qt 6 plugin, in one repo: the build logic logos-module-builder delegates to, and the runtime that build produces plugins against. Keeping both here is what lets the plugin technology be swapped without touching the module builder or individual modules.
Outputs
| Output | What it is |
|---|---|
lib / rawLib |
The Nix build functions (buildPlugin, generate, buildHeaders, devShellInputs, plus common). rawLib takes its Logos deps as arguments; lib pre-fills them from this flake. |
packages.<sys>.logos-qt-host |
The Qt host runtime a plugin links: LogosAPI, LogosAPIProvider, LogosProviderBase, the legacy QtProviderObject adapter, and core/interface.h. Static library, headers, and a find_package(logos-qt-host) config. |
packages.<sys>.logos-qt-host-generator |
Emits the Qt plugin glue around a cdylib module's C ABI (<name>_cdylib_glue.{h,cpp}) from its LIDL contract. |
logos-qt-host is also the default package.
LogosProviderBase's two pure slots — callMethod() and getMethods() — are
filled by logos-qt-host-generator --backend cdylib, in the emitted
<name>_cdylib_glue.cpp. logos_provider_object.h also still defines the
LOGOS_PROVIDER / LOGOS_METHOD macros, but they are vestigial: they were
scanned by logos-cpp-generator --provider-header to emit a
logos_provider_dispatch.cpp for interface: "provider" modules, and that flag,
that interface value and that file are all gone (the flag is now refused with a
message naming interface: "universal"). Under universal a module's plain
public methods are its API — the contract is derived from the impl header
named by codegen.impl_class / codegen.impl_header, and there is no marker to
write. Nothing this repo builds expands either macro any more; they are kept
only so an older translation unit still compiles.
There is no cmake-module output and no LogosModule.cmake here.
LogosModule.cmake lives in logos-module-builder, and only there. This repo
shipped a second copy until the builder was made to point
LOGOS_MODULE_BUILDER_ROOT at its own copy for every module type: the builder
only overrode that variable when a MODULE carried the file (none does), so
ui_qml plugins configured with this repo's copy while core modules configured
with the builder's, and the two drifted apart in silence. The CMake module reads
the builder's own variables (LOGOS_API_STYLE, LOGOS_MODULE_GO_STATIC_LIBS,
generated_code/), so the builder is where it belongs.
cmake/ went away with that copy and is gone for good. It briefly came back to
hold the four LogosView*.in templates logos_module(REP_FILE ...)
instantiates, which had the mirror-image problem — a byte-identical second copy
of them lived here with nothing comparing the two. Both copies are now one copy,
in logos-view-module, which owns the ui_qml authoring flavour end to end:
the templates, LogosViewModule.cmake, the view glue generator, and the
.rep-file replica-factory fixture that proves the plugin an authoring build
produces still loads and casts. logos-module-builder inputs that repo and hands
the directory to every plugin build as LOGOS_VIEW_TEMPLATE_DIR; this backend
never names it.
Which is the line this repo now holds to: it handles exclusively what makes a
cdylib module loadable by logos-module-loader-qt — the Qt host runtime a
plugin links, the generator that wraps a cdylib's C ABI in that plugin, and the
Nix functions that compile and package the result. View-plugin authoring is
somebody else's repo. (buildPlugin still packages a
<name>_replica_factory library when a module's build emits one — that is
plugin packaging of a build artifact, the same as the _plugin library beside
it, and carries no knowledge of where the templates live.)
lib / rawLib are pure Nix and stay that way: nothing reachable from them
mentions the two C++ derivations, so a consumer that only wants the build
functions never realises a Qt or protocol build to get them.
By-name contracts
Two things cross the host/plugin boundary as a string rather than a symbol,
because a plugin and the host that loads it are separate images: each links its
own copy of LogosAPI, ModuleProxy and TokenManager, at distinct addresses
and with no undefined reference to the other's (Mach-O is TWOLEVEL, PE has no
interposition; only ELF's flat namespace collapses them). A direct C++ call
binds to the calling image's copy. QMetaObject::invokeMethod does not — it
resolves through metaObject() / qt_metacall, which are virtual, and the
vptr was written by the host's constructor.
| Contract | Emitted by | Reached by |
|---|---|---|
aboutToUnload() / unloadFinished() |
qt-host-generator, on the plugin class |
cpp/logos_plugin_unload.cpp |
currentCallerJson() — who is calling this dispatch |
cpp/logos_api.h, on LogosAPI |
cpp/logos_provider_object.cpp, then pushed across the module-impl C ABI by the generated glue |
Nothing in either build ties the two ends together: rename one side and every
module still compiles, links and loads, and the feature is simply never found —
a silent, permanent no-op that looks exactly like a module which legitimately
declined. Both halves of both contracts live in this repo, which is what
makes tests/test-unload-contract.nix and tests/test-caller-contract.nix
possible: they scrape the name out of the emitter and require the consumer to
reach for that exact string, in both directions.
Layout
lib/ the Nix build functions (buildPlugin, generate, buildHeaders)
cpp/ the Qt host runtime library (logos-qt-host)
core/interface.h the legacy Qt plugin interface (PluginInterface)
qt-host-generator/ the cdylib -> Qt-plugin glue emitter
nix/ derivations for the two C++ outputs
tests/ flake checks