mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 09:41:06 +00:00
chore(generator): retire ApiStyle::Std
The Std surface — std-typed signatures over a QVariant + LogosAPIClient body — no longer had a caller. `interface: "universal"` modules moved to `lp` (std types over the Qt-free logos-protocol C ABI), and nothing else ever selected it, so every Std branch was dead weight sitting in front of the two live ones. `--api-style=std` is now rejected with a message naming the retirement rather than aliased to `qt`. A stale caller that still passes it wants std signatures; handing it the Qt surface would fail later, further from the cause. The collapse is deliberate about the branches where Std was tested BEFORE Qt, since a naive "delete the block containing ApiStyle::Std" changes Qt output: - makeHeader's include block tested Std first, so its `else` is the Qt include list — the Qt includes are kept and promoted, not deleted. - recordToWireExpr / recordFromWireExpr returned the Qt map form from a guarded `if` and the Std form from the function's trailing `return`. The guard is dropped and the Qt form promoted to the tail; deleting only the trailing return would have left a path falling off the end. - The private-member `else if (!events.isEmpty())` arm reads as an event test but was Std-only; the Qt arm (m_eventReplica + m_eventSource) survives, so setEventSource/trigger still have their storage. - `if (apiStyle == Qt || !events.isEmpty())` is a disjunction, not an Std branch: it unwraps to an unconditional emit, keeping ensureReplica()'s declaration next to its definition. - `isRec || style == Std` loses only the right disjunct — dropping `isRec ||` would double-wrap record fields in QVariant::fromValue. mapParamTypeStd / mapReturnTypeStd / isStdRefType stay: they are the shared std type table that ApiStyle::Lp reaches through the non-Qt arm of paramTypeFor / returnTypeFor / byRefFor and directly from lpPushExpr / lpFromJsonExpr. Verified by output equivalence rather than by the build succeeding: the generator was run over 13 fixture cases (the full_api contract as both a bound interface and a baked dep, three record-bearing contracts incl. map-of-record fields, the chat module's production contract, and a no-events contract) for both qt and lp, before and after. `diff -r` over the 194 resulting files reports no differences, and the experimental --lidl backends are byte-identical too. Test suite: 180/180, unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8e7ed6e0ec
commit
70586290d3
@@ -222,7 +222,7 @@ public:
|
||||
LogosMap doWork(const std::string& input) {
|
||||
// Cross-module call through the flat LogosModules aggregator.
|
||||
// Because this module is `interface: "universal"`, mkLogosModule.nix
|
||||
// passed -DLOGOS_API_STYLE=std to the codegen, so every <Dep>
|
||||
// passed -DLOGOS_API_STYLE=lp to the codegen, so every <Dep>
|
||||
// wrapper takes/returns std types — no Qt at the call site.
|
||||
std::string reply = modules().some_dep.echo(input);
|
||||
// ...
|
||||
@@ -290,13 +290,18 @@ Each module's build picks **one** API style for the generated `<Module>` client
|
||||
|
||||
| `metadata.json#interface` | `LOGOS_API_STYLE` | Wrapper signatures |
|
||||
|---|---|---|
|
||||
| `"universal"` | `std` | `std::string`, `std::vector<std::string>`, `LogosMap`, `LogosList`, `int64_t`, `StdLogosResult` |
|
||||
| `"universal"` / `"cdylib"` | `lp` | `std::string`, `std::vector<std::string>`, `LogosMap`, `LogosList`, `int64_t`, `StdLogosResult` |
|
||||
| `"legacy"` / `"provider"` / absent | `qt` (default) | `QString`, `QStringList`, `QVariantList`, `QVariantMap`, `int`, `LogosResult` |
|
||||
|
||||
`mkLogosModule.nix` reads `interface` and threads `-DLOGOS_API_STYLE=std` through to the codegen for universal modules; everyone else defaults to Qt and stays bit-for-bit backward compatible. Inside the universal module's `.cpp`, the call site is:
|
||||
A third value, `std`, used to name a std-typed surface whose body still went
|
||||
through `QVariant` + `LogosAPIClient`. It was retired once universal modules
|
||||
moved to `lp`; `--api-style=std` is now rejected outright rather than aliased,
|
||||
so a stale build fails loudly instead of silently getting Qt signatures.
|
||||
|
||||
`mkLogosModule.nix` reads `interface` and threads `-DLOGOS_API_STYLE=lp` through to the codegen for universal modules; everyone else defaults to Qt and stays bit-for-bit backward compatible. Inside the universal module's `.cpp`, the call site is:
|
||||
|
||||
```cpp
|
||||
// Universal module (api-style=std):
|
||||
// Universal module (api-style=lp):
|
||||
std::string reply = modules().some_dep.echo("hi");
|
||||
```
|
||||
|
||||
@@ -307,7 +312,7 @@ std::string reply = modules().some_dep.echo("hi");
|
||||
QString reply = modules().some_dep.echo(QString("hi"));
|
||||
```
|
||||
|
||||
The wire is identical (`QVariant` under the hood); for std mode the Qt↔std conversion is inlined in the generated wrapper's `.cpp`, so the calling translation unit needs zero Qt headers.
|
||||
The two carry the same values; the `lp` wrapper marshals them over the logos-protocol C ABI (`lp_*`) instead of `QVariant`, so the calling translation unit needs zero Qt headers and links no qt-sdk.
|
||||
|
||||
> **Migrating to std types**: The choice is driven entirely by `interface`. A handcrafted module that wants std types should switch to `interface: "universal"` — there's no per-flag override on `metadata.json`.
|
||||
|
||||
|
||||
@@ -92,7 +92,9 @@ The codegen exposes **one** wrapper class per module — `<Module>` — with sig
|
||||
| `--api-style` | Wrapper signatures |
|
||||
|---|---|
|
||||
| `qt` (default) | `QString` / `QStringList` / `QVariantList` / `QVariantMap` / `int` / `LogosResult` |
|
||||
| `std` | `std::string` / `std::vector<std::string>` / `LogosMap` / `LogosList` / `int64_t` / `StdLogosResult` |
|
||||
| `lp` | `std::string` / `std::vector<std::string>` / `LogosMap` / `LogosList` / `int64_t` / `StdLogosResult`, over the Qt-free logos-protocol C ABI |
|
||||
|
||||
(A third value, `std` — std signatures over a `QVariant` / `LogosAPIClient` body — was retired; the generator now rejects `--api-style=std` instead of aliasing it.)
|
||||
|
||||
Both styles emit:
|
||||
|
||||
@@ -113,15 +115,15 @@ Only the modules explicitly listed as dependencies are exposed. The runtime's `c
|
||||
|
||||
`ApiStyle` enum + new helpers in `generator_lib`:
|
||||
|
||||
- `enum class ApiStyle { Qt, Std }` — passed to every wrapper-emitting function.
|
||||
- File-local `mapParamTypeStd` / `mapReturnTypeStd` / `stdParamToQVariant` / `qVariantToStdReturn` — std-side type-mapping + Qt↔std conversion expressions. Hidden from `generator_lib.h` (not part of the public surface).
|
||||
- `makeHeader(moduleName, className, methods, apiStyle, events)` / `makeSource(moduleName, className, headerBaseName, methods, apiStyle, events)` — single entry points that branch on `apiStyle` internally to emit the right include block, signature shape, and conversion bridges. `events` is loaded from a `<name>.lidl` sidecar via `--events-from`; when non-empty, the wrapper also gets one typed `on<EventName>(callback)` adapter per declared event (callback arg types follow `apiStyle`). The std-style wrapper grows the necessary `ensureReplica()` plumbing on demand.
|
||||
- `enum class ApiStyle { Qt, Lp }` — passed to every wrapper-emitting function.
|
||||
- File-local `mapParamTypeStd` / `mapReturnTypeStd` — the std-side type-mapping table the `lp` surface exposes. Hidden from `generator_lib.h` (not part of the public surface).
|
||||
- `makeHeader(moduleName, className, methods, apiStyle, events)` / `makeSource(moduleName, className, headerBaseName, methods, apiStyle, events)` — single entry points that branch on `apiStyle` internally to emit the right include block, signature shape, and conversion bridges. `events` is loaded from a `<name>.lidl` sidecar via `--events-from`; when non-empty, the wrapper also gets one typed `on<EventName>(callback)` adapter per declared event (callback arg types follow `apiStyle`).
|
||||
|
||||
Flag plumbing:
|
||||
|
||||
1. `metadata.json#interface == "universal"` → `mkLogosModule.nix` adds `-DLOGOS_API_STYLE=std` to `extraCmakeFlags`. Anything else (`"legacy"`, `"provider"`, absent) leaves the default `qt`.
|
||||
2. `LogosModule.cmake` reads `${LOGOS_API_STYLE}` (default `qt`) and forwards `--api-style=${LOGOS_API_STYLE}` to the `logos-cpp-generator --general-only` invocation that writes the umbrella. Each module's Nix build emits **two** header derivations (`<name>.headers-qt` and `<name>.headers-std`) via `buildHeaders.nix` — one `logos-cpp-generator --api-style=…` run per style, at the dep's build time. A consumer's `buildPlugin.nix` picks `dep.headers-${apiStyle}` and copies its `include/` straight into the build sandbox; no codegen runs at consume time. Nix's laziness means only the variant a downstream actually depends on is realised.
|
||||
3. `legacy/main.cpp` parses `--api-style` once and threads the resulting `ApiStyle` through `generateFromPlugin`, `writeUmbrellaHeader{,FromDeps}`. No `_api_std.{h,cpp}` files are ever emitted; each module gets a single `<name>_api.h` + `<name>_api.cpp` pair regardless of style.
|
||||
1. `metadata.json#interface == "universal"` (or `"cdylib"`) → `mkLogosModule.nix` adds `-DLOGOS_API_STYLE=lp` to `extraCmakeFlags`. Anything else (`"legacy"`, `"provider"`, absent) leaves the default `qt`.
|
||||
2. `LogosModule.cmake` reads `${LOGOS_API_STYLE}` (default `qt`) and forwards `--api-style=${LOGOS_API_STYLE}` to the `logos-cpp-generator --general-only` invocation that writes the umbrella. Each module's Nix build emits **two** header derivations (`<name>.headers-qt` and `<name>.headers-lp`) via `buildHeaders.nix` — one `logos-cpp-generator --api-style=…` run per style, at the dep's build time. A consumer's `buildPlugin.nix` picks `dep.headers-${apiStyle}` and copies its `include/` straight into the build sandbox; no codegen runs at consume time. Nix's laziness means only the variant a downstream actually depends on is realised.
|
||||
3. `legacy/main.cpp` parses `--api-style` once (rejecting the retired `std`) and threads the resulting `ApiStyle` through `generateFromPlugin`, `writeUmbrellaHeader{,FromDeps}`. No per-style filenames are ever emitted; each module gets a single `<name>_api.h` + `<name>_api.cpp` pair regardless of style.
|
||||
|
||||
### Provider Generation (logos-qt-generator)
|
||||
|
||||
@@ -191,7 +193,7 @@ The `--events-from <path>` flag points the legacy `<plugin>.dylib --module-only`
|
||||
|
||||
```bash
|
||||
logos-cpp-generator /path/to/plugin.dylib \
|
||||
--module-only --api-style std \
|
||||
--module-only --api-style lp \
|
||||
--events-from /path/to/dep/share/logos/my_module.lidl \
|
||||
--output-dir ./generated
|
||||
```
|
||||
|
||||
@@ -321,7 +321,9 @@ Generated from LIDL (not from `--from-header`). Each module gets **one** `<Modul
|
||||
| `--api-style` | Wrapper signatures |
|
||||
|---|---|
|
||||
| `qt` (default) | QString / QStringList / QVariantList / QVariantMap / int / LogosResult |
|
||||
| `std` | std::string / std::vector<std::string> / LogosMap / LogosList / int64_t / StdLogosResult |
|
||||
| `lp` | std::string / std::vector<std::string> / LogosMap / LogosList / int64_t / StdLogosResult, over the Qt-free logos-protocol C ABI |
|
||||
|
||||
(`std` — the same signatures over a `QVariant` / `LogosAPIClient` body — was retired; `--api-style=std` is now an error.)
|
||||
|
||||
Both styles provide:
|
||||
|
||||
|
||||
@@ -101,16 +101,14 @@ QString toQVariantConversion(const QString& type, const QString& argExpr)
|
||||
return argExpr + ".toString()";
|
||||
}
|
||||
|
||||
// ─── Std (pure-C++) type-mapping helpers ─────────────────────────────────
|
||||
// ─── std (pure-C++) type-mapping table ───────────────────────────────────
|
||||
//
|
||||
// File-local — not exposed in generator_lib.h. The single public entry
|
||||
// point is makeHeader / makeSource taking an `ApiStyle` argument; when
|
||||
// `apiStyle == Std`, those functions internally route through these
|
||||
// helpers to pick the std type-mapping table. There's only one wrapper
|
||||
// class per module — `<Module>` — whose signatures depend on apiStyle.
|
||||
// The std-typed body still goes through the QVariant wire; the Qt↔std
|
||||
// conversion is inlined at the call site, contained to the generated
|
||||
// .cpp so callers never include Qt headers.
|
||||
// File-local — not exposed in generator_lib.h. This is the type table the
|
||||
// Qt-free surface (ApiStyle::Lp) exposes: the wrapper's signatures are std
|
||||
// types so a universal / cdylib module's own translation units never name a
|
||||
// Qt type. Reached through paramTypeFor / returnTypeFor / byRefFor below (the
|
||||
// non-Qt arm of each) and directly from the Lp backend's lpPushExpr /
|
||||
// lpFromJsonExpr.
|
||||
|
||||
static QString mapParamTypeStd(const QString& qtType)
|
||||
{
|
||||
@@ -146,81 +144,6 @@ static QString mapReturnTypeStd(const QString& qtType)
|
||||
return base;
|
||||
}
|
||||
|
||||
// Returns a C++ expression that lifts a std-typed parameter into a
|
||||
// QVariant-side temporary suitable for invokeRemoteMethod. The
|
||||
// temporaries are rvalues consumed inline at the call site.
|
||||
static QString stdParamToQVariant(const QString& qtType, const QString& argName)
|
||||
{
|
||||
const QString base = mapParamType(qtType);
|
||||
if (base == "QString")
|
||||
return "QString::fromStdString(" + argName + ")";
|
||||
if (base == "QStringList")
|
||||
return "[&]{ QStringList _q; _q.reserve(static_cast<int>(" + argName +
|
||||
".size())); for (const auto& _s : " + argName +
|
||||
") _q.append(QString::fromStdString(_s)); return _q; }()";
|
||||
if (base == "QByteArray")
|
||||
return "QByteArray(reinterpret_cast<const char*>(" + argName +
|
||||
".data()), static_cast<int>(" + argName + ".size()))";
|
||||
if (base == "QJsonArray")
|
||||
return "QJsonDocument::fromJson(QByteArray::fromStdString(" + argName +
|
||||
".dump())).array()";
|
||||
if (base == "QVariantList" || base == "QVariantMap" || base == "QVariant")
|
||||
return "QJsonDocument::fromJson(QByteArray::fromStdString(" + argName +
|
||||
".dump())).toVariant()" + (base == "QVariantList" ? ".toList()"
|
||||
: base == "QVariantMap" ? ".toMap()"
|
||||
: "");
|
||||
if (base == "int")
|
||||
// The std signature exposes `int64_t`; widen the QVariant
|
||||
// payload to qlonglong so the wire carries the full 64-bit
|
||||
// value instead of silently truncating to 32 bits on the way
|
||||
// through `static_cast<int>`. (Reported by Copilot review on
|
||||
// PR #61 — the std-typed surface and the wire payload were
|
||||
// disagreeing for any value outside the int32 range.)
|
||||
return "static_cast<qlonglong>(" + argName + ")";
|
||||
return argName;
|
||||
}
|
||||
|
||||
// Returns a C++ expression that converts a QVariant return value into
|
||||
// the std-typed return type. `varExpr` is the source QVariant.
|
||||
static QString qVariantToStdReturn(const QString& qtType, const QString& varExpr)
|
||||
{
|
||||
const QString base = mapReturnType(qtType);
|
||||
if (base == "void")
|
||||
return QString();
|
||||
if (base == "bool")
|
||||
return varExpr + ".toBool()";
|
||||
if (base == "int")
|
||||
return "static_cast<int64_t>(" + varExpr + ".toInt())";
|
||||
if (base == "qlonglong")
|
||||
return varExpr + ".toLongLong()";
|
||||
if (base == "qulonglong")
|
||||
return varExpr + ".toULongLong()";
|
||||
if (base == "double" || base == "float")
|
||||
return varExpr + ".toDouble()";
|
||||
if (base == "QString")
|
||||
return varExpr + ".toString().toStdString()";
|
||||
if (base == "QStringList")
|
||||
return "[&]{ std::vector<std::string> _v; const QStringList _q = " +
|
||||
varExpr + ".toStringList(); _v.reserve(static_cast<size_t>(_q.size())); "
|
||||
"for (const QString& _s : _q) _v.push_back(_s.toStdString()); return _v; }()";
|
||||
if (base == "QByteArray")
|
||||
return "[&]{ const QByteArray _b = " + varExpr +
|
||||
".toByteArray(); return std::vector<uint8_t>(_b.begin(), _b.end()); }()";
|
||||
if (base == "QJsonArray" || base == "QVariantList")
|
||||
return "LogosList::parse(QJsonDocument(QJsonArray::fromVariantList(" +
|
||||
varExpr + ".toList())).toJson(QJsonDocument::Compact).toStdString())";
|
||||
if (base == "QVariantMap" || base == "QVariant")
|
||||
return "LogosMap::parse(QJsonDocument(QJsonObject::fromVariantMap(" +
|
||||
varExpr + ".toMap())).toJson(QJsonDocument::Compact).toStdString())";
|
||||
if (base == "LogosResult")
|
||||
return "[&]{ StdLogosResult _r; const LogosResult _q = " + varExpr +
|
||||
".value<LogosResult>(); _r.success = _q.success; "
|
||||
"if (_q.value.isValid()) _r.value = LogosMap::parse("
|
||||
"QJsonDocument(QJsonObject::fromVariantMap(_q.value.toMap())).toJson(QJsonDocument::Compact).toStdString()); "
|
||||
"_r.error = _q.error.toString().toStdString(); return _r; }()";
|
||||
return varExpr + ".toString().toStdString()";
|
||||
}
|
||||
|
||||
// ─── Records ─────────────────────────────────────────────────────────────
|
||||
//
|
||||
// A contract's `type Status { port: uint }` is a REAL C++ struct on the
|
||||
@@ -343,12 +266,9 @@ static QString recordToWireExpr(const RecordSet& rs, const QString& t, ApiStyle
|
||||
if (shape == RecordShape::List)
|
||||
return "[&]{ QVariantList __acc; for (const auto& __e : " + expr
|
||||
+ ") __acc.append(" + conv + "(__e)); return __acc; }()";
|
||||
// Qt keys are QString, std keys std::string.
|
||||
if (style == ApiStyle::Qt)
|
||||
return "[&]{ QVariantMap __acc; for (auto __i = " + expr + ".cbegin(); __i != " + expr
|
||||
+ ".cend(); ++__i) __acc.insert(__i.key(), " + conv + "(__i.value())); return __acc; }()";
|
||||
return "[&]{ QVariantMap __acc; for (const auto& __kv : " + expr
|
||||
+ ") __acc.insert(QString::fromStdString(__kv.first), " + conv + "(__kv.second)); return __acc; }()";
|
||||
// Map, Qt surface: the source container is a QMap, so keys are QString.
|
||||
return "[&]{ QVariantMap __acc; for (auto __i = " + expr + ".cbegin(); __i != " + expr
|
||||
+ ".cend(); ++__i) __acc.insert(__i.key(), " + conv + "(__i.value())); return __acc; }()";
|
||||
}
|
||||
|
||||
static QString recordFromWireExpr(const RecordSet& rs, const QString& t, ApiStyle style,
|
||||
@@ -372,13 +292,10 @@ static QString recordFromWireExpr(const RecordSet& rs, const QString& t, ApiStyl
|
||||
if (shape == RecordShape::List)
|
||||
return "[&]{ " + cpp + " __acc; for (const QVariant& __e : (" + wire
|
||||
+ ").toList()) __acc.push_back(" + conv + "(__e)); return __acc; }()";
|
||||
if (style == ApiStyle::Qt)
|
||||
return "[&]{ " + cpp + " __acc; const QVariantMap __src = (" + wire
|
||||
+ ").toMap(); for (auto __i = __src.cbegin(); __i != __src.cend(); ++__i) "
|
||||
"__acc.insert(__i.key(), " + conv + "(__i.value())); return __acc; }()";
|
||||
// Map, Qt surface: the destination container is a QMap, so keys are QString.
|
||||
return "[&]{ " + cpp + " __acc; const QVariantMap __src = (" + wire
|
||||
+ ").toMap(); for (auto __i = __src.cbegin(); __i != __src.cend(); ++__i) "
|
||||
"__acc[__i.key().toStdString()] = " + conv + "(__i.value()); return __acc; }()";
|
||||
"__acc.insert(__i.key(), " + conv + "(__i.value())); return __acc; }()";
|
||||
}
|
||||
|
||||
// Param-type predicate: passed by const-ref?
|
||||
@@ -433,13 +350,12 @@ static bool byRefFor(const QString& qtType, const QString& cppType, ApiStyle sty
|
||||
return (style == ApiStyle::Qt) ? isQtRefType(cppType) : isStdRefType(cppType);
|
||||
}
|
||||
|
||||
// Typed value -> wire value (QVariant for Qt/Std, nlohmann::json for Lp).
|
||||
// Typed value -> wire value (QVariant for Qt, nlohmann::json for Lp).
|
||||
static QString toWireFor(const QString& qtType, ApiStyle style, const RecordSet& rs, const QString& expr)
|
||||
{
|
||||
const QString rec = recordToWireExpr(rs, qtType, style, expr);
|
||||
if (!rec.isEmpty()) return rec;
|
||||
if (style == ApiStyle::Lp) return lpPushExpr(qtType, expr);
|
||||
if (style == ApiStyle::Std) return stdParamToQVariant(qtType, expr);
|
||||
return expr; // Qt: the wrapper's own surface already IS the wire type
|
||||
}
|
||||
|
||||
@@ -450,7 +366,6 @@ static QString fromWireFor(const QString& qtType, ApiStyle style, const RecordSe
|
||||
const QString rec = recordFromWireExpr(rs, qtType, style, wire, qual);
|
||||
if (!rec.isEmpty()) return rec;
|
||||
if (style == ApiStyle::Lp) return lpFromJsonExpr(qtType, wire);
|
||||
if (style == ApiStyle::Std) return qVariantToStdReturn(qtType, wire);
|
||||
return toQVariantConversion(mapParamType(qtType), wire);
|
||||
}
|
||||
|
||||
@@ -505,7 +420,7 @@ static void emitRecordConversions(QTextStream& s, const RecordSet& rs, ApiStyle
|
||||
const QString v = toWireFor(f.type, style, rs, "v." + f.name);
|
||||
const bool isRec = recordShape(rs, f.type, nullptr) != RecordShape::None;
|
||||
s << " __m.insert(QStringLiteral(\"" << f.name << "\"), "
|
||||
<< (isRec || style == ApiStyle::Std ? v : "QVariant::fromValue(" + v + ")")
|
||||
<< (isRec ? v : "QVariant::fromValue(" + v + ")")
|
||||
<< ");\n";
|
||||
}
|
||||
s << " return __m;\n";
|
||||
@@ -545,43 +460,19 @@ QString makeHeader(const QString& moduleName, const QString& className, const QJ
|
||||
QString h;
|
||||
QTextStream s(&h);
|
||||
s << "#pragma once\n";
|
||||
if (apiStyle == ApiStyle::Std) {
|
||||
// Pure-C++ surface. Qt is still pulled in transitively by
|
||||
// logos_api.h (LogosAPI is a QObject), but the wrapper's
|
||||
// signatures are entirely std types so callers never have to
|
||||
// type a Qt name themselves.
|
||||
s << "#include <cstdint>\n";
|
||||
s << "#include <string>\n";
|
||||
s << "#include <vector>\n";
|
||||
s << "#include <functional>\n";
|
||||
s << "#include \"logos_types.h\"\n";
|
||||
s << "#include \"logos_json.h\"\n";
|
||||
s << "#include \"logos_result.h\"\n";
|
||||
s << "#include \"logos_api.h\"\n";
|
||||
s << "#include \"logos_api_client.h\"\n";
|
||||
s << "#include \"logos_call_error.h\"\n";
|
||||
// Needed for the m_eventReplica member when the module declares
|
||||
// any events. Cheap to include unconditionally — keeps the
|
||||
// header symmetric with the Qt-style branch.
|
||||
if (!events.isEmpty()) s << "#include \"logos_object.h\"\n";
|
||||
// Record maps are std::map on the std surface.
|
||||
if (!rs.isEmpty()) s << "#include <map>\n";
|
||||
s << "\n";
|
||||
} else {
|
||||
s << "#include <QString>\n";
|
||||
s << "#include <QVariant>\n";
|
||||
s << "#include <QStringList>\n";
|
||||
s << "#include <QJsonArray>\n";
|
||||
s << "#include <QVariantList>\n";
|
||||
s << "#include <QVariantMap>\n";
|
||||
s << "#include <functional>\n";
|
||||
s << "#include <utility>\n";
|
||||
s << "#include \"logos_types.h\"\n";
|
||||
s << "#include \"logos_api.h\"\n";
|
||||
s << "#include \"logos_api_client.h\"\n";
|
||||
s << "#include \"logos_call_error.h\"\n";
|
||||
s << "#include \"logos_object.h\"\n\n";
|
||||
}
|
||||
s << "#include <QString>\n";
|
||||
s << "#include <QVariant>\n";
|
||||
s << "#include <QStringList>\n";
|
||||
s << "#include <QJsonArray>\n";
|
||||
s << "#include <QVariantList>\n";
|
||||
s << "#include <QVariantMap>\n";
|
||||
s << "#include <functional>\n";
|
||||
s << "#include <utility>\n";
|
||||
s << "#include \"logos_types.h\"\n";
|
||||
s << "#include \"logos_api.h\"\n";
|
||||
s << "#include \"logos_api_client.h\"\n";
|
||||
s << "#include \"logos_call_error.h\"\n";
|
||||
s << "#include \"logos_object.h\"\n\n";
|
||||
s << "class " << className << " {\n";
|
||||
s << "public:\n";
|
||||
emitRecordStructs(s, rs, apiStyle);
|
||||
@@ -591,31 +482,28 @@ QString makeHeader(const QString& moduleName, const QString& className, const QJ
|
||||
} else {
|
||||
s << " explicit " << className << "(LogosAPI* api);\n\n";
|
||||
}
|
||||
if (apiStyle == ApiStyle::Qt) {
|
||||
// Event subscription / trigger surface — Qt-typed.
|
||||
s << " using RawEventCallback = std::function<void(const QString&, const QVariantList&)>;\n";
|
||||
s << " using EventCallback = std::function<void(const QVariantList&)>;\n\n";
|
||||
s << " bool on(const QString& eventName, RawEventCallback callback);\n";
|
||||
s << " bool on(const QString& eventName, EventCallback callback);\n";
|
||||
s << " void setEventSource(LogosObject* source);\n";
|
||||
s << " LogosObject* eventSource() const;\n";
|
||||
s << " void trigger(const QString& eventName);\n";
|
||||
s << " void trigger(const QString& eventName, const QVariantList& data);\n";
|
||||
s << " template<typename... Args>\n";
|
||||
s << " void trigger(const QString& eventName, Args&&... args) {\n";
|
||||
s << " trigger(eventName, packVariantList(std::forward<Args>(args)...));\n";
|
||||
s << " }\n";
|
||||
s << " void trigger(const QString& eventName, LogosObject* source, const QVariantList& data);\n";
|
||||
s << " template<typename... Args>\n";
|
||||
s << " void trigger(const QString& eventName, LogosObject* source, Args&&... args) {\n";
|
||||
s << " trigger(eventName, source, packVariantList(std::forward<Args>(args)...));\n";
|
||||
s << " }\n\n";
|
||||
}
|
||||
// Event subscription / trigger surface — Qt-typed.
|
||||
s << " using RawEventCallback = std::function<void(const QString&, const QVariantList&)>;\n";
|
||||
s << " using EventCallback = std::function<void(const QVariantList&)>;\n\n";
|
||||
s << " bool on(const QString& eventName, RawEventCallback callback);\n";
|
||||
s << " bool on(const QString& eventName, EventCallback callback);\n";
|
||||
s << " void setEventSource(LogosObject* source);\n";
|
||||
s << " LogosObject* eventSource() const;\n";
|
||||
s << " void trigger(const QString& eventName);\n";
|
||||
s << " void trigger(const QString& eventName, const QVariantList& data);\n";
|
||||
s << " template<typename... Args>\n";
|
||||
s << " void trigger(const QString& eventName, Args&&... args) {\n";
|
||||
s << " trigger(eventName, packVariantList(std::forward<Args>(args)...));\n";
|
||||
s << " }\n";
|
||||
s << " void trigger(const QString& eventName, LogosObject* source, const QVariantList& data);\n";
|
||||
s << " template<typename... Args>\n";
|
||||
s << " void trigger(const QString& eventName, LogosObject* source, Args&&... args) {\n";
|
||||
s << " trigger(eventName, source, packVariantList(std::forward<Args>(args)...));\n";
|
||||
s << " }\n\n";
|
||||
// Typed event subscribers — generated from the `.lidl` sidecar shipped
|
||||
// with the dep's pre-built headers (via --events-from). One typed
|
||||
// adapter per declared event, callback-arg types follow apiStyle.
|
||||
// The generic `on(name, cb)` channel above stays available; for
|
||||
// std-style consumers it's not exposed but the typed accessors are.
|
||||
// The generic `on(name, cb)` channel above stays available alongside them.
|
||||
for (const QJsonValue& ev : events) {
|
||||
const QJsonObject eo = ev.toObject();
|
||||
const QString evName = eo.value("name").toString();
|
||||
@@ -690,34 +578,23 @@ QString makeHeader(const QString& moduleName, const QString& className, const QJ
|
||||
s << asyncCallbackType << " callback, Timeout timeout = Timeout());\n";
|
||||
}
|
||||
s << "\nprivate:\n";
|
||||
// ensureReplica() is needed whenever the wrapper subscribes to
|
||||
// events — in Qt mode that's always (the generic `on(...)` channel
|
||||
// is exposed); in std mode it's gated on at least one declared
|
||||
// event in the LIDL sidecar.
|
||||
if (apiStyle == ApiStyle::Qt || !events.isEmpty()) {
|
||||
s << " LogosObject* ensureReplica();\n";
|
||||
}
|
||||
if (apiStyle == ApiStyle::Qt) {
|
||||
s << " template<typename... Args>\n";
|
||||
s << " static QVariantList packVariantList(Args&&... args) {\n";
|
||||
s << " QVariantList list;\n";
|
||||
s << " list.reserve(sizeof...(Args));\n";
|
||||
s << " using Expander = int[];\n";
|
||||
s << " (void)Expander{0, (list.append(QVariant::fromValue(std::forward<Args>(args))), 0)...};\n";
|
||||
s << " return list;\n";
|
||||
s << " }\n";
|
||||
}
|
||||
// ensureReplica() is needed whenever the wrapper subscribes to events,
|
||||
// which on the Qt surface is always: the generic `on(...)` channel is
|
||||
// exposed even when the contract declares no typed events.
|
||||
s << " LogosObject* ensureReplica();\n";
|
||||
s << " template<typename... Args>\n";
|
||||
s << " static QVariantList packVariantList(Args&&... args) {\n";
|
||||
s << " QVariantList list;\n";
|
||||
s << " list.reserve(sizeof...(Args));\n";
|
||||
s << " using Expander = int[];\n";
|
||||
s << " (void)Expander{0, (list.append(QVariant::fromValue(std::forward<Args>(args))), 0)...};\n";
|
||||
s << " return list;\n";
|
||||
s << " }\n";
|
||||
s << " LogosAPI* m_api;\n";
|
||||
s << " LogosAPIClient* m_client;\n";
|
||||
s << " QString m_moduleName;\n";
|
||||
if (apiStyle == ApiStyle::Qt) {
|
||||
s << " LogosObject* m_eventReplica = nullptr;\n";
|
||||
s << " LogosObject* m_eventSource = nullptr;\n";
|
||||
} else if (!events.isEmpty()) {
|
||||
// std-style consumer: only the receive-side replica is needed
|
||||
// (no `trigger(...)` API on the std wrapper, so no eventSource).
|
||||
s << " LogosObject* m_eventReplica = nullptr;\n";
|
||||
}
|
||||
s << " LogosObject* m_eventReplica = nullptr;\n";
|
||||
s << " LogosObject* m_eventSource = nullptr;\n";
|
||||
s << "};\n";
|
||||
return h;
|
||||
}
|
||||
@@ -731,24 +608,8 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
|
||||
QTextStream s(&c);
|
||||
s << "#include \"" << headerBaseName << "\"\n\n";
|
||||
s << "#include <QDebug>\n";
|
||||
if (apiStyle == ApiStyle::Std) {
|
||||
// Conversion bridges between std types and QVariant — confined
|
||||
// to this .cpp so the caller's translation unit doesn't need
|
||||
// any Qt headers itself.
|
||||
s << "#include <QJsonDocument>\n";
|
||||
s << "#include <QJsonArray>\n";
|
||||
s << "#include <QJsonObject>\n";
|
||||
s << "#include <QByteArray>\n";
|
||||
s << "#include <QStringList>\n";
|
||||
s << "#include <QVariantList>\n";
|
||||
s << "#include <QVariantMap>\n";
|
||||
// logos_object.h is the type of LogosObject* used by typed event
|
||||
// accessors when the module declares events. Always include in
|
||||
// std mode when events are present.
|
||||
if (!events.isEmpty()) s << "#include \"logos_object.h\"\n";
|
||||
}
|
||||
if (apiStyle == ApiStyle::Qt && !rs.isEmpty()) {
|
||||
// Record conversions build QVariantMaps regardless of api style.
|
||||
if (!rs.isEmpty()) {
|
||||
// Record conversions build QVariantMaps.
|
||||
s << "#include <QVariantMap>\n";
|
||||
}
|
||||
s << "\n";
|
||||
@@ -766,79 +627,61 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
|
||||
s << className << "::" << className << "(LogosAPI* api) : m_api(api), m_client(api->getClient(\"" << moduleName << "\")), m_moduleName(QStringLiteral(\"" << moduleName << "\")) {}\n\n";
|
||||
}
|
||||
|
||||
// ensureReplica() — generated for std mode too when events are
|
||||
// declared. The body is identical to the Qt version; pulled up
|
||||
// here so both branches share it.
|
||||
if (apiStyle == ApiStyle::Std && !events.isEmpty()) {
|
||||
s << "LogosObject* " << className << "::ensureReplica() {\n";
|
||||
s << " if (!m_eventReplica) {\n";
|
||||
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
|
||||
s << " if (!replica) {\n";
|
||||
s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n";
|
||||
s << " return nullptr;\n";
|
||||
s << " }\n";
|
||||
s << " m_eventReplica = replica;\n";
|
||||
s << " }\n";
|
||||
s << " return m_eventReplica;\n";
|
||||
s << "}\n\n";
|
||||
}
|
||||
if (apiStyle == ApiStyle::Qt) {
|
||||
s << "LogosObject* " << className << "::ensureReplica() {\n";
|
||||
s << " if (!m_eventReplica) {\n";
|
||||
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
|
||||
s << " if (!replica) {\n";
|
||||
s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n";
|
||||
s << " return nullptr;\n";
|
||||
s << " }\n";
|
||||
s << " m_eventReplica = replica;\n";
|
||||
s << " }\n";
|
||||
s << " return m_eventReplica;\n";
|
||||
s << "}\n\n";
|
||||
s << "bool " << className << "::on(const QString& eventName, RawEventCallback callback) {\n";
|
||||
s << " if (!callback) {\n";
|
||||
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
|
||||
s << " return false;\n";
|
||||
s << " }\n";
|
||||
s << " LogosObject* origin = ensureReplica();\n";
|
||||
s << " if (!origin) {\n";
|
||||
s << " return false;\n";
|
||||
s << " }\n";
|
||||
s << " m_client->onEvent(origin, eventName, callback);\n";
|
||||
s << " return true;\n";
|
||||
s << "}\n\n";
|
||||
s << "bool " << className << "::on(const QString& eventName, EventCallback callback) {\n";
|
||||
s << " if (!callback) {\n";
|
||||
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
|
||||
s << " return false;\n";
|
||||
s << " }\n";
|
||||
s << " return on(eventName, [callback](const QString&, const QVariantList& data) {\n";
|
||||
s << " callback(data);\n";
|
||||
s << " });\n";
|
||||
s << "}\n\n";
|
||||
s << "void " << className << "::setEventSource(LogosObject* source) {\n";
|
||||
s << " m_eventSource = source;\n";
|
||||
s << "}\n\n";
|
||||
s << "LogosObject* " << className << "::eventSource() const {\n";
|
||||
s << " return m_eventSource;\n";
|
||||
s << "}\n\n";
|
||||
s << "void " << className << "::trigger(const QString& eventName) {\n";
|
||||
s << " trigger(eventName, QVariantList{});\n";
|
||||
s << "}\n\n";
|
||||
s << "void " << className << "::trigger(const QString& eventName, const QVariantList& data) {\n";
|
||||
s << " if (!m_eventSource) {\n";
|
||||
s << " qWarning() << \"" << className << ": no event source set for trigger\" << eventName;\n";
|
||||
s << " return;\n";
|
||||
s << " }\n";
|
||||
s << " m_client->onEventResponse(m_eventSource, eventName, data);\n";
|
||||
s << "}\n\n";
|
||||
s << "void " << className << "::trigger(const QString& eventName, LogosObject* source, const QVariantList& data) {\n";
|
||||
s << " if (!source) {\n";
|
||||
s << " qWarning() << \"" << className << ": cannot trigger\" << eventName << \"with null source\";\n";
|
||||
s << " return;\n";
|
||||
s << " }\n";
|
||||
s << " m_client->onEventResponse(source, eventName, data);\n";
|
||||
s << "}\n\n";
|
||||
}
|
||||
s << "LogosObject* " << className << "::ensureReplica() {\n";
|
||||
s << " if (!m_eventReplica) {\n";
|
||||
s << " LogosObject* replica = m_client->requestObject(m_moduleName);\n";
|
||||
s << " if (!replica) {\n";
|
||||
s << " qWarning() << \"" << className << ": failed to acquire remote object for events on\" << m_moduleName;\n";
|
||||
s << " return nullptr;\n";
|
||||
s << " }\n";
|
||||
s << " m_eventReplica = replica;\n";
|
||||
s << " }\n";
|
||||
s << " return m_eventReplica;\n";
|
||||
s << "}\n\n";
|
||||
s << "bool " << className << "::on(const QString& eventName, RawEventCallback callback) {\n";
|
||||
s << " if (!callback) {\n";
|
||||
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
|
||||
s << " return false;\n";
|
||||
s << " }\n";
|
||||
s << " LogosObject* origin = ensureReplica();\n";
|
||||
s << " if (!origin) {\n";
|
||||
s << " return false;\n";
|
||||
s << " }\n";
|
||||
s << " m_client->onEvent(origin, eventName, callback);\n";
|
||||
s << " return true;\n";
|
||||
s << "}\n\n";
|
||||
s << "bool " << className << "::on(const QString& eventName, EventCallback callback) {\n";
|
||||
s << " if (!callback) {\n";
|
||||
s << " qWarning() << \"" << className << ": ignoring empty event callback for\" << eventName;\n";
|
||||
s << " return false;\n";
|
||||
s << " }\n";
|
||||
s << " return on(eventName, [callback](const QString&, const QVariantList& data) {\n";
|
||||
s << " callback(data);\n";
|
||||
s << " });\n";
|
||||
s << "}\n\n";
|
||||
s << "void " << className << "::setEventSource(LogosObject* source) {\n";
|
||||
s << " m_eventSource = source;\n";
|
||||
s << "}\n\n";
|
||||
s << "LogosObject* " << className << "::eventSource() const {\n";
|
||||
s << " return m_eventSource;\n";
|
||||
s << "}\n\n";
|
||||
s << "void " << className << "::trigger(const QString& eventName) {\n";
|
||||
s << " trigger(eventName, QVariantList{});\n";
|
||||
s << "}\n\n";
|
||||
s << "void " << className << "::trigger(const QString& eventName, const QVariantList& data) {\n";
|
||||
s << " if (!m_eventSource) {\n";
|
||||
s << " qWarning() << \"" << className << ": no event source set for trigger\" << eventName;\n";
|
||||
s << " return;\n";
|
||||
s << " }\n";
|
||||
s << " m_client->onEventResponse(m_eventSource, eventName, data);\n";
|
||||
s << "}\n\n";
|
||||
s << "void " << className << "::trigger(const QString& eventName, LogosObject* source, const QVariantList& data) {\n";
|
||||
s << " if (!source) {\n";
|
||||
s << " qWarning() << \"" << className << ": cannot trigger\" << eventName << \"with null source\";\n";
|
||||
s << " return;\n";
|
||||
s << " }\n";
|
||||
s << " m_client->onEventResponse(source, eventName, data);\n";
|
||||
s << "}\n\n";
|
||||
|
||||
// Typed event adapters — one per declared event. The callback type
|
||||
// uses the apiStyle's type surface; the body unmarshals from the
|
||||
@@ -911,9 +754,8 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
|
||||
const QString retQual = returnTypeFor(qtRet, apiStyle, rs, className + "::");
|
||||
QJsonArray params = o.value("parameters").toArray();
|
||||
|
||||
// Helper closures kept inline so the two branches don't get
|
||||
// pulled apart visually — the per-arg / per-return shape is
|
||||
// the only thing that varies between Qt and Std modes.
|
||||
// Helper closures kept inline so the signature and the call that
|
||||
// consumes it stay next to each other.
|
||||
auto emitParam = [&](const QJsonObject& p, bool& byRefOut) {
|
||||
QString qtPt = p.value("type").toString();
|
||||
QString pt = paramTypeFor(qtPt, apiStyle, rs);
|
||||
@@ -970,8 +812,6 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
|
||||
// nothing
|
||||
} else if (retIsRecord) {
|
||||
s << " return " << fromWireFor(qtRet, apiStyle, rs, "_result") << ";\n";
|
||||
} else if (apiStyle == ApiStyle::Std) {
|
||||
s << " return " << qVariantToStdReturn(qtRet, "_result") << ";\n";
|
||||
} else if (ret == "bool") {
|
||||
s << " return _result.toBool();\n";
|
||||
} else if (ret == "qlonglong") {
|
||||
@@ -1036,22 +876,6 @@ QString makeSource(const QString& moduleName, const QString& className, const QS
|
||||
// A record decodes field by field; an invalid QVariant yields a
|
||||
// default-constructed struct, matching the scalar paths.
|
||||
s << " callback(" << fromWireFor(qtRet, apiStyle, rs, "v", className + "::") << ");\n";
|
||||
} else if (apiStyle == ApiStyle::Std) {
|
||||
// Default-construct on dispatch failure, matching the
|
||||
// existing Qt code path which falls back to a zero / empty
|
||||
// value when the QVariant is invalid.
|
||||
QString defaultVal;
|
||||
if (ret == "bool") defaultVal = "false";
|
||||
else if (ret == "int64_t") defaultVal = "0";
|
||||
else if (ret == "double") defaultVal = "0.0";
|
||||
else if (ret == "std::string") defaultVal = "std::string()";
|
||||
else if (ret.startsWith("std::vector")) defaultVal = ret + "()";
|
||||
else if (ret == "LogosMap") defaultVal = "LogosMap::object()";
|
||||
else if (ret == "LogosList") defaultVal = "LogosList::array()";
|
||||
else if (ret == "StdLogosResult") defaultVal = "StdLogosResult{}";
|
||||
else defaultVal = ret + "{}";
|
||||
s << " if (!v.isValid()) { callback(" << defaultVal << "); return; }\n";
|
||||
s << " callback(" << qVariantToStdReturn(qtRet, "v") << ");\n";
|
||||
} else {
|
||||
QString defaultVal;
|
||||
if (ret == "bool") defaultVal = "false";
|
||||
@@ -1187,12 +1011,15 @@ QVector<ParsedMethod> parseProviderHeader(const QString& headerPath, QTextStream
|
||||
|
||||
// ─── ApiStyle::Lp (Qt-free) wrapper emission ─────────────────────────────
|
||||
//
|
||||
// Same std-typed surface as ApiStyle::Std, but the generated body calls the
|
||||
// logos-protocol C ABI through logos::LpClient instead of LogosAPIClient, so
|
||||
// the wrapper's translation unit pulls in no Qt. Used for the cdylib outbound
|
||||
// path (a Qt-free module calling its dependencies / subscribing to their
|
||||
// events). The class still holds a single target; Static bakes it, Bound takes
|
||||
// it at construction (interface dependencies).
|
||||
// A std-typed surface (the mapParamTypeStd / mapReturnTypeStd table above)
|
||||
// whose generated body calls the logos-protocol C ABI through logos::LpClient
|
||||
// instead of LogosAPIClient, so the wrapper's translation unit pulls in no Qt.
|
||||
// This is the only remaining std-typed flavour; the retired ApiStyle::Std
|
||||
// exposed the same signatures over a QVariant + LogosAPIClient body.
|
||||
//
|
||||
// Used for the cdylib outbound path (a Qt-free module calling its dependencies
|
||||
// / subscribing to their events). The class still holds a single target;
|
||||
// Static bakes it, Bound takes it at construction (interface dependencies).
|
||||
|
||||
// std value -> nlohmann::json push expression. nlohmann handles
|
||||
// string/int64/double/bool/vector<string>/json (LogosMap/LogosList) directly;
|
||||
|
||||
@@ -17,18 +17,21 @@ struct ParsedMethod {
|
||||
// Which type surface to expose on the generated per-module wrapper.
|
||||
// Each module's build picks ONE — there's no composite output. Default
|
||||
// is Qt for backward compatibility; `interface: "universal"` modules
|
||||
// flip to Std via the -DLOGOS_API_STYLE=std CMake flag the module
|
||||
// builder threads through.
|
||||
// flip to Lp via the -DLOGOS_API_STYLE=lp CMake flag the module builder
|
||||
// threads through.
|
||||
// Qt — legacy Qt-typed surface (QString/QVariant…), body via LogosAPIClient.
|
||||
// Std — std-typed surface, but the body still bridges through QVariant +
|
||||
// LogosAPIClient (so the wrapper .cpp links qt-sdk).
|
||||
// Lp — std-typed surface AND a Qt-free body: the wrapper calls the
|
||||
// logos-protocol C ABI (lp_*) directly via logos::LpClient, so the
|
||||
// module's translation units never include Qt or link qt-sdk. This is
|
||||
// the path that lets a cdylib module do outbound typed calls/event
|
||||
// subscriptions while staying Qt-free (Qt confined to the QRO transport
|
||||
// inside logos-protocol + the generated plugin glue).
|
||||
enum class ApiStyle { Qt, Std, Lp };
|
||||
//
|
||||
// A third flavour, Std, used to sit between the two: the std-typed surface
|
||||
// with a body that still bridged through QVariant + LogosAPIClient. Nothing
|
||||
// selected it any more (universal modules go straight to Lp), so it was
|
||||
// retired; `--api-style=std` is now a hard error rather than a silent alias.
|
||||
enum class ApiStyle { Qt, Lp };
|
||||
|
||||
// Whether the generated wrapper targets ONE fixed module (the historical
|
||||
// behaviour) or binds to a module name chosen at runtime.
|
||||
@@ -57,14 +60,12 @@ QString toProviderArgDecode(const QString& type, const QString& argExpr,
|
||||
const QString& path);
|
||||
|
||||
// makeHeader / makeSource emit the single `<Class>` wrapper for a
|
||||
// module. When `apiStyle == Std`, parameter / return types come from
|
||||
// the std-typed mapping table (std::string / std::vector<std::string>
|
||||
// / LogosMap / LogosList / int64_t / StdLogosResult) and the .cpp
|
||||
// body wraps the QVariant wire with inline Qt↔std conversions —
|
||||
// callers never include Qt headers. When `apiStyle == Qt`, the output
|
||||
// matches the legacy Qt-typed surface (QString / QStringList /
|
||||
// QVariantList / QVariantMap / int / LogosResult). The class name is
|
||||
// always `<Module>` either way; the two styles are mutually exclusive.
|
||||
// module. When `apiStyle == Qt`, the output is the legacy Qt-typed
|
||||
// surface (QString / QStringList / QVariantList / QVariantMap / int /
|
||||
// LogosResult) with a body that calls LogosAPIClient. When
|
||||
// `apiStyle == Lp`, they delegate to makeHeaderLp / makeSourceLp below,
|
||||
// which emit the std-typed, Qt-free surface. The class name is always
|
||||
// `<Module>` either way; the two styles are mutually exclusive.
|
||||
//
|
||||
// `events` carries typed event prototypes loaded from a `.lidl`
|
||||
// sidecar via --events-from. Each entry is
|
||||
@@ -92,8 +93,9 @@ QString toProviderArgDecode(const QString& type, const QString& argExpr,
|
||||
QString makeHeader(const QString& moduleName, const QString& className, const QJsonArray& methods, ApiStyle apiStyle = ApiStyle::Qt, const QJsonArray& events = {}, BindMode bindMode = BindMode::Static, const QJsonArray& records = {});
|
||||
QString makeSource(const QString& moduleName, const QString& className, const QString& headerBaseName, const QJsonArray& methods, ApiStyle apiStyle = ApiStyle::Qt, const QJsonArray& events = {}, BindMode bindMode = BindMode::Static, const QJsonArray& records = {});
|
||||
|
||||
// Qt-free (ApiStyle::Lp) wrapper emission. Same std-typed surface as the Std
|
||||
// flavor, but the generated body calls the logos-protocol C ABI through
|
||||
// Qt-free (ApiStyle::Lp) wrapper emission. A std-typed surface
|
||||
// (std::string / std::vector<std::string> / LogosMap / LogosList / int64_t /
|
||||
// StdLogosResult) whose generated body calls the logos-protocol C ABI through
|
||||
// logos::LpClient instead of LogosAPIClient — no Qt in the wrapper's TU.
|
||||
// makeHeader/makeSource dispatch here when apiStyle == ApiStyle::Lp.
|
||||
QString makeHeaderLp(const QString& moduleName, const QString& className, const QJsonArray& methods, const QJsonArray& events = {}, BindMode bindMode = BindMode::Static, const QJsonArray& records = {});
|
||||
|
||||
@@ -389,7 +389,7 @@ static bool writeUmbrellaHeader(const QString& genDirPath, QTextStream& err)
|
||||
{
|
||||
// Generate logos_sdk.h: include every per-module wrapper header in
|
||||
// the gen dir and aggregate them into a flat `LogosModules` struct.
|
||||
// The wrappers may be Qt-typed or Std-typed depending on the
|
||||
// The wrappers may be Qt-typed or std-typed (lp) depending on the
|
||||
// --api-style picked for this build; the umbrella shape doesn't
|
||||
// change because either flavor produces the same accessor name
|
||||
// (`<dep>`) on the same class name (`<Dep>`).
|
||||
@@ -880,8 +880,9 @@ static int generateFromPlugin(const QString& pluginInputPath, const QString& out
|
||||
|
||||
// Single per-module wrapper file pair. apiStyle decides the
|
||||
// signature shape: Qt-typed for legacy / handcrafted callers
|
||||
// (default), std-typed when the consuming module's build passed
|
||||
// --api-style=std (typically because it's `interface: "universal"`).
|
||||
// (default), std-typed and Qt-free when the consuming module's build
|
||||
// passed --api-style=lp (typically because it's `interface:
|
||||
// "universal"` or `"cdylib"`).
|
||||
// Both produce the same filename and class name, so the umbrella
|
||||
// doesn't need to know which style was picked. `events` (loaded
|
||||
// from a sibling `.lidl` sidecar via --events-from) adds typed
|
||||
@@ -955,14 +956,19 @@ int legacy_main(int argc, char* argv[])
|
||||
// Parse --general-only option
|
||||
bool generalOnly = args.contains("--general-only");
|
||||
|
||||
// Parse --api-style option (qt | std). Picks which type surface
|
||||
// Parse --api-style option (qt | lp). Picks which type surface
|
||||
// the generated `<Module>` wrapper exposes. Default is qt for
|
||||
// backward compatibility — every existing module that doesn't
|
||||
// declare `interface: "universal"` in its metadata.json keeps
|
||||
// its Qt-typed LogosModules surface. Universal modules get
|
||||
// -DLOGOS_API_STYLE=std threaded through by mkLogosModule.nix /
|
||||
// LogosModule.cmake, which becomes `--api-style=std` here.
|
||||
// Both forms accepted: `--api-style std` and `--api-style=std`.
|
||||
// its Qt-typed LogosModules surface. Universal / cdylib modules get
|
||||
// -DLOGOS_API_STYLE=lp threaded through by mkLogosModule.nix /
|
||||
// LogosModule.cmake, which becomes `--api-style=lp` here.
|
||||
// Both forms accepted: `--api-style lp` and `--api-style=lp`.
|
||||
//
|
||||
// `std` was a third surface (std types over a QVariant/LogosAPIClient
|
||||
// body). It is retired, and rejected LOUDLY rather than aliased to qt:
|
||||
// a stale caller that still passes it wants std signatures, and silently
|
||||
// handing it the Qt surface would only fail later, further from the cause.
|
||||
ApiStyle apiStyle = ApiStyle::Qt;
|
||||
{
|
||||
QString apiVal;
|
||||
@@ -977,11 +983,17 @@ int legacy_main(int argc, char* argv[])
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (apiVal == "std") apiStyle = ApiStyle::Std;
|
||||
if (apiVal == "std") {
|
||||
err << "--api-style=std was retired: the Std surface (std types over a "
|
||||
<< "QVariant/LogosAPIClient body) no longer exists.\n"
|
||||
<< "Use 'lp' for the Qt-free std-typed surface, or 'qt' for the "
|
||||
<< "Qt-typed one.\n";
|
||||
return 1;
|
||||
}
|
||||
else if (apiVal == "lp") apiStyle = ApiStyle::Lp;
|
||||
else if (!apiVal.isEmpty() && apiVal != "qt") {
|
||||
err << "Unknown --api-style value: " << apiVal
|
||||
<< " (expected 'qt', 'std', or 'lp')\n";
|
||||
<< " (expected 'qt' or 'lp')\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// C++ module actually gets for its dependencies (`--dep <name>=<lidl>`).
|
||||
//
|
||||
// A contract's `type Status { ... }` used to reach every C++ consumer as an
|
||||
// untyped bag: QVariant on the Qt surface, LogosMap on the std/lp one. The
|
||||
// untyped bag: QVariant on the Qt surface, LogosMap on the lp one. The
|
||||
// caller then had to know the field names AND, for a `bstr` field, that the
|
||||
// value arrives as the canonical `{"_bytes": "..."}` envelope it must unwrap
|
||||
// itself — while Rust and the client-stub backend hand back a real struct.
|
||||
@@ -97,11 +97,11 @@ TEST(Records, QtWrapperExposesTheStruct)
|
||||
EXPECT_FALSE(h.contains("describeStatus(QVariant"));
|
||||
}
|
||||
|
||||
// The std / lp surfaces spell the same records in std types — a universal
|
||||
// The lp surface spells the same records in std types — a universal
|
||||
// (Qt-free) module never sees a Qt name.
|
||||
TEST(Records, StdAndLpWrappersUseStdFieldTypes)
|
||||
TEST(Records, LpWrapperUsesStdFieldTypes)
|
||||
{
|
||||
for (ApiStyle style : {ApiStyle::Std, ApiStyle::Lp}) {
|
||||
for (ApiStyle style : {ApiStyle::Lp}) {
|
||||
const QString h = makeHeader("info_module", "InfoModule", statusMethods(),
|
||||
style, {}, BindMode::Static, statusRecords());
|
||||
EXPECT_TRUE(h.contains(" uint64_t port{};")) << h.toStdString();
|
||||
@@ -113,7 +113,7 @@ TEST(Records, StdAndLpWrappersUseStdFieldTypes)
|
||||
EXPECT_FALSE(h.contains("LogosMap getStatus("));
|
||||
}
|
||||
|
||||
// std::map needs its header on the std/lp surfaces.
|
||||
// std::map needs its header on the lp surface.
|
||||
const QString lp = makeHeader("info_module", "InfoModule", statusMethods(),
|
||||
ApiStyle::Lp, {}, BindMode::Static, statusRecords());
|
||||
EXPECT_TRUE(lp.contains("#include <map>"));
|
||||
@@ -136,7 +136,7 @@ TEST(Records, BytesFieldsUseTheCanonicalEncoding)
|
||||
EXPECT_TRUE(qt.contains("__out.blob = __m.value(QStringLiteral(\"blob\")).toByteArray();"));
|
||||
}
|
||||
|
||||
// The conversions are file-local statics in the .cpp: a std/lp consumer's own
|
||||
// The conversions are file-local statics in the .cpp: an lp consumer's own
|
||||
// translation units must not need the wire type to include the header.
|
||||
TEST(Records, ConversionsStayOutOfTheHeader)
|
||||
{
|
||||
@@ -172,11 +172,11 @@ TEST(Records, ReturnTypesAreQualifiedInTheDefinition)
|
||||
// from its own uninitialized local — it compiled, with only a warning.
|
||||
TEST(Records, ContainerLambdasDoNotShadowTheDecoderLocals)
|
||||
{
|
||||
for (ApiStyle style : {ApiStyle::Qt, ApiStyle::Std, ApiStyle::Lp}) {
|
||||
for (ApiStyle style : {ApiStyle::Qt, ApiStyle::Lp}) {
|
||||
const QString c = makeSource("info_module", "InfoModule", "info_module_api.h",
|
||||
statusMethods(), style, {}, BindMode::Static,
|
||||
statusRecords());
|
||||
// The decoder's own map is `__m` (Qt/Std); a nested lambda must not
|
||||
// The decoder's own map is `__m` (Qt); a nested lambda must not
|
||||
// declare another one.
|
||||
EXPECT_FALSE(c.contains("const QVariantMap __m = (__m.value")) << c.toStdString();
|
||||
EXPECT_FALSE(c.contains("const nlohmann::json& __j = w.at")) << c.toStdString();
|
||||
@@ -188,7 +188,7 @@ TEST(Records, ContainerLambdasDoNotShadowTheDecoderLocals)
|
||||
TEST(Records, EmptyRecordSetChangesNothing)
|
||||
{
|
||||
const QJsonArray methods{method("ping", "QString", QJsonArray{param("msg", "QString")})};
|
||||
for (ApiStyle style : {ApiStyle::Qt, ApiStyle::Std, ApiStyle::Lp}) {
|
||||
for (ApiStyle style : {ApiStyle::Qt, ApiStyle::Lp}) {
|
||||
EXPECT_EQ(makeHeader("m", "M", methods, style, {}, BindMode::Static, {}),
|
||||
makeHeader("m", "M", methods, style, {}, BindMode::Static));
|
||||
EXPECT_EQ(makeSource("m", "M", "m_api.h", methods, style, {}, BindMode::Static, {}),
|
||||
|
||||
Reference in New Issue
Block a user