mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 09:41:06 +00:00
`cpp-generator/legacy/` held four things and only one was legacy. The shared
emitter library was misfiled there: `generator_lib.{h,cpp}` is already consumed
by the MODERN `experimental/lidl_gen_client.h` and by all 11 tests under
tests/generator/. `lidl_to_json.{h,cpp}` likewise. Both are now
`cpp-generator/`; `legacy/` is down to `main.cpp` + `legacy_main.h`.
The logos_sdk umbrella (`struct LogosModules`) is not legacy either — it is the
CURRENT typed-dependency surface. `LogosModuleContext::modules()` returns it,
so every universal module that calls a declared dependency goes through it, and
LogosModule.cmake runs `--general-only` for every module build. Yet the only
code that could emit it lived inside the directory the plan wants deleted.
So `cpp-generator/main.cpp` gains `--umbrella`, with `--general-only` routed to
the same implementation and dispatched before the fall-through to legacy_main.
The deps-driven emission needed no rewriting: `makeUmbrella{Header,Source}
FromDeps` were already in generator_lib, and legacy/main.cpp merely wrapped
them in file I/O. -352 lines from legacy/main.cpp (827 -> 475), including the
interface-wrapper helpers that only that branch used.
`--general-only` keeps working identically, because LogosModule.cmake and
logos-basecamp both call it. The alias is guarded on `--metadata`, since
`--general-only` was never a standalone mode — without metadata it fell through
and reported the flag as a missing plugin path, and it still does.
The scraping `writeUmbrellaHeader`/`writeUmbrellaSource` are untouched: they
belong to `generateFromPlugin`, the QPluginLoader introspection path, and die
with it.
Verified byte-identical, which is the whole claim of a relocation. An
adversarial pass built its own pre- and post-change binaries and diffed the
emitted `logos_sdk.{h,cpp}` across 12 real metadata.json files x {qt,lp} x
{--general-only,--umbrella}: 48/48 identical, stdout/stderr/exit included, with
a positive control (qt vs lp) confirming the harness can see a difference. Real
modules then built through logos-module-builder against both binaries with
`diff -r` empty, including the compiled plugin. 266/266 tests pass, and the
pre-change tree also reports 266, so no test was silently dropped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
128 lines
7.7 KiB
C++
128 lines
7.7 KiB
C++
#ifndef GENERATOR_LIB_H
|
|
#define GENERATOR_LIB_H
|
|
|
|
#include <QString>
|
|
#include <QJsonArray>
|
|
#include <QStringList>
|
|
#include <QTextStream>
|
|
#include <QVector>
|
|
#include <QPair>
|
|
|
|
// 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 Lp via the -DLOGOS_API_STYLE=lp CMake flag the module builder
|
|
// threads through.
|
|
// Qt — legacy Qt-typed surface (QString/QVariant…), body via LogosAPIClient.
|
|
// 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).
|
|
//
|
|
// 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 };
|
|
|
|
// Parse the `--api-style` flag out of a raw argument list. Both spellings are
|
|
// accepted (`--api-style lp` and `--api-style=lp`); absent means Qt. Returns
|
|
// false — having written a diagnostic to `err` — for a value the generator
|
|
// refuses, in which case `outStyle` is untouched and the caller must exit 1.
|
|
//
|
|
// Lives here, next to the enum, because BOTH CLI entry points need it: the
|
|
// umbrella mode in main.cpp and legacy_main's plugin path. Two copies of this
|
|
// table is exactly how the surfaces drift apart.
|
|
//
|
|
// `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.
|
|
bool parseApiStyleFlag(const QStringList& args, ApiStyle& outStyle, QTextStream& err);
|
|
|
|
// Whether the generated wrapper targets ONE fixed module (the historical
|
|
// behaviour) or binds to a module name chosen at runtime.
|
|
// Static — the module name is baked into the ctor + every remote call,
|
|
// so `<Class>(LogosAPI*)` always talks to that one module.
|
|
// This is what name-baked dependency wrappers use.
|
|
// Bound — the ctor takes `(LogosAPI*, const QString& moduleName)` and
|
|
// stores it in `m_moduleName`; every remote call routes through
|
|
// that member. This is what *interface* wrappers use: one
|
|
// interface, bound to a concrete module name at runtime.
|
|
// Default is Static so existing callers and their generated output are
|
|
// byte-for-byte unchanged.
|
|
enum class BindMode { Static, Bound };
|
|
|
|
QString toPascalCase(const QString& name);
|
|
QString normalizeType(QString t);
|
|
QString mapParamType(const QString& qtType);
|
|
QString mapReturnType(const QString& qtType);
|
|
QString toQVariantConversion(const QString& type, const QString& argExpr);
|
|
|
|
// Incoming provider argument -> the declared type, via the canonical codec
|
|
// (logos::qtArgFromVariant). `path` names the slot in the diagnostic a
|
|
// rejection carries, e.g. "arg0". See the definition for why this is separate
|
|
// from toQVariantConversion.
|
|
QString toProviderArgDecode(const QString& type, const QString& argExpr,
|
|
const QString& path);
|
|
|
|
// makeHeader / makeSource emit the single `<Class>` wrapper for a
|
|
// 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
|
|
// { "name": "<event>", "params": [ { "name": "...", "type": "<QtTypeName>" } ] }
|
|
// (Qt-typed names — same surface methods come through). When non-empty,
|
|
// the wrapper also gets one `on<EventName>(callback)` accessor per
|
|
// event next to the existing generic `onEvent(name, callback)` channel.
|
|
// The accessor signature uses the apiStyle's type surface for the
|
|
// callback's argument types.
|
|
//
|
|
// `bindMode` selects a fixed-module wrapper (Static, default) or a
|
|
// runtime-bound interface wrapper (Bound) — see BindMode above. In Bound
|
|
// mode `moduleName` is used only for the class/file naming the caller
|
|
// already decided; the emitted code never bakes it into a call.
|
|
//
|
|
// `records` carries the contract's `type Foo { ... }` declarations, as
|
|
// [ { "name": "Foo", "fields": [ { "name": "...", "type": "<QtTypeName>" } ] } ]
|
|
// Each becomes a struct NESTED in the wrapper class (`<Class>::Foo`, so two
|
|
// deps may both declare a `Status`), and every method / event that mentions
|
|
// one is typed with it instead of falling back to QVariant / LogosMap. Field
|
|
// types use the same Qt type-name spelling as methods, so a field can name
|
|
// another record, `QList<Record>`, or `QMap<QString, Record>`. Empty (the
|
|
// default, and what the metaobject-introspection path passes) leaves the
|
|
// generated output exactly as it was.
|
|
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. 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 = {});
|
|
QString makeSourceLp(const QString& moduleName, const QString& className, const QString& headerBaseName, const QJsonArray& methods, const QJsonArray& events = {}, BindMode bindMode = BindMode::Static, const QJsonArray& records = {});
|
|
|
|
// The umbrella (`logos_sdk.h` / `logos_sdk.cpp`) over a module's declared
|
|
// `metadata.json#dependencies` + interface dependencies: one `#include` and one
|
|
// `LogosModules` member per dep, so a module reaches its deps as
|
|
// `modules().<dep>`. `deps` is the raw metadata array — elements are read
|
|
// through dependencyNames() (metadata_dependencies.h), never element by
|
|
// element, so includes and members can never disagree about what it declares.
|
|
//
|
|
// ApiStyle::Lp emits the Qt-free umbrella: no LogosAPI member, each wrapper
|
|
// self-creates its lp_client on behalf of `originName` (the module being
|
|
// generated for), so the struct is default-constructible. Qt emits the
|
|
// LogosAPI-threading form, where `originName` is unused.
|
|
QString makeUmbrellaHeaderFromDeps(const QJsonArray& deps, const QStringList& interfaceNames, ApiStyle apiStyle = ApiStyle::Qt, const QString& originName = QString());
|
|
QString makeUmbrellaSourceFromDeps(const QJsonArray& deps, const QStringList& interfaceNames);
|
|
|
|
#endif // GENERATOR_LIB_H
|