probe(qt-consumer): throwaway module proving interface_dependencies works on a Qt api-style module

test-qtbind-probe-module is type: core with NO `interface` key, so the
backend picks apiStyle=qt. It declares interface_dependencies on full_api
(a .lidl copy of the shared contract) plus concrete dependencies on both
providers, forwards echoInt, and re-emits intEvent.

Phase-1 throwaway: delete once the real Qt-typed proxy lands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dario Gabriel Lipicar
2026-07-29 18:09:48 -03:00
co-authored by Claude Opus 5
parent 44c0beca9d
commit 432812a2cc
7 changed files with 241 additions and 0 deletions
+24
View File
@@ -173,6 +173,28 @@
'';
};
# THROWAWAY PROBE (feat/qt-consumer-proxy phase 1): a `type: core` module
# with NO `interface` key — so the backend picks apiStyle=qt — that binds
# the full_api interface at runtime. Answers whether
# interface_dependencies works on a non-universal module and whether the
# generated wrapper is genuinely Qt-typed.
qtbindProbe = mkModule {
src = ./test-qtbind-probe-module;
configFile = ./test-qtbind-probe-module/metadata.json;
flakeInputs = {
test_fullapi_cpp = fullapiCpp;
test_fullapi_rust = fullapiRust;
};
preConfigure = ''
echo "Running logos-cpp-generator --provider-header for test_qtbind_probe..."
logos-cpp-generator --provider-header "$(pwd)/src/test_qtbind_probe_impl.h" --output-dir "$(pwd)"
if [ ! -f logos_provider_dispatch.cpp ]; then
echo "ERROR: logos_provider_dispatch.cpp was not generated" >&2
exit 1
fi
'';
};
# Universal QML+Qt UI plugin consuming full_api via an interface
# dependency. mkQmlModule delegates to the same buildCppPlugin pipeline
# (universal codegen + interface deps) and bundles the QML view.
@@ -224,6 +246,7 @@
test_fullapi_ext_cpp = fullapiExtCpp.packages.${system};
test_fullapi_proxy = fullapiProxy.packages.${system};
test_fullapi_proxy_rust = fullapiProxyRust.packages.${system};
test_qtbind_probe = qtbindProbe.packages.${system};
test_fullapi_ui = fullapiUi.packages.${system};
test_fullapi_ui_qml = fullapiUiQml.packages.${system};
test_context_module_cpp = contextCpp.packages.${system};
@@ -248,6 +271,7 @@
test_fullapi_ext_cpp = fullapiExtCpp.packages.${system}.default;
test_fullapi_proxy = fullapiProxy.packages.${system}.default;
test_fullapi_proxy_rust = fullapiProxyRust.packages.${system}.default;
test_qtbind_probe = qtbindProbe.packages.${system}.default;
test_fullapi_ui = fullapiUi.packages.${system}.default;
test_fullapi_ui_qml = fullapiUiQml.packages.${system}.default;
test_context_module_cpp = contextCpp.packages.${system}.default;
+20
View File
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.14)
project(TestQtbindProbePlugin LANGUAGES CXX)
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/LogosModule.cmake")
include(cmake/LogosModule.cmake)
else()
message(FATAL_ERROR "LogosModule.cmake not found")
endif()
logos_module(
NAME test_qtbind_probe
PROVIDER_HEADER src/test_qtbind_probe_impl.h
SOURCES
src/test_qtbind_probe_loader.h
src/test_qtbind_probe_impl.h
src/test_qtbind_probe_impl.cpp
logos_provider_dispatch.cpp
)
@@ -0,0 +1,54 @@
module full_api {
version "1.0.0"
description "Full API contract: every supported method parameter/return type and event parameter type. Bound at runtime to test_fullapi_cpp, test_fullapi_rust, or either proxy."
method whoAmI() -> tstr
method echoString(v: tstr) -> tstr
method echoBytes(v: bstr) -> bstr
method echoInt(v: int) -> int
method echoUint(v: uint) -> uint
method echoDouble(v: float64) -> float64
method echoBool(v: bool) -> bool
method echoAny(v: any) -> any
method echoStringList(v: [tstr]) -> [tstr]
method echoIntList(v: [int]) -> [int]
method echoUintList(v: [uint]) -> [uint]
method echoDoubleList(v: [float64]) -> [float64]
method echoBoolList(v: [bool]) -> [bool]
method echoList(v: [any]) -> [any]
method echoMap(v: {tstr: any}) -> {tstr: any}
method doVoid() -> void
method echoTriple(i: int, s: tstr, b: bstr) -> tstr
method makeResult(ok: bool) -> result
method fireStringEvent(v: tstr) -> bool
method fireBytesEvent(v: bstr) -> bool
method fireIntEvent(v: int) -> bool
method fireUintEvent(v: uint) -> bool
method fireDoubleEvent(v: float64) -> bool
method fireBoolEvent(v: bool) -> bool
method fireAnyEvent(v: any) -> bool
method fireStringListEvent(v: [tstr]) -> bool
method fireIntListEvent(v: [int]) -> bool
method fireUintListEvent(v: [uint]) -> bool
method fireDoubleListEvent(v: [float64]) -> bool
method fireBoolListEvent(v: [bool]) -> bool
method fireListEvent(v: [any]) -> bool
method fireMapEvent(v: {tstr: any}) -> bool
method fireTripleEvent(i: int, s: tstr, b: bstr) -> bool
event stringEvent(v: tstr)
event bytesEvent(v: bstr)
event intEvent(v: int)
event uintEvent(v: uint)
event doubleEvent(v: float64)
event boolEvent(v: bool)
event anyEvent(v: any)
event stringListEvent(v: [tstr])
event intListEvent(v: [int])
event uintListEvent(v: [uint])
event doubleListEvent(v: [float64])
event boolListEvent(v: [bool])
event listEvent(v: [any])
event mapEvent(v: {tstr: any})
event tripleEvent(i: int, s: tstr, b: bstr)
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "test_qtbind_probe",
"version": "1.0.0",
"type": "core",
"category": "testing",
"description": "THROWAWAY PROBE: smallest possible non-universal (Qt api-style) module that binds the full_api interface at runtime. Exists to answer whether interface_dependencies works on a module with no `interface` key, and whether the generated wrapper really is Qt-typed.",
"main": "test_qtbind_probe_plugin",
"dependencies": ["test_fullapi_cpp", "test_fullapi_rust"],
"interface_dependencies": [
{ "name": "full_api", "file": "interfaces/full_api.lidl" }
],
"nix": {
"packages": {
"build": [],
"runtime": []
},
"external_libraries": [],
"cmake": {
"find_packages": [],
"extra_sources": []
}
}
}
@@ -0,0 +1,58 @@
#include "test_qtbind_probe_impl.h"
#include <QDebug>
// Generated at build time. Because metadata.json declares
// `interface_dependencies`, this umbrella carries a
// `FullApi bind_full_api(const QString&)` factory. Because the module has NO
// `interface` key (type: core), the backend picks apiStyle=qt, so `FullApi` is
// the Qt-TYPED wrapper (QString / QByteArray / qlonglong / qulonglong /
// QVariantList / QVariantMap / LogosResult).
#include "logos_sdk.h"
void TestQtbindProbeImpl::onInit(LogosAPI* api)
{
m_api = api;
delete m_logos;
m_logos = new LogosModules(api);
qDebug() << "TestQtbindProbeImpl: initialized (qt api style, bound interface)";
}
bool TestQtbindProbeImpl::useProvider(const QString& moduleName)
{
if (!m_logos) return false;
m_providerName = moduleName;
delete m_target;
// The whole point of the probe: a runtime-bound Qt-typed interface wrapper.
m_target = new FullApi(m_logos->bind_full_api(moduleName));
// ONE event: subscribe through the generated typed accessor.
m_target->onIntEvent([this](qlonglong v) {
m_lastEvent = QString("intEvent:%1").arg(v);
qDebug() << "TestQtbindProbeImpl: got intEvent" << v;
emitEvent("intEvent", QVariantList() << QVariant::fromValue(v));
});
return true;
}
QString TestQtbindProbeImpl::currentProvider()
{
return m_providerName;
}
qlonglong TestQtbindProbeImpl::echoInt(qlonglong v)
{
if (!m_target) return 0;
return m_target->echoInt(v);
}
bool TestQtbindProbeImpl::fireIntEvent(qlonglong v)
{
if (!m_target) return false;
return m_target->fireIntEvent(v);
}
QString TestQtbindProbeImpl::getLastEvent()
{
return m_lastEvent;
}
@@ -0,0 +1,40 @@
#ifndef TEST_QTBIND_PROBE_IMPL_H
#define TEST_QTBIND_PROBE_IMPL_H
#include "logos_provider_object.h"
#include "logos_api.h"
#include "logos_api_client.h"
#include "logos_sdk.h"
#include "logos_types.h"
// THROWAWAY PROBE. Binds the `full_api` interface at runtime through the
// generated Qt-typed wrapper (`modules().bind_full_api(name)`), forwards ONE
// method and subscribes to ONE event. Nothing else.
class TestQtbindProbeImpl : public LogosProviderBase
{
LOGOS_PROVIDER(TestQtbindProbeImpl, "test_qtbind_probe", "1.0.0")
protected:
void onInit(LogosAPI* api) override;
public:
// Bind the interface to a concrete provider module name at runtime.
LOGOS_METHOD bool useProvider(const QString& moduleName);
LOGOS_METHOD QString currentProvider();
// The one forwarded method.
LOGOS_METHOD qlonglong echoInt(qlonglong v);
// The one event: subscribe to the target's `intEvent`, record what arrived.
LOGOS_METHOD bool fireIntEvent(qlonglong v);
LOGOS_METHOD QString getLastEvent();
private:
LogosAPI* m_api = nullptr;
LogosModules* m_logos = nullptr;
FullApi* m_target = nullptr;
QString m_providerName;
QString m_lastEvent;
};
#endif // TEST_QTBIND_PROBE_IMPL_H
@@ -0,0 +1,21 @@
#ifndef TEST_QTBIND_PROBE_LOADER_H
#define TEST_QTBIND_PROBE_LOADER_H
#include <QObject>
#include "interface.h"
#include "logos_provider_object.h"
#include "test_qtbind_probe_impl.h"
class TestQtbindProbeLoader : public QObject, public PluginInterface, public LogosProviderPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID LogosProviderPlugin_iid FILE "metadata.json")
Q_INTERFACES(PluginInterface LogosProviderPlugin)
public:
QString name() const override { return "test_qtbind_probe"; }
QString version() const override { return "1.0.0"; }
LogosProviderObject* createProviderObject() override { return new TestQtbindProbeImpl(); }
};
#endif // TEST_QTBIND_PROBE_LOADER_H