Files
Dario Gabriel LipicarandClaude Opus 5 d6c8885494 feat(view-generator): own the ui plugin emitter, and the header it pairs with
The `--backend ui` emitter existed twice: live in logos-qt-sdk's
qt-generator, and as an uncalled successor here. They were byte-identical
until logos-qt-sdk#38 gave the live one the module teardown hook, and this
copy has been ~45 lines behind ever since.

That drift is dangerous in a specific way. ui-host reaches aboutToUnload()
BY NAME through the meta-object; a plugin class that does not declare it has
no such meta-method, invokeMethod returns false, and the host moves on —
INDISTINGUISHABLE from a plugin answering "Synchronous, nothing to wait
for". Had the migration completed with this copy, every view would have
silently and permanently lost its chance to finish, and nothing would have
failed: not a build, not a load, not a call.

This repo is the right home. The emitter needs nothing from logos-qt-sdk —
`--backend ui` is the one qt-generator backend that never reads LIDL, and
its only qt-sdk symbol was lidlToPascalCase, which this copy already
inlines. The LogosView*.in templates the generated glue compiles against
already live here. And the backend owning its glue is the same rule that
puts the cdylib glue generator in logos-plugin-qt.

logos_ui_plugin_context.h HAD TO COME TOO, which the plan did not
anticipate. It was duplicated as well, and stale by the same changeset. The
emitter and that header are a matched pair — the emitter writes a call, the
header declares what it calls — and they stayed consistent only because one
pin versioned both. Moving the emitter alone fails concretely:
`no member named 'maybeUiPluginAboutToUnload' in namespace '_logos_codegen_'`.

The emitter is taken VERBATIM: the .cpp differs from the live copy only in
three generator-name banners, and the .h and the context header are
byte-identical to it. Every difference in the successor was it being behind
or vaguer — two were outright regressions (the .h named LogosModuleContext,
the exact confusion the live comment warns against, and the .cpp described
emitted files by names neither main uses).

main.cpp accepts `--backend ui` and refuses anything else, converging with
the in-flight qml-dep branch rather than colliding with it.

TESTS CARRIED ACROSS, because the old home's blind spot is the whole story:
`--backend ui` had NO test at all, which is how it became the one generated
plugin class without the hook. qt-sdk's five text assertions come over, plus
a new behavioural check: tests/ui-plugin-metaobject compiles the emitted
plugin against the REAL context header and REAL templates, loads it with
QPluginLoader, and dumps the meta-object —

    [4] SIGNAL unloadFinished()  [5] METHOD initLogos  [6] METHOD aboutToUnload -> int

Negative control: origin/master's stale emitter compiles and loads fine and
the check still fails with both named diagnostics. The silent gap, made loud.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 20:27:16 -03:00
..

LogosView*.in — the view-plugin templates

These four files are configure_file templates instantiated per module by logos_replica_factory() (this directory's LogosViewModule.cmake) and by logos_module(REP_FILE ...) in logos-module-builder:

template produces linked into
LogosViewPluginBase.{h,cpp}.in <Rep>ViewPluginBase the module plugin
LogosViewReplicaFactory.{h,cpp}.in <Rep>ReplicaFactoryPlugin <name>_replica_factory

They are pure Qt: Q_OBJECT, Q_PLUGIN_METADATA, repc-generated *SimpleSource/*Replica types, qmlRegisterUncreatableMetaObject. Nothing outside a Qt view plugin can use them.

The rule

There is exactly one copy of each, and it is this one. No consumer keeps a local copy — not even a test fixture. LogosModule.cmake in logos-module-builder hard-errors when LOGOS_VIEW_TEMPLATE_DIR is unset rather than falling back to a sibling copy, and so does tests/rep-file-plugin here: a silent fallback to a second copy is the failure this layout exists to remove.

They are also siblings of LogosViewModule.cmake by requirement — that function resolves them through CMAKE_CURRENT_FUNCTION_LIST_DIR. Move the .cmake without them and configure_file() fails.

Why this repo

This repo owns the ui_qml / interface: universal authoring flavour end to end: the .rep-driven generator, LogosViewModule.cmake, the LogosUiPluginContext header, and these templates. A view module is a distinct authoring flavour rather than a variant of a core module, so changing how views are built is a one-repo change.

It also can be everyone's copy. This repo is a leaf — its only flake input is logos-nix — so any consumer can depend on it without a cycle. That is the property the previous home lacked.

What moved, and why

These templates used to live in logos-plugin-qt (cmake/), and before that next to LogosModule.cmake in logos-module-builder, with each move driven by which repo the then-current set of consumers could all reach:

  • next to LogosModule.cmake: the natural home, since that is the code that instantiates them — but the rep-file-plugin fixture in logos-plugin-qt also instantiates them and cannot reach logos-module-builder (the dependency runs logos-module-builder → logos-plugin-qt, one way), so the fixture ended up holding a byte-identical second copy with nothing comparing the two;
  • logos-plugin-qt: fixed that, because logos-module-builder can read it and the fixture lives there — but it made the Qt plugin-loading backend the owner of a view-plugin authoring concern.

logos-plugin-qt now handles exclusively what makes a cdylib module loadable by logos-module-loader-qt. Its cmake/ directory is gone, this repo took the templates and the rep-file-plugin fixture, and logos-module-builder reads them from here. The set of consumers did not change; only the repo all of them can reach did.

LogosModule.cmake did not come along — that file is logos-module-builder's build-system contract (LOGOS_API_STYLE, LOGOS_MODULE_GO_STATIC_LIBS, generated_code/) and stays there. Only the Qt-specific view templates it instantiates are published from this side.

How consumers get them

  • nix: packages.<system>.logos-view-templates — the four .in files flat in one directory, which is the shape LOGOS_VIEW_TEMPLATE_DIR wants. packages.<system>.cmake-module publishes the same bytes under share/cmake/LogosViewModule/, next to the .cmake that resolves them as siblings. Same content, two addressing schemes, one source.
  • logos-module-builder passes that directory into every ui_qml module build — as -DLOGOS_VIEW_TEMPLATE_DIR= and as an exported environment variable, so both nix build and a hand-run cmake in a module dev shell resolve it.
  • CMake: a consumer resolves LOGOS_VIEW_TEMPLATE_DIR (cache variable first, then environment) and hard-errors when it is unset.

The interface declared inside the templates

LogosViewPlugin and LogosViewReplicaFactory are also declared, separately, by logos-view-module-runtime — the host that loads these plugins. That duplication is deliberate and cannot be collapsed: a module plugin must build against Qt alone, without the host runtime on its include path. The two declarations meet at runtime via the IID string, where a mismatch is silent.

That pair is enforced instead of documented: logos-module-builder's view-interface-abi check (it is the one repo that can see both sides) compares them and fails on any difference. It reads three separate things, because three separate strings have to line up:

  • #define <Name>_iid and the ordered pure-virtual list — the abstract shape;
  • the argument of Q_DECLARE_INTERFACE, resolved through the #defines, since that is what qobject_cast compares and it need not be the macro;
  • on the concrete classes in these templates — the ones carrying Q_OBJECT, Q_PLUGIN_METADATA and Q_INTERFACES — the exported IID and the declared interface list.

That last group is the runtime binding, and for a while it was outside the check's window: bumping Q_PLUGIN_METADATA(IID ...) to /2.0, or deleting Q_INTERFACES, left the guard green while breaking a real view.

Locally, ../tests/rep-file-plugin covers the same two mutations from the other direction — it builds a plugin from these templates, asserts the exact IID in the resulting binary (not a substring of it), then QPluginLoaders it and performs the same qobject_cast the host does.