Dario Gabriel LipicarandClaude Opus 5 c459e47787 feat(glue): pull the caller from the host image and push it across the C ABI
Everything under this has merged — the protocol resolves the caller and
opens a scope (#70), both backends define logos_module_set_call_caller
(cpp-sdk #147, rust-sdk #47) — and nothing connected the two, so
currentCaller() returned Unknown everywhere. This is the wire.

Per dispatch, all on the dispatch thread: pull the caller document back out
of the HOST image, push it into the module image, dispatch, pop.

WHY THE PULL IS AN invokeMethod AND NOT A CALL
The host binary and the module plugin EACH define LogosAPI — meta-object
included — with their own statics at distinct addresses and no undefined
reference to the other's. Mach-O is TWOLEVEL, PE has no interposition. A
direct logosAPI()->currentCallerJson() binds to the PLUGIN copy and reads
the PLUGIN thread-local: empty, forever, silently, on macOS and Windows.
invokeMethod resolves through metaObject()/qt_metacall, which are virtual
and whose vptr the HOST constructor wrote, so it lands in host code on the
calling thread. Same channel initLogos and aboutToUnload already use.

IN MULTI THE PULL IS BEFORE THE CAPTURE
callMethod is entered on the dispatch thread and captures by value into
QThread::create, so the pull happens there and the JSON rides along; the
push/pop triple moves verbatim into the worker. Pulling inside the worker
also compiles, and every multi call would read Unknown because that thread
never had a scope. A test asserts the source ORDER, and it was driven red
by making exactly that mistake.

The invokable's name was not chosen here: logos-protocol master already
names currentCallerJson in three places, including a CMakeLists comment
saying the header ships so this file can answer it.

Two checks beyond the wire, because the by-name test alone left gaps:

  * test-glue-compiles — NOTHING in this repo compiled the emitted glue.
    test-qt-host-generator.cpp documents a bug that escaped through exactly
    that hole. Both branches now build as a real Qt plugin with the C ABI
    stubbed and -Wl,--no-undefined, so a missing symbol fails at link
    rather than at a user's dlopen.
  * test-caller-invokable — six runtime assertions, including that a scope
    open on one thread is INVISIBLE on another, which is the actual
    justification for the multi placement.

The contract test strips comments before its negative assertions (both
files legitimately NAME the forbidden calls in prose) and guards that with
a positive control, so the negatives cannot pass vacuously.

Guard is MAJOR-aware expanded arithmetic. Dropping only the MAJOR > 0 arm
still passes a grep, so the test resolves at 1.0 and asserts the call
survives; that mutation was driven red too.

11 checks green on x86_64-linux, the three new ones green on
aarch64-darwin, and the Windows mingw cross builds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 18:46:35 -03:00

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
S
Description
Qt-specific plugin build logic
Readme
594 KiB
Languages
Nix 49.6%
C++ 45%
CMake 3.3%
Shell 1.4%
C 0.7%