mirror of
https://github.com/logos-co/logos-plugin-qt.git
synced 2026-08-27 08:51:07 +00:00
1aa3e31c029062a8288e5ef4da1f25ca14a8a0dd
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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>
|
||
|
|
9b2c64e5a4 |
feat: the Qt host runtime and cdylib-glue generator (#19)
* feat: the Qt host runtime and the cdylib-glue generator
This repo was the Qt plugin BUILD backend — pure Nix functions plus a CMake
module, with no C++ target at all. But the two things a Qt host actually needs
at runtime lived in logos-qt-sdk, where they are neither a language wrapper over
the protocol C API nor codegen:
LogosAPI / LogosAPIProvider owns a LogosTransportHost per transport,
constructs ModuleProxy / ModuleHandshakeProxy,
publishes the handshake surface, seeds trust
anchors, injects the token validator
LogosProviderBase the base every generated provider derives
PluginInterface the Qt plugin loading contract
the cdylib->Qt glue generator the emitter that wraps a language-neutral
cdylib in a Qt plugin
They move here, so "swap the plugin technology" is a one-repo change.
ADDITIVE: the sources are COPIED and logos-qt-sdk is untouched. Removing them
there now would turn nine downstream masters red at once; that comes later,
after consumers are repointed.
qt_provider_object (and logos_qt_arg_decode, which its QMetaObject dispatch
needs) is carried deliberately even though it is legacy: logos_api_provider
falls back to wrapping a plain QObject in it, and the modules that rely on that
have not been migrated yet. It goes once they are.
The generator links Qt Core and logos-lidl only — never logos-cpp-sdk. It needed
exactly one helper from that SDK's shared frontend, lidlToPascalCase (~12
lines), which is inlined instead, the same way logos-view-module does it. It
also REFUSES `--backend <anything but cdylib>` rather than ignoring the flag:
callers are migrating from a tool where --backend was required and dispatched
on, so silently treating `--backend qt` as cdylib would emit confidently wrong
artifacts with a zero exit.
`rawLib`, `lib` and `cmake-module` deliberately do not reference the new
derivations, so a consumer that only wants the Nix build functions never
realises a Qt/protocol build. Proven, not assumed: with both new inputs
overridden to a bogus flake, cmake-module and rawLib still evaluate while
logos-qt-host fails — so the override bites and the cheap outputs really never
touch it.
Behaviour preservation is the whole claim of a relocation, so it is measured:
the emitted glue is BYTE-IDENTICAL to logos-qt-generator --backend cdylib over
every one of the 20 .lidl contracts in the workspace, in both single and
concurrency:multi mode (40 pairs, exit codes included), and single vs multi do
differ from each other, so both code paths were really exercised. The built
liblogos_qt_host.a is byte-identical to liblogos_qt_sdk.a, exporting the same
390 symbols.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: qt-style consumers take their wrappers from logos-qt-generator
The qt api-style path emitted its per-dependency wrappers from
logos-cpp-generator's legacy ApiStyle::Qt emitter — a second implementation of
the consumer surface, with its own conversion table and its own transport. The
qt-generator `--backend consumer` veneer exists to replace it so there is ONE
transport, one codec and one Qt type mapper under both consumer surfaces. It was
golden-tested and wired into nothing.
The umbrella is what made this non-obvious. `--general-only` emits both the
per-dep wrappers AND logos_sdk.{h,cpp} (the LogosModules aggregate);
`--backend consumer` emits no umbrella at all, and the umbrella is live —
storage-ui and wallet-ui both use LogosModules. Nor is the umbrella
style-agnostic: makeUmbrellaHeaderFromDeps branches hard on apiStyle, emitting
`LogosModules(LogosAPI*)` with `dep(api)` members for qt and a
default-constructible struct with `dep("<origin>")` for lp.
What it IS agnostic to is WHICH generator produced the wrappers for a given
style — the qt branch's two constructor shapes are exactly what the consumer
backend emits for Static and Bound, and both spell the member's type with the
same PascalCase routine, shared through cpp-sdk's share/lidl-frontend. So
cpp-generator still emits the umbrella into a scratch dir, from which only
logos_sdk.{h,cpp} is copied, and qt-generator emits the wrappers. No legacy
wrapper text reaches the build tree; the shipped wrapper has exactly one author.
--dep is dropped from the umbrella call (those flags only drive wrapper
emission; the umbrella's members come from metadata.json dependencies), while
--interface is kept, because the bind_<name> factories come from the interface
names and a cross-repo interface can only reach the generator through a flag.
The lp path is emitted verbatim as before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: drop PROVIDER_HEADER from LogosModule.cmake
Follows the removal of logos-cpp-generator --provider-header. Nothing in the
workspace passed PROVIDER_HEADER to logos_module().
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(glue): forward the host-services grant across the C ABI (C3)
The cdylib glue now reads a `hostServices` property off the LogosAPI object and
calls logos_module_grant_host_services with it, on the SAME property-stamping
channel the host already uses for authToken/modulePath/instanceId — no new
host<->plugin API.
It has to cross the C ABI rather than being recorded host-side: the host binary
and the cdylib each link their own copy of logos-protocol, so each has its own
process-global grant state, exactly as each has its own TokenManager (which is
why the auth token is re-seeded here too). Measured, not assumed: a built
plugin DEFINES 25 lp_* symbols and imports zero.
Ordered before the context forward, so a privileged impl may already use the
granted services from its context-ready hook.
An ordinary module has no such property and stays fail-closed with no call at
all — lp_grant_host_services REPLACES the current grant, so pushing an empty
string would be a pointless clear. A refused grant (malformed JSON, or a name
outside the closed set) is reported rather than swallowed: the module keeps
running UNPRIVILEGED, and the symptom otherwise surfaces far away as an
unexplained empty token registry.
Tests assert the forwarding, the empty-property guard, the refusal warning, and
the ORDER (by comparing emitted line numbers, since that is the kind of thing a
refactor silently reverses and which then breaks a trust-root module at
startup).
Note ws test ran only 1 of this repo's 4 checks, so the new test was run
directly by name — see the ws check-discovery gap.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test: document the two-copy drift this generator test now guards
logos-qt-sdk ships the same cdylib-glue emitter. Both compile and both emit
loadable glue, so calling the wrong one is not an error — it silently emits
OLDER glue, which is how the host-services grant went undelivered for a whole
phase while every build stayed green.
The grant assertions added with the forwarding are what make that visible: if
the builder is pointed back at qt-sdk's generator, these fail instead of a
module quietly coming up unprivileged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(glue): guard the grant forwarding, and include the header that defines it
Same protocol-MINOR guard as the C-ABI export, so glue for a module built
against logos-protocol < 0.3 still compiles.
The guard needs logos_protocol.h, which the glue did NOT include — and an
undefined macro in `#if defined(X) && X >= 3` is simply false, so the first
version of this change compiled everywhere and silently stopped forwarding the
grant: logos-test-modules ipc-tests went PASS -> FAIL again, with capability
refusing every requestModule exactly as it had before.
That is the second time this seam has failed by going quiet rather than loud.
Verified in BOTH directions this time: template_module builds against the old
protocol (guard off), and ipc-tests passes against the new one (guard on).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(tokens): a LogosAPI can be built on its own token store
Origin was never consulted on the hot path — LogosAPIClient reads its store
first and mints only on a miss — so a per-plugin `LogosAPI(origin=name)` was
a no-op while this ctor hard-wired `&TokenManager::instance()`. What has to
differ is the STORE.
The existing ctors now resolve their store through
`TokenManager::forIdentity(module_name)`, which returns instance() itself —
pointer-identical — for every name nobody has isolated, so no existing caller
changes by one byte. New: a ctor taking an explicit store, and
`LogosAPI::forIdentity(name)`, which isolates first and then constructs (the
required order: a client captures its store by raw pointer) and returns
nullptr rather than a half-isolated identity.
* feat: a module's own qt wrapper comes from its contract, not its plugin
buildHeaders was the last place the legacy Qt consumer emitter was reached on
purpose, and BOTH of its paths reached it: the native one introspected the
compiled plugin (`<plugin.so> --module-only --api-style qt`), the cross one ran
`--general-only --dep` and then deleted the umbrella it incidentally emitted.
B5 moved buildPlugin's consume-time wrappers to `logos-qt-generator --backend
consumer`; the wrapper a module PUBLISHES for its own consumers kept coming
from the other emitter, so the same file name had two authors depending on
which derivation produced it.
Contract-first now. A module that publishes a LIDL contract — every universal
and cdylib module does, and a handcrafted one can commit src/<name>.lidl — has
its qt wrapper generated from that contract, natively and under cross alike.
Introspection survives exactly where nothing else describes the module: a
legacy Qt plugin whose QMetaObject is the only account of its API that exists.
That residue dies with the last such module, not here.
The public declaration set does not move. Six real providers, emitted both ways
and diffed over the surface a consumer can name: capability_module 12,
chat_module 84, storage_module 102, package_manager 131, test_fullapi_cpp 113,
test_fullapi_rust 113 — identical in every case. The entire diff is private:
`ensureReplica` / `packVariantList` / `m_client` / `m_eventReplica` collapse to
one `logos::qt::LpBridge* m_bridge`. That is the transport, which is the point
of the exercise.
Two consequences worth naming. The qt variant no longer references the compiled
plugin at all, so capability_module's headers-qt went from depending on the SDK
plus the module .dylib to depending on logos-qt-generator plus a .lidl — a
contract module's headers now cost a parse instead of a Qt build. And the cross
path stops being a DIFFERENT emitter from the native one: `--api-style qt` at
host=x86_64-w64-mingw32 takes the same branch darwin takes, which retires the
`--general-only` + `rm logos_sdk.*` shape that only ever existed because
introspection is impossible there.
The failure mode this file has to defend against is silence, not breakage: a
routing bug leaves every build green while the wrapper quietly comes from the
old emitter again. So the selection is announced with its reason
(`buildHeaders: emitter=…`), the contract path asserts its own output names
LpBridge before installing it, and the one branch where a contract EXISTS but
the caller passed no generator is reported as a REGRESSION on stderr rather
than silently demoted. tests/test-headers-emitter-routing.nix pins all four
selections against the real routing code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(b2b): repoint this repo's LogosModule.cmake at logos-qt-host
This file is not a spare copy. logos-module-builder's buildCppPlugin sets
LOGOS_MODULE_BUILDER_ROOT only when the MODULE'S OWN repo ships a
cmake/LogosModule.cmake, and none do -- so for every ui_qml / view plugin, and
for the counter fixture, the CMake module that actually runs is this one, taken
from the backend root. logos-module-builder's copy is what mkLogosModule's core
path uses. Two live copies, selected by a pathExists probe.
That was invisible while logos-qt-sdk forwarded the host runtime's headers: this
copy probed ${LOGOS_QT_SDK_ROOT}/include/cpp/logos_api.h, put
${LOGOS_QT_SDK_ROOT}/include{,/cpp,/core} on the include path, and linked
logos-qt-sdk::logos_qt_sdk -- all of which resolved through the forwarders and
the INTERFACE hop. With the forwarders gone it configures straight into
"logos-qt-sdk not found" while looking at a perfectly good prefix. I found this
by deleting the file on the theory that it was dead and watching eight module
builds fail; the theory was wrong and the build said so.
Repointed the same way logos-module-builder's copy already was:
- LOGOS_QT_HOST_ROOT selects the host runtime, defaulting to this repo (the
CMake module sits next to the cpp/ it needs) and hard-erroring otherwise.
There is no logos-qt-sdk fallback -- it would configure and then fail in the
compile.
- the source layout compiles ${LOGOS_QT_HOST_ROOT}/cpp/*.cpp, now including
logos_qt_arg_decode.cpp, which qt_provider_object.cpp's dispatch needs and
the qt-sdk-era list omitted.
- the installed layout links logos-qt-host::logos_qt_host, refusing a package
that resolves without defining its target.
- LOGOS_QT_SDK_ROOT stays, probed by logos_qt_wire.h (a name this SDK owns;
logos_api.h is in neither of its layouts any more) and contributing only the
Qt-typed consumer headers.
Every plugin built through this file is byte-identical before and after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: stop shipping a second LogosModule.cmake
This repo's cmake/LogosModule.cmake was a duplicate of
logos-module-builder's, and the build chose between them by module type:
logos-module-builder set LOGOS_MODULE_BUILDER_ROOT only when a MODULE carried
that file (none does), so every ui_qml plugin configured with THIS copy while
every core module configured with the builder's. Both compiled. That is how a
stale generator, a stale host-runtime repoint and a missing source file each
shipped in a green tree.
The CMake module is the builder's build-system contract -- it reads
LOGOS_API_STYLE, LOGOS_MODULE_GO_STATIC_LIBS and generated_code/, none of
which this backend knows about -- and it cannot live here anyway: the
dependency runs builder -> backend, so this repo could never re-export the
builder's file. It is deleted, not forwarded. logos-module-builder now passes
LOGOS_MODULE_BUILDER_ROOT for both module types (its 0ddac92), so the deletion
lands after the routing, not before it.
Consequently:
- lib/default.nix no longer defaults LOGOS_MODULE_BUILDER_ROOT to backendRoot.
A caller that forgets it now gets a FATAL_ERROR from the module's own
CMakeLists instead of a build against whatever this repo contains. That was
backendRoot's only use, so the parameter goes too.
- packages.cmake-module is gone; `default` is logos-qt-host, the output this
repo now produces that a consumer can build.
- cmake/ is gone. LogosViewPluginBase.{h,cpp}.in were read only by the deleted
file. The two LogosViewReplicaFactory templates are read only by
tests/test-rep-file-plugin.nix, so they move into that fixture's own cmake/
and are what they are: a test fixture, not a shipped template.
Checks: header-generator-guard, headers-emitter-routing, qt-host-generator,
rep-file-plugin, vanilla-plugin OK. qt-host fails identically at the pre-change
baseline -- this repo's lock pins a logos-protocol whose TokenManager has no
forIdentity/isolateIdentity, so cpp/logos_api.cpp will not compile from it.
It builds green in the workspace closure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat: the view templates exist once, and this repo is where
The four LogosView*.in templates had two byte-identical copies:
logos-module-builder/cmake/ (LIVE — LogosModule.cmake)
logos-plugin-qt/tests/rep-file-plugin/cmake/ (fixture — rep-file-plugin)
Nothing compared them. This is the fifth defect of that exact shape in this
refactor, and it was created by the fix for the fourth: when this repo's
duplicate LogosModule.cmake was deleted, the templates it read were MOVED into
the fixture rather than removed, because the fixture still needed them.
It needed them because it cannot reach where they went. The edge runs
logos-module-builder -> logos-plugin-qt, one way, so a fixture inside this repo
can never consume logos-module-builder's copy, and this repo cannot re-export
it either. "Own them in the builder" is not reachable; a copy in the fixture is
what "own them in the builder" degrades into.
So ownership follows the direction that works. The templates are this repo's:
* cmake/ holds the one copy, with cmake/README.md stating the rule and the
argument for it.
* packages.<sys>.logos-view-templates publishes them as a nameable output.
* lib.buildPlugin / lib.generate set LOGOS_VIEW_TEMPLATE_DIR (cmake flag and
env var) on every plugin build, so logos-module-builder's LogosModule.cmake
receives the directory without either repo growing a new input.
* devShellInputs exports it too, so a hand-run cmake in a module dev shell
resolves it the same way.
LogosModule.cmake itself does NOT come back here. That file is the builder's
build-system contract and stays there; only the Qt-specific templates it
instantiates are published from this side of the edge.
The fixture now takes the directory from the harness and has nothing to fall
back to — a missing LOGOS_VIEW_TEMPLATE_DIR is a FATAL_ERROR, not a quiet
second copy. Proof it reads the owned file and not a leftover: changing the IID
in cmake/LogosViewReplicaFactory.h.in makes rep-file-plugin fail its
"IID found in binary" assertion.
rep-file-plugin is also added to CI. It is the check that instantiates these
templates and it was never listed in the workflow, so the templates had no CI
coverage at all — part of why a duplicate could sit in the fixture unnoticed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(view): assert the exact IID, and load the plugin the way the host does
`rep-file-plugin` asserted `strings | grep -q "logos.view.replica_factory"`.
That is a SUBSTRING match, and it is satisfied by every way the plugin can be
broken. Two mutations were run through it and it stayed green on both:
* `Q_PLUGIN_METADATA(IID LogosViewReplicaFactory_iid)` changed to the literal
"logos.view.replica_factory/2.0" — the built dylib then carries BOTH
strings, because Q_DECLARE_INTERFACE still emits /1.0, and the substring
matched either one.
* `Q_INTERFACES(LogosViewReplicaFactory)` deleted — no string in the binary
changes at all; only moc's qt_metacast entry disappears, so the host's
qobject_cast returns nullptr and the view is blank.
They are two independent bindings, so they need two assertions:
* the IID is now compared EXACTLY. Every logos.view.replica_factory/<v>
string in the binary is collected and the set must be exactly the one
expected IID, which is what makes the /2.0 case fail: a binary carrying
both is proof that Q_PLUGIN_METADATA and Q_DECLARE_INTERFACE disagree.
* the plugin is then actually loaded. load_factory_check.cpp does what
LogosQmlBridge::loadFactory does — QPluginLoader::instance() followed by
qobject_cast<LogosViewReplicaFactory*> — and also checks the factory is
wired to the repc-generated RepTestReplica. Deleting Q_INTERFACES fails
here, at the same call the host would have failed at.
The checker deliberately has AUTOMOC OFF: it includes the generated header
only for the interface declaration and its Q_DECLARE_INTERFACE, and must not
moc the concrete plugin class that header also declares. It carries no copy of
the interface — that duplication is the thing being guarded against.
Comments in cmake/ updated to say what the two checks actually cover now, and
that the module side is checked from the binary end here while agreement with
the HOST side is logos-module-builder's `view-interface-abi`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): logos-qt-host builds for the x86_64-windows target
The Qt host runtime moved here from logos-qt-sdk, but its Windows target did
not come with it. logos-qt-sdk keys `packages` by `forAllTargets`; this repo
keyed them by `forAllSystems` over four unix systems, so
`packages.x86_64-windows.logos-qt-host` did not exist and every consumer that
threads the pseudo-system through — logos-liblogos, and through it
logos-basecamp — failed at EVALUATION with
error: attribute 'x86_64-windows' missing
before a single derivation was built. The split traded a Windows-capable
provider for one that is not.
`packages` now uses `logos-nix.lib.forAllTargets`, the same helper
logos-protocol and logos-lidl already build their Windows legs with. `checks`
and `devShells` deliberately stay on `forAllSystems`: every check either runs
what it built or loads a plugin, and a PE does not run on the Linux builder.
Two things the cross target needs, both gated on the HOST platform so the
native derivation does not move:
* `$cmakeFlags` is forwarded to the hand-rolled cmake line. mkDerivation puts
-DCMAKE_SYSTEM_NAME / -DCMAKE_SYSTEM_PROCESSOR / the host-system triple
there for a cross build and leaves it empty natively, so a hand-rolled
configure that omits it is precisely the shape that silently drops them.
* the Qt cross flags (QT_HOST_PATH, QT_ADDITIONAL_HOST_PACKAGES_PREFIX_PATH)
plus dontWrapQtApps. CMAKE_AUTOMOC is on and RemoteObjects is required, so
moc and repc must come from the BUILD platform's Qt; and the Qt wrapper
hooks cannot evaluate for a mingw host, while qtbase's setup hook aborts in
qtPreHook unless one of them ran or dontWrapQtApps is set. There is nothing
to wrap in any case — this package installs an archive and headers.
NATIVE DERIVATION UNCHANGED, measured rather than argued: `nix derivation show`
for aarch64-darwin before and after differs in the `src` snapshot alone (`src =
./.`, so any commit moves it). Every other env var is byte-identical and
inputDrvs compares equal, so nothing downstream rebuilds for a reason other
than this repo's source having changed.
`logos-qt-host-generator` is deliberately NOT published for the Windows target.
It is a HOST TOOL, executed during a consumer's build, which is why every
caller in logos-module-builder already reaches for it as
`packages.${buildSystemFor system}.logos-qt-host-generator` — resolving to
x86_64-linux there. A PE under `packages.x86_64-windows` would only offer the
Linux builder a binary it cannot run.
LAZINESS PROVEN BY CONTROL, not by grep. With BOTH inputs the host runtime
needs overridden to a flake that provides no packages at all:
lib -> evaluates (7 attrs)
rawLib -> evaluates (7 attrs)
packages.aarch64-darwin.logos-view-templates -> evaluates
packages.x86_64-windows.logos-view-templates -> evaluates
packages.aarch64-darwin.logos-qt-host -> FAILS at logos-protocol
packages.x86_64-windows.logos-qt-host -> FAILS at logos-protocol
packages.aarch64-darwin.logos-qt-host-generator -> FAILS at logos-lidl
So the override really bites, the cheap outputs a consumer of the Nix build
functions asks for still never realise a Qt + protocol build, and the new
Windows branch does not drag one in either.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(deps): rev-pin logos-protocol at c8bab12 (per-client token store)
LogosAPI is now constructible on its own token store, which needs
TokenManager::forIdentity / isolateIdentity. Those landed on
logos-protocol's feat/per-client-token-store branch and are NOT on its
master — master is still LOGOS_PROTOCOL_VERSION_MINOR 2, so the
previously locked e6d5b57 (protocol master's tip) cannot compile
cpp/logos_api.cpp, and the cdylib glue's `MINOR >= 3` guard would
silently drop the host-services grant.
Rev-pinned in the URL rather than left master-tracking, because
`nix flake update` cannot reach a commit that is not on the tracked
branch. Re-point at master once that branch merges.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(buildPlugin): drop the transitional header-copy fallback
For a dependency that published no LIDL contract, the plugin build copied that
dependency's PRE-BUILT `headers-<apiStyle>` output into generated_code/ --
which also forced the dependency's whole plugin to be compiled just to obtain
headers. Every dependency now arrives as `--dep <name>=<name>.lidl` and its
wrapper is generated from the contract instead.
The path is dead, measured rather than assumed. `moduleDepIncludes` was
interpolated into the plugin derivation's `preConfigure`, so its liveness is
readable at EVAL time: I probed `.preConfigure` on every package reachable
within two hops of the workspace flake (303 derivations) plus every variant of
every module in logos-test-modules (40 more) and grepped for the block's own
"(legacy header-copy)" echo. 343 non-empty preConfigure strings, ZERO hits --
including test_ipc_module and test_ipc_new_api_module, the two modules with
concrete `dependencies` and no `interface` declaration.
That probe has a control, because a zero-hit grep is worth nothing without one:
patching mkLogosModule's `depIsLidl` to `false` makes the same eval print
"Copying qt-typed include files from test_basic_module (legacy header-copy)..."
for both of test_ipc_module's deps. The probe sees the block when it is there.
`moduleDeps` is still ACCEPTED, and asserted EMPTY. The caller fills it from
`legacyHeaderDepNames` -- deps for which `depIsLidl` is false -- and a caller
that predates this change keeps passing them. Silently dropping those would
leave the module with neither a `--dep` wrapper nor a copied header, and the
build would die inside a generated translation unit on a missing
`<dep>_api.h`, pointing at the generator instead of at the stale dependency.
So it throws, naming the deps. Verified with the same forced-`depIsLidl` patch:
the error names test_basic_module and test_extlib_module.
Verified:
* all six checks built by name, each exit 0 -- header-generator-guard,
headers-emitter-routing, qt-host, qt-host-generator, rep-file-plugin,
vanilla-plugin
* test_ipc_module, test_ipc_new_api_module, test_fullapi_cpp and the
workspace's logos-accounts-ui all build against this tree (exit 0), with
reach proven by store-path diff: test_ipc_module is
867kcfxr... with the override and pnj60rsc... without it
* test_qml_backend fails, IDENTICALLY with and without the override
("no member named 'add' in 'TestBasicModule'") -- pre-existing at the
pinned revs, not caused by this change
NOT removed, and it is worth saying why: the Windows cross-compile branch in
buildHeaders.nix (`crossNoIntrospect` -> `crossBuildPhase`) does NOT serve this
fallback. It is the emitter for `--api-style lp` headers under cross, selected
because a Linux builder cannot dlopen a PE, and its `!contractIsLidl` half is
the hard error that stops a contract-less module shipping an empty API under
cross. Both halves are still reachable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(buildPlugin): honour the consumer surface, and gate origin binding
Backend half of codegen.consumer_api_style (logos-module-builder 08ae7ac).
`apiStyle` now honours `config.consumer_api_style`, which also selects which
pre-built `dep.headers-${apiStyle}` variant a consumer takes.
Adds `originBound = isQt && packagedAsCdylib`, which is what actually passes
`--binding origin` to the generators. Deriving it here rather than reading a
key means the hazardous combination cannot be requested: only a cdylib-packaged
image reaches it, and that is the image whose TokenManager is filled by
logos_module_accept_token rather than by LpBridge::syncTokens. A Qt plugin given
an origin-bound wrapper would be silently unauthenticated.
`assertConsumerApiStyle` restates the gate here as well as in parseMetadata,
because callers can reach rawLib.generate directly and bypass metadata parsing
entirely.
The flag is appended as a suffix on an existing argument
(`--api-style qt${bindingFlag}`) so the emitted shell is byte-identical when
empty — verified by A/B'ing the generated tree for a Qt plugin, not just the
built output.
New check `consumer-api-style-gate` drives the real `generate` and asserts on
the emitted generator command line across 16 configurations. Its mutation
control is the important one: setting `originBound = isQt` — dropping the
packagedAsCdylib conjunct — makes it fail by name on legacy and ui_qml, so the
suite pins the conjunct itself rather than only the lp refusal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(qt-host-generator): capture isVoidMethod in the multi worker lambda
`concurrency: "multi"` runs the handler on a QThread, and the lambda it hands
to QThread::create takes no capture-default on purpose — it outlives the frame,
so every capture has to be by value deliberately. The capture list was written
out by hand in two variants selected on `resultMethods`, but the BODY reads two
flags, `isVoidMethod` and `isResultMethod`, each emitted under its own
condition. Two lists cannot cover four combinations:
void + result -> captured isResultMethod only; body still read isVoidMethod
void, no result -> captured neither; body still read isVoidMethod
result, no void -> correct
neither -> correct
So any module with a void method failed to compile under multi, at the
generated glue rather than anywhere the author could see. Building the list
from the same two conditions that decide whether the locals exist removes the
possibility of them disagreeing.
Found by giving test_ipc_new_api_module `concurrency: "multi"` — it has a void
method (triggerBasicEvent) and no result method, i.e. the doubly-broken case.
The existing multi test did not catch this because it only ever GREPPED the
emitted text and never compiled it, and its fixture happens to declare both a
void and a result method — so it was reading output that would not build. The
assertions added here close that specific gap rather than the general one: for
each flag the body reads, the capture list must name it. A second contract
covers void-without-result, the combination neither hand-written list could
express. Both were confirmed to FAIL against the pre-fix generator (the first
reports the actual emitted list) and pass after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* ci: use logos-co/setup-nix-cache-action for Nix setup and caching
Replaces the per-repo installer + cachix pair with the shared action, which
installs Nix with the Logos Attic cache (cache.nix.logos.co) preconfigured and
publishes what the job builds — master to the public cache, every other ref to
ci.
Each converted job also gains
environment: ${{ github.ref == 'refs/heads/master' && 'public-cache' || '' }}
because ATTIC_TOKEN_PUBLIC only exists inside that environment. Without it the
secret resolves empty on master and publishing is silently skipped — the job
still passes, so the omission would not show up as a failure.
The action installs Nix itself on every runner, macOS included. That is a
deliberate reversal of the workaround these files carried: the comments here
said cachix/install-nix-action collides with the runner's pre-existing _nixbld
users (eDSRecordAlreadyExists), so DeterminateSystems' installer was used
instead. It no longer reproduces — logos-delivery-module has already been
converted the plain way and its `build-and-test (macos-latest)` leg passes.
Keeping the workaround would have meant a second installer plus a duplicated
substituter/key block in ten files, guarding against something two green runs
say does not happen. If it ever recurs it fails loudly at install, which is
recoverable; the silent-skip above is the failure mode worth engineering
against.
One property is deliberately NOT carried over: the old cachix step ran with
`continue-on-error: true` so a failed cache push could not fail a job whose
tests passed. The action exposes no equivalent, and adding one here would also
swallow genuine setup failures now that the same step installs Nix rather than
only publishing at the end.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(deps): track logos-protocol master again
The rev pin on feat/per-client-token-store existed because the trust-root
surface it needed lived only on that branch while protocol master was still
LOGOS_PROTOCOL_VERSION_MINOR 2. Both comments here said to re-point once it
merged; it has (logos-protocol#59), and master is 0.4.0 — MINOR 4, carrying
lp_token_keys, lp_inform_module_token_to, lp_grant_host_services and
TokenManager::forIdentity / isolateIdentity.
That matters beyond compiling: the cdylib glue's grant forwarding is guarded on
MINOR >= 3, so a master pin taken too early would not have failed loudly — it
would have dropped the grant silently. The guard now opens.
Verified against master rather than assumed: #default builds and the checks pass
(cpp-sdk `tests`, plugin-qt `qt-host-generator`).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: mark the provider macros vestigial and fix the glue banner
logos_provider_object.h still presented LOGOS_PROVIDER / LOGOS_METHOD as "the
developer-facing API" and said logos-cpp-generator --provider-header produces
the dispatch from them. That scanner was removed, so nothing reads LOGOS_METHOD
any more; the macros are kept only so an older TU still compiles. Module code
no longer derives LogosProviderBase either — it derives logos-cpp-sdk's Qt-free
LogosModuleContext, and LogosProviderBase is now the base the *generated*
<name>_cdylib_glue.cpp derives from.
The drift guard in tests/ was written when logos-qt-sdk shipped a rival copy of
this emitter; that copy is gone and the flag now refuses, so the comment is
recast as history. The assertion stays — it pins the property that made the
divergence detectable.
Also fixes the banner this generator stamps into its own output, which
credited "logos-cpp-generator --cdylib". Nothing pins the old string.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor: shed the view-plugin templates, keeping this repo to cdylib loading
This repo's job is making a cdylib module loadable by logos-module-loader-qt.
The LogosView*.in templates, the logos-view-templates output, the
LOGOS_VIEW_TEMPLATE_DIR plumbing and the .rep fixture are ui_qml AUTHORING, a
different concern; they now live in logos-view-module, which already owned that
flavour and inputs only logos-nix so nothing can cycle through it.
Removed: cmake/ (the four templates + README), the logos-view-templates
package, common.nix's viewTemplateDir and its -DLOGOS_VIEW_TEMPLATE_DIR entry,
the LOGOS_VIEW_TEMPLATE_DIR env entries, and tests/rep-file-plugin with its
check.
logos-module-builder supplies LOGOS_VIEW_TEMPLATE_DIR from its own
logos-view-module input through the extraCmakeFlags/extraEnv seams it already
uses for the other five roots, so nothing here needed a new parameter — the
change is pure deletion.
Three sites carried the variable, not two: buildPlugin's env, generate's env
(dead — mkBuildPlugin.generate sets dontConfigure, so cmake never runs and
nothing reads it), and devShellInputs' shellHook, which logos-module-builder
splices verbatim into every module dev shell. Dropping the third without the
builder taking it over would have left `nix develop` broken for every ui_qml
module, visible only when someone ran cmake by hand.
buildPlugin.nix keeps its <name>_replica_factory packaging: the LIBRARY is a
build artifact of the module tree this backend compiles, exactly like the
_plugin library beside it, and packaging what a plugin build emits needs the
output filename convention, not view knowledge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|