Dario LipicarandClaude Opus 5 1aa3e31c02 feat(qt-host): add logos_qt_host_shared, and assert its layering (#22)
* feat(qt-host): add logos_qt_host_shared, and assert its layering

PR-3 of the shared-runtime migration. Requires logos-protocol#65, which exports
logos_protocol_shared from the CMake package; without it this build hard-fails
by design rather than falling back.

WHY. LogosAPI (here) and TokenManager / LogosAPIClient / StoreRegistry (in
logos-protocol) must exist EXACTLY ONCE per process. Every image linking a
static archive gets its own copy of every function-local static inside it, so
the host writes a capability token into one store and another in-process image
reads an empty one -- with no build diagnostic. Linking ONE shared library is
what replaces the whole-archive + generated-.def scheme in which liblogos_core
absorbed both archives and re-exported them.

THE STATIC ARCHIVE STAYS, and not transitionally. Module plugins and ui_qml
backends run in their OWN processes, so their own copy is the CORRECT
per-process singleton; staying static also keeps a .lgx self-contained, since a
.lgx records an empty nix closure and a shared library would not travel with it.
In-process images link the shared target; out-of-process images link the archive.

THE ONE LINE THAT MATTERS is that logos_qt_host_shared PUBLIC-links
${LP_SHARED_TARGET}, not ${LP_TARGET}. Linking the static archive there would
embed a second TokenManager INSIDE liblogos_qt_host itself -- the same bug one
layer down, and invisible to the consumer-side symbol gate, which would treat
this library as a provider and pass. It is a one-word mistake that builds,
links, installs and loads, and surfaces only as refused calls at runtime in a
different repo. So this PR carries its own check:

  checks.<system>.shared-runtime-layering

asserting liblogos_qt_host.{dylib,so} DEFINES LogosAPI, DEFINES NEITHER
TokenManager NOR LogosAPIClient, IMPORTS TokenManager, and has
liblogos_protocol on its link line. The positive assertion doubles as the
validity control: if nm or c++filt were broken it reports 0 for LogosAPI and
fails, rather than reporting a reassuring zero for the other two.

LogosAPI switches from LOGOS_SHARED_API to LOGOS_QT_HOST_API (logos-protocol#64)
because the two are not the same choice in one translation unit: building this
library, LogosAPI must be EXPORTED while TokenManager must be IMPORTED. On
Windows the shared target also compiles with LOGOS_SHARED_USE_DLL so the
protocol-owned types become dllimport; LOGOS_QT_HOST_BUILDING_SHARED is tested
FIRST in logos_shared_api.h, so LogosAPI still resolves to dllexport there.

ARCHIVE DESTINATION on the install is load-bearing on Windows and inert
elsewhere: a shared library's import library (.dll.a) is the ARCHIVE artifact
while the DLL is RUNTIME, so omitting it installs no import library and a
consumer gets an imported target whose IMPORTED_IMPLIB does not exist.

VERIFIED, aarch64-darwin:

  liblogos_qt_host.dylib   defines LogosAPI:: 23
                           defines TokenManager:: 0, LogosAPIClient:: 0
                           imports TokenManager:: 5, LogosAPIClient:: 1
                           links @rpath/liblogos_protocol.dylib
  static archive           symbol tables IDENTICAL to master (1494 lines), one
                           byte differing -- ar metadata, not content. The macro
                           switch is behaviour-preserving for every existing
                           consumer.
  all 7 checks             PASS

The layering check was confirmed discriminating rather than vacuous by running
its logic against liblogos_core.dylib, which defines TokenManager 32 times and
is correctly rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(deps): take logos-protocol master, which now exports the shared library

logos-protocol#65 merged as 2e3344a. The lock pinned 0d2a3c0, the commit BEFORE
it, so this branch would have configured against a protocol with no exported
logos_protocol_shared and tripped its own guard:

    logos-protocol::logos_protocol_shared is not a target, so
    logos_qt_host_shared cannot be built.

That guard is deliberate -- the alternative is silently falling back to the
static archive and embedding a second TokenManager -- but it means the lock bump
is a hard prerequisite rather than housekeeping.

Also moves protocol 0.4.0 -> 0.5.0 (#63, the module teardown surface), which is
why the Windows export table grows 360 -> 839: more symbols in the archive, all
of them generated rather than curated.

All 7 checks PASS against the merged protocol, including shared-runtime-layering.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:29:02 -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.

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%