From 3d7e3e68b37d75b9e771cb1fa11d37649decd8b2 Mon Sep 17 00:00:00 2001 From: Dario Gabriel Lipicar Date: Sat, 15 Aug 2026 10:55:33 -0300 Subject: [PATCH] feat: the view templates exist once, and this repo is where MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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..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 --- .github/workflows/ci.yml | 6 ++ cmake/LogosViewPluginBase.cpp.in | 18 ++++++ cmake/LogosViewPluginBase.h.in | 44 +++++++++++++ .../LogosViewReplicaFactory.cpp.in | 0 cmake/LogosViewReplicaFactory.h.in | 41 ++++++++++++ cmake/README.md | 64 +++++++++++++++++++ flake.nix | 17 +++++ lib/common.nix | 9 +++ lib/default.nix | 22 +++++++ tests/rep-file-plugin/CMakeLists.txt | 18 +++++- .../cmake/LogosViewReplicaFactory.h.in | 31 --------- tests/test-rep-file-plugin.nix | 19 ++++-- 12 files changed, 249 insertions(+), 40 deletions(-) create mode 100644 cmake/LogosViewPluginBase.cpp.in create mode 100644 cmake/LogosViewPluginBase.h.in rename {tests/rep-file-plugin/cmake => cmake}/LogosViewReplicaFactory.cpp.in (100%) create mode 100644 cmake/LogosViewReplicaFactory.h.in create mode 100644 cmake/README.md delete mode 100644 tests/rep-file-plugin/cmake/LogosViewReplicaFactory.h.in diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc01dc4..eeb4421 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,12 @@ jobs: - name: Build vanilla Qt plugin test run: nix build '.#checks.x86_64-linux.vanilla-plugin' + # The check that instantiates cmake/LogosView*.in. It existed but was + # never listed here, so the templates had no CI coverage at all — which + # is part of why a duplicate of them could sit in the fixture unnoticed. + - name: Build .rep replica factory test + run: nix build '.#checks.x86_64-linux.rep-file-plugin' + - name: Header generator guard test run: nix build '.#checks.x86_64-linux.header-generator-guard' diff --git a/cmake/LogosViewPluginBase.cpp.in b/cmake/LogosViewPluginBase.cpp.in new file mode 100644 index 0000000..c3c9357 --- /dev/null +++ b/cmake/LogosViewPluginBase.cpp.in @@ -0,0 +1,18 @@ +// Auto-generated by logos_module(REP_FILE ...) — DO NOT EDIT. +#include "LogosViewPluginBase.h" +#include "rep_@LOGOS_REP_BASE@_source.h" + +#include + +bool @LOGOS_REP_CLASS@ViewPluginBase::enableRemoting(QRemoteObjectHostBase* host) +{ + if (!host || !m_backend) return false; + // The backend must inherit @LOGOS_REP_CLASS@SimpleSource (which the rep + // compiler generates from @LOGOS_REP_BASE@.rep). Using the templated + // enableRemoting<@LOGOS_REP_CLASS@SourceAPI> overload publishes the + // typed source signature so typed replicas on the client side reach the + // Valid state and start receiving property updates. + auto* src = qobject_cast<@LOGOS_REP_CLASS@SimpleSource*>(m_backend); + if (!src) return false; + return host->enableRemoting<@LOGOS_REP_CLASS@SourceAPI>(src); +} diff --git a/cmake/LogosViewPluginBase.h.in b/cmake/LogosViewPluginBase.h.in new file mode 100644 index 0000000..2ed1be4 --- /dev/null +++ b/cmake/LogosViewPluginBase.h.in @@ -0,0 +1,44 @@ +// Auto-generated by logos_module(REP_FILE ...) — DO NOT EDIT. +#pragma once + +#include + +class QObject; +class QRemoteObjectHostBase; + +// The module side of the LogosViewPlugin interface. ui-host declares it too, +// in logos-view-module-runtime/include/LogosViewPlugin.h; the two never share +// a header on purpose — a module plugin must compile against Qt alone, without +// the host runtime on its include path. They meet at runtime through the IID +// string, the standard Qt plugin-interface pattern. +// +// The agreement is CHECKED, not requested: logos-module-builder's +// `view-interface-abi` check (tests/test-view-interface-abi.nix, run by that +// repo's CI) compares the IID and the pure-virtual signature list on both +// sides and fails the build if they differ. +class LogosViewPlugin { +public: + virtual ~LogosViewPlugin() = default; + virtual QObject* viewObject() = 0; + virtual bool enableRemoting(QRemoteObjectHostBase* host) = 0; +}; + +#define LogosViewPlugin_iid "logos.view.plugin/1.0" +Q_DECLARE_INTERFACE(LogosViewPlugin, LogosViewPlugin_iid) + +// Concrete base generated per-module. Plugins inherit from this and call +// setBackend() once their backend QObject is constructed; enableRemoting() +// is implemented in the generated .cpp using the repc-generated +// @LOGOS_REP_CLASS@SourceAPI type, so typed replicas on the client side +// match signatures. +class @LOGOS_REP_CLASS@ViewPluginBase : public LogosViewPlugin { +public: + QObject* viewObject() override { return m_backend; } + bool enableRemoting(QRemoteObjectHostBase* host) override; + +protected: + void setBackend(QObject* backend) { m_backend = backend; } + +private: + QObject* m_backend = nullptr; +}; diff --git a/tests/rep-file-plugin/cmake/LogosViewReplicaFactory.cpp.in b/cmake/LogosViewReplicaFactory.cpp.in similarity index 100% rename from tests/rep-file-plugin/cmake/LogosViewReplicaFactory.cpp.in rename to cmake/LogosViewReplicaFactory.cpp.in diff --git a/cmake/LogosViewReplicaFactory.h.in b/cmake/LogosViewReplicaFactory.h.in new file mode 100644 index 0000000..4bb8e9e --- /dev/null +++ b/cmake/LogosViewReplicaFactory.h.in @@ -0,0 +1,41 @@ +// Auto-generated by logos_module(REP_FILE ...) — DO NOT EDIT. +#pragma once + +#include +#include + +class QRemoteObjectNode; +class QMetaObject; + +// The module side of the LogosViewReplicaFactory plugin interface. The host +// side declares it too, in logos-view-module-runtime/include/ +// LogosViewReplicaFactory.h; the two never share a header on purpose — a +// module plugin must compile against Qt alone, without the host runtime on its +// include path. They meet at runtime through the IID string, the standard Qt +// plugin-interface pattern, and a mismatch is silent: qobject_cast returns +// nullptr and the view simply never appears. +// +// So the agreement is CHECKED, not requested. logos-module-builder's +// `view-interface-abi` check (tests/test-view-interface-abi.nix, run by that +// repo's CI) extracts the IID and the pure-virtual signature list from this +// template and from the runtime header and fails the build if they differ. +// Edit one side and that check goes red — do not add a "keep in sync" note +// here instead, one lived here for a long time and never stopped a drift. +class LogosViewReplicaFactory { +public: + virtual ~LogosViewReplicaFactory() = default; + virtual QObject* acquire(QRemoteObjectNode* node) = 0; + virtual const QMetaObject* replicaMetaObject() const = 0; +}; + +#define LogosViewReplicaFactory_iid "logos.view.replica_factory/1.0" +Q_DECLARE_INTERFACE(LogosViewReplicaFactory, LogosViewReplicaFactory_iid) + +class @LOGOS_FACTORY_CLASS@ : public QObject, public LogosViewReplicaFactory { + Q_OBJECT + Q_PLUGIN_METADATA(IID LogosViewReplicaFactory_iid) + Q_INTERFACES(LogosViewReplicaFactory) +public: + QObject* acquire(QRemoteObjectNode* node) override; + const QMetaObject* replicaMetaObject() const override; +}; diff --git a/cmake/README.md b/cmake/README.md new file mode 100644 index 0000000..7974bab --- /dev/null +++ b/cmake/README.md @@ -0,0 +1,64 @@ +# `LogosView*.in` — the view-plugin templates, and why they live here + +These four files are `configure_file` templates instantiated per module by +`logos_module(REP_FILE ...)`: + +| template | produces | linked into | +|---|---|---| +| `LogosViewPluginBase.{h,cpp}.in` | `ViewPluginBase` | the module plugin | +| `LogosViewReplicaFactory.{h,cpp}.in` | `ReplicaFactoryPlugin` | `_replica_factory` | + +They are pure Qt: `Q_OBJECT`, `Q_PLUGIN_METADATA`, `repc`-generated +`*SimpleSource`/`*Replica` types, `qmlRegisterUncreatableMetaObject`. Nothing +outside the Qt backend 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. + +## Why this repo and not logos-module-builder + +`LogosModule.cmake` — the code that instantiates these templates — lives in +logos-module-builder, so that repo looks like the obvious home, and the +templates did live next to it. But two consumers need them: + +1. logos-module-builder's `LogosModule.cmake`, for every real `ui_qml` module; +2. this repo's `tests/rep-file-plugin` fixture, which is the check that proves + the repc + factory pipeline still produces a loadable plugin. + +The dependency runs **logos-module-builder → logos-plugin-qt**, one way. A +fixture inside logos-plugin-qt therefore cannot reach logos-module-builder, and +that is exactly how the fixture ended up holding a byte-identical second copy +with nothing comparing the two. This repo is the only place both consumers can +read from, so ownership moves to the direction that actually works: the +templates are published here, and logos-module-builder receives the directory +as `LOGOS_VIEW_TEMPLATE_DIR`. + +`LogosModule.cmake` itself does **not** move back here — that file is +logos-module-builder's and stays there. Only the Qt-specific templates it +instantiates are published from this side of the edge. + +## How consumers get them + +* nix: `packages..logos-view-templates`, and + `lib.buildPlugin` / `lib.generate` set `LOGOS_VIEW_TEMPLATE_DIR` in the + build environment automatically, so a module build needs no extra wiring. +* CMake: `logos_module()` resolves `LOGOS_VIEW_TEMPLATE_DIR` (cache variable + first, then environment) and hard-errors when it is unset. There is no + "look next to LogosModule.cmake" fallback — a silent fallback to a second + copy is the failure this layout exists to remove. + +## 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, and logos-view-module-runtime depends on *this* repo, so the +include could only ever go the wrong way. 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) extracts +the IID and the pure-virtual signature list from each and fails on any +difference. diff --git a/flake.nix b/flake.nix index 7db6251..6cc9562 100644 --- a/flake.nix +++ b/flake.nix @@ -71,7 +71,24 @@ # invisible until something depended on it. The CMake module is the # builder's build-system contract — it reads LOGOS_API_STYLE, # LOGOS_MODULE_GO_STATIC_LIBS, generated_code/ — so it lives there, once. + # + # cmake/ came back for a narrower reason: the four LogosView*.in + # templates. Those had the mirror-image problem — they sat next to + # LogosModule.cmake in the builder, but this repo's rep-file-plugin + # fixture also instantiates them and cannot reach the builder, so it kept + # a byte-identical second copy with nothing comparing the two. See + # cmake/README.md. packages = forAllSystems ({ pkgs, system, ... }: { + # The LogosView*.in templates, as a nameable output. `logos_module()` + # gets the same directory through LOGOS_VIEW_TEMPLATE_DIR; this output + # exists so a consumer (logos-module-builder's view-interface-abi + # check) can refer to the templates without depending on the layout of + # this repo's source tree. + logos-view-templates = pkgs.runCommand "logos-view-templates" { } '' + mkdir -p $out + cp ${./cmake}/LogosView*.in $out/ + ''; + logos-qt-host = import ./nix/qt-host.nix { inherit pkgs; src = ./.; diff --git a/lib/common.nix b/lib/common.nix index b014a8a..433c6cc 100644 --- a/lib/common.nix +++ b/lib/common.nix @@ -47,12 +47,21 @@ rec { pkgs.qt6.qtremoteobjects ]; + # The ONE copy of the LogosView*.in templates that logos_module(REP_FILE ...) + # instantiates. See ../cmake/README.md for why they are owned here rather + # than next to LogosModule.cmake: logos-module-builder depends on this repo + # and not the reverse, so this is the only directory both consumers of the + # templates — that repo's LogosModule.cmake, and this repo's + # tests/rep-file-plugin fixture — can read from. + viewTemplateDir = ../cmake; + # CMake flags for Qt plugin builds. # Only includes logosModule (for interface.h). # SDK flags are added by the builder layer, not here. commonCmakeFlags = { logosModule }: [ "-GNinja" "-DLOGOS_MODULE_ROOT=${logosModule}" + "-DLOGOS_VIEW_TEMPLATE_DIR=${viewTemplateDir}" ]; # Platform-specific post-build commands for library path fixing diff --git a/lib/default.nix b/lib/default.nix index f2abb4a..60e59ee 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -55,6 +55,15 @@ in { # whatever this repo happens to contain. env = { LOGOS_MODULE_ROOT = "${logosModule}"; + # The LogosView*.in templates logos_module(REP_FILE ...) instantiates. + # They are owned by THIS repo (see ../cmake/README.md) because + # logos-module-builder depends on it and not the reverse — that + # direction is what lets the one copy serve both that repo's + # LogosModule.cmake and this repo's tests/rep-file-plugin fixture. + # Passed as an env var as well as a cmake flag so a module that drives + # cmake itself still resolves it; LogosModule.cmake hard-errors when + # neither is set rather than falling back to a sibling directory. + LOGOS_VIEW_TEMPLATE_DIR = "${common.viewTemplateDir}"; } // extraEnv; meta = with lib; { description = config.description; @@ -104,6 +113,15 @@ in { # whatever this repo happens to contain. env = { LOGOS_MODULE_ROOT = "${logosModule}"; + # The LogosView*.in templates logos_module(REP_FILE ...) instantiates. + # They are owned by THIS repo (see ../cmake/README.md) because + # logos-module-builder depends on it and not the reverse — that + # direction is what lets the one copy serve both that repo's + # LogosModule.cmake and this repo's tests/rep-file-plugin fixture. + # Passed as an env var as well as a cmake flag so a module that drives + # cmake itself still resolves it; LogosModule.cmake hard-errors when + # neither is set rather than falling back to a sibling directory. + LOGOS_VIEW_TEMPLATE_DIR = "${common.viewTemplateDir}"; } // extraEnv; meta = with lib; { description = config.description; @@ -162,6 +180,10 @@ in { buildInputs = common.commonBuildInputs pkgs; shellHook = '' ${if logosModule != null then ''export LOGOS_MODULE_ROOT="${logosModule}"'' else ""} + # Same directory the nix build passes in, so a hand-run `cmake` inside a + # module's dev shell resolves the templates the way logos_module() does. + # Without it a ui_qml module would hit LogosModule.cmake's hard error. + export LOGOS_VIEW_TEMPLATE_DIR="${common.viewTemplateDir}" ''; }; diff --git a/tests/rep-file-plugin/CMakeLists.txt b/tests/rep-file-plugin/CMakeLists.txt index a80777b..a96a58c 100644 --- a/tests/rep-file-plugin/CMakeLists.txt +++ b/tests/rep-file-plugin/CMakeLists.txt @@ -7,8 +7,22 @@ set(CMAKE_AUTOMOC ON) find_package(Qt6 REQUIRED COMPONENTS Core RemoteObjects Qml) -# ── Locate templates (injected by the nix test harness into cmake/) ──────── -set(TEMPLATE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +# ── Locate templates ────────────────────────────────────────────────────── +# The fixture does NOT carry its own copy. It used to: the templates were +# moved in here when this repo's duplicate LogosModule.cmake was deleted, and +# they then sat byte-identical to logos-module-builder's with nothing comparing +# them. They are owned by this repo now (../../cmake) and the harness passes +# that directory in, the same way logos_module() receives it in a real build. +if(NOT LOGOS_VIEW_TEMPLATE_DIR AND DEFINED ENV{LOGOS_VIEW_TEMPLATE_DIR}) + set(LOGOS_VIEW_TEMPLATE_DIR "$ENV{LOGOS_VIEW_TEMPLATE_DIR}") +endif() +if(NOT LOGOS_VIEW_TEMPLATE_DIR) + message(FATAL_ERROR + "rep-file-plugin: LOGOS_VIEW_TEMPLATE_DIR is not set. Pass the " + "repository's cmake/ directory; this fixture has no copy to fall " + "back to, deliberately.") +endif() +set(TEMPLATE_DIR "${LOGOS_VIEW_TEMPLATE_DIR}") # ── .rep file under test ────────────────────────────────────────────────── set(REP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/rep_test.rep") diff --git a/tests/rep-file-plugin/cmake/LogosViewReplicaFactory.h.in b/tests/rep-file-plugin/cmake/LogosViewReplicaFactory.h.in deleted file mode 100644 index a4bee58..0000000 --- a/tests/rep-file-plugin/cmake/LogosViewReplicaFactory.h.in +++ /dev/null @@ -1,31 +0,0 @@ -// Auto-generated by logos_module(REP_FILE ...) — DO NOT EDIT. -#pragma once - -#include -#include - -class QRemoteObjectNode; -class QMetaObject; - -// Local copy of the LogosViewReplicaFactory interface. The host side has its -// own copy in logos-view-module-runtime; they match by IID string, which is -// the standard Qt plugin-interface pattern. Keep the method signatures in -// sync with logos-view-module-runtime/src/LogosViewReplicaFactory.h. -class LogosViewReplicaFactory { -public: - virtual ~LogosViewReplicaFactory() = default; - virtual QObject* acquire(QRemoteObjectNode* node) = 0; - virtual const QMetaObject* replicaMetaObject() const = 0; -}; - -#define LogosViewReplicaFactory_iid "logos.view.replica_factory/1.0" -Q_DECLARE_INTERFACE(LogosViewReplicaFactory, LogosViewReplicaFactory_iid) - -class @LOGOS_FACTORY_CLASS@ : public QObject, public LogosViewReplicaFactory { - Q_OBJECT - Q_PLUGIN_METADATA(IID LogosViewReplicaFactory_iid) - Q_INTERFACES(LogosViewReplicaFactory) -public: - QObject* acquire(QRemoteObjectNode* node) override; - const QMetaObject* replicaMetaObject() const override; -}; diff --git a/tests/test-rep-file-plugin.nix b/tests/test-rep-file-plugin.nix index 3a030dc..18588da 100644 --- a/tests/test-rep-file-plugin.nix +++ b/tests/test-rep-file-plugin.nix @@ -4,6 +4,11 @@ let pluginSrc = ./rep-file-plugin; + # The ONE copy of the templates, straight from this repo's cmake/. The + # fixture is handed the same directory logos_module() is handed in a real + # build, so this check now exercises the file every ui_qml module compiles — + # not a private duplicate of it that could pass while the real one is broken. + viewTemplates = ../cmake; in pkgs.stdenv.mkDerivation { pname = "logos-plugin-qt-rep-file-test"; @@ -18,19 +23,19 @@ pkgs.stdenv.mkDerivation { dontUseCmakeConfigure = true; - # The templates live in the fixture's own cmake/ (tests/rep-file-plugin/cmake). - # They used to be copied in from this repo's cmake/ — the same directory that - # held the duplicate LogosModule.cmake. That directory is gone: the CMake - # module and the templates it instantiates are logos-module-builder's, and - # exist once, there. What is left here is a test fixture, and it says so by - # living under tests/. + # The templates are this repo's, and there is exactly one copy of them: + # cmake/. LogosModule.cmake still belongs to logos-module-builder — only the + # Qt-specific templates it instantiates are published from this side, because + # logos-module-builder depends on this repo and not the reverse, so this is + # the only directory both it and this fixture can read. See cmake/README.md. + LOGOS_VIEW_TEMPLATE_DIR = "${viewTemplates}"; buildPhase = '' runHook preBuild mkdir -p build cd build - cmake .. -GNinja + cmake .. -GNinja -DLOGOS_VIEW_TEMPLATE_DIR="$LOGOS_VIEW_TEMPLATE_DIR" ninja cd ..