mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-30 17:21:15 +00:00
feat(generator): a Qt-typed umbrella that needs no LogosAPI
Splits a consumer's TYPE SURFACE from its TRANSPORT. Until now the qt umbrella
was `explicit LogosModules(LogosAPI* api)` while the lp one was default-
constructible, so "Qt types" implicitly meant "has a LogosAPI" — and a cdylib
module, whose provider surface is the std logos_module_impl.h C ABI and which
holds no LogosAPI anywhere, could not have Qt-typed dependency wrappers at all.
Its generated glue emits `new LogosModules()` unconditionally
(lidl_gen_cdylib.cpp:693), so the combination did not merely misbehave, it did
not compile.
That was a codegen choice, not a law: the wrapper bodies already run over lp_*.
`--binding api|origin` selects it, defaulting to `api`. A second enum rather
than a third ApiStyle value, deliberately: ApiStyle names the type surface and
is switched on by six emitters (makeHeader/makeSource/returnTypeFor/
paramTypeFor/toWireFor/fromWireFor); a "Qt types, explicit origin" member would
force all six to answer a transport question whose honest answer is "same as
Qt" every time. ApiStyle::Lp ignores the new axis — lp is origin-bound by
construction — and that is asserted rather than assumed.
The emitted umbrella bakes metadata.json#name as the origin literal:
LogosModules() : test_fullapi_cpp(QStringLiteral("test_fullapi_qtproxy")) {}
FullApi bind_full_api(const QString& moduleName) {
return FullApi(QStringLiteral("test_fullapi_qtproxy"), moduleName); }
Origin is the CONSUMER's own name and target is the dep — origin first in both
bind_ overloads. This is the load-bearing property: LpBridge::forTarget derives
origin from `api->moduleName()`, and reusing it silently gives a consumer the
caller's identity, which has already preserved a privilege escalation once in
this tree. An empty metadata name is refused at the CLI (exit 6, naming the
file) and emits `#error` in the header: a module that cannot state its identity
must not compile, and must never be handed a blank or borrowed one.
Verified additive on 172 real metadata.json x 2 api-styles = 344 runs, all
producing output, byte-identical old binary vs new. Mutation control: swapping
bind_<iface>'s (origin, moduleName) to (moduleName, origin) fails the suite at
MakeUmbrellaTest.QtExplicitOriginStatesTheConsumersOwnNameEverywhere. 281 -> 286
tests.
Framing worth keeping: the origin is SELF-ASSERTED from the module's own
metadata and is not attested by the transport. That is not a regression —
`api->moduleName()` is equally process-stated — but "explicit origin" means the
module names itself, not that the host vouches for the name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2ef1c25929
commit
620f2e184c
+42
-4
@@ -222,11 +222,30 @@ static int runUmbrellaMode(const QStringList& args, const QString& progName,
|
||||
ApiStyle apiStyle = ApiStyle::Qt;
|
||||
if (!parseApiStyleFlag(args, apiStyle, err)) return 1;
|
||||
|
||||
// `--binding api|origin` — the umbrella's transport binding (generator_lib.h,
|
||||
// next to the UmbrellaBinding enum).
|
||||
UmbrellaBinding binding = UmbrellaBinding::FromApi;
|
||||
if (!parseUmbrellaBindingFlag(args, binding, err)) return 1;
|
||||
|
||||
// With the Qt surface, `origin` means the per-dependency wrappers are
|
||||
// logos-qt-generator's (`--backend consumer --binding origin`) and this
|
||||
// run emits the UMBRELLA ONLY. The wrapper emitter reached below is the
|
||||
// legacy Qt one, whose every constructor takes a LogosAPI — writing those
|
||||
// next to an origin-bound umbrella would put two mutually incompatible
|
||||
// wrapper flavours in one output directory, and the umbrella's members
|
||||
// would not compile against them. Skipping is the honest outcome, and it
|
||||
// is said out loud rather than inferred from an empty directory.
|
||||
//
|
||||
// Interface NAMES are still collected below, and still drive the
|
||||
// `bind_<name>(...)` factories; only the wrapper FILES are skipped.
|
||||
const bool skipWrappers =
|
||||
(apiStyle == ApiStyle::Qt && binding == UmbrellaBinding::ExplicitOrigin);
|
||||
|
||||
const int metaIdx = args.indexOf("--metadata");
|
||||
if (metaIdx == -1 || metaIdx + 1 >= args.size()) {
|
||||
err << "Usage: " << progName
|
||||
<< " --metadata /absolute/path/to/metadata.json --umbrella (or --general-only)"
|
||||
" [--output-dir /path/to/output] [--api-style qt|lp]"
|
||||
" [--output-dir /path/to/output] [--api-style qt|lp] [--binding api|origin]"
|
||||
" [--interface <name>=<file.lidl|file.h>[=<ImplClass>]]"
|
||||
" [--dep <name>=<file.lidl>]\n";
|
||||
return 1;
|
||||
@@ -316,10 +335,14 @@ static int runUmbrellaMode(const QStringList& args, const QString& progName,
|
||||
}
|
||||
|
||||
// Generate one bound wrapper (<name>_api.{h,cpp}) per interface.
|
||||
if (!ifaceSpecs.isEmpty()) {
|
||||
if (!ifaceSpecs.isEmpty() && !skipWrappers) {
|
||||
if (!generateInterfaceWrappers(ifaceSpecs, genDirPath, apiStyle, out, err)) {
|
||||
return 9;
|
||||
}
|
||||
} else if (!ifaceSpecs.isEmpty()) {
|
||||
err << "Note: --binding origin — emitting the umbrella only. The "
|
||||
<< ifaceSpecs.size() << " interface wrapper(s) must come from "
|
||||
<< "logos-qt-generator --backend consumer --bind bound --binding origin.\n";
|
||||
}
|
||||
|
||||
// Concrete dependencies generated from their published LIDL
|
||||
@@ -347,10 +370,14 @@ static int runUmbrellaMode(const QStringList& args, const QString& progName,
|
||||
haveDep.insert(sp.name);
|
||||
depSpecs.append(sp);
|
||||
}
|
||||
if (!depSpecs.isEmpty()) {
|
||||
if (!depSpecs.isEmpty() && !skipWrappers) {
|
||||
if (!generateInterfaceWrappers(depSpecs, genDirPath, apiStyle, out, err, BindMode::Static)) {
|
||||
return 9;
|
||||
}
|
||||
} else if (!depSpecs.isEmpty()) {
|
||||
err << "Note: --binding origin — emitting the umbrella only. The "
|
||||
<< depSpecs.size() << " dependency wrapper(s) must come from "
|
||||
<< "logos-qt-generator --backend consumer --bind static --binding origin.\n";
|
||||
}
|
||||
|
||||
QStringList interfaceNames;
|
||||
@@ -362,6 +389,17 @@ static int runUmbrellaMode(const QStringList& args, const QString& progName,
|
||||
// what those return. For the Lp (Qt-free) flavor the umbrella bakes this
|
||||
// module's name as the lp_client origin.
|
||||
const QString originName = obj.value("name").toString();
|
||||
// The origin is this module's OWN name, and with `--binding origin` it is
|
||||
// the only thing standing between a generated wrapper and calling out under
|
||||
// somebody else's identity. Refuse at the CLI as well as in the emitter
|
||||
// (which writes an `#error`): failing here names the metadata file, which
|
||||
// is where the fix is.
|
||||
if (binding == UmbrellaBinding::ExplicitOrigin && originName.isEmpty()) {
|
||||
err << "--binding origin needs the consuming module's own name, and "
|
||||
<< metaResolvedPath << " declares no \"name\". The origin is asserted, "
|
||||
<< "never derived from a caller.\n";
|
||||
return 6;
|
||||
}
|
||||
const QDir genDir(genDirPath);
|
||||
{
|
||||
QFile outFile(genDir.filePath("logos_sdk.h"));
|
||||
@@ -369,7 +407,7 @@ static int runUmbrellaMode(const QStringList& args, const QString& progName,
|
||||
err << "Failed to write umbrella header: " << outFile.fileName() << "\n";
|
||||
return 7;
|
||||
}
|
||||
outFile.write(makeUmbrellaHeaderFromDeps(deps, interfaceNames, apiStyle, originName).toUtf8());
|
||||
outFile.write(makeUmbrellaHeaderFromDeps(deps, interfaceNames, apiStyle, originName, binding).toUtf8());
|
||||
outFile.close();
|
||||
}
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user