refactor(cpp-generator): merge legacy/ into the generator, dropping its dead half

`legacy/` was never a library with an API surface. Two files, 475 lines, exactly
one exported symbol — `int legacy_main(int, char**)` — with every other
definition `static`, compiled INTO logos-cpp-generator and reached by fallthrough
at the end of main(). So there was nothing to keep separate: it is one mode of
this binary, and it now lives beside the others as plugin_introspect.{cpp,h}
behind `runPluginIntrospectMode()`, named for what it does.

Deleting it was never an option — that premise was checked and refused earlier.
`--general-only` alone has ~10 live callers across 7 repos including the central
module path (buildPlugin.nix:206,211, buildHeaders.nix:221,
LogosModule.cmake:423). The mode is load-bearing; only its packaging was wrong.

110 lines go with the move, all genuinely unreferenced:

  * cppStringEscape — zero callers anywhere.
  * writeUmbrellaHeader / writeUmbrellaSource and the `if (!moduleOnly)` block
    that called them. This is the real prize: a SECOND, directory-SCRAPING
    implementation of logos_sdk.{h,cpp}, unreachable in practice because
    generate-module-headers.sh:60 always passes --module-only. generator_lib's
    deps-driven makeUmbrella*FromDeps is now the only umbrella emitter, so the
    two cannot drift.
  * a dead `QJsonDocument doc(methods);` and its commented-out use.

With the block gone, `--module-only` suppresses nothing, so the parameter and
its plumbing go too. The FLAG stays tolerated rather than rejected, because
generate-module-headers.sh passes it unconditionally — a comment at the old
parse site says so.

Verified: #default builds, and both checks pass — `generator-cli` (which
exercises the CLI surface, including --general-only) and `tests`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dario Gabriel Lipicar
2026-08-18 16:46:09 -03:00
co-authored by Claude Opus 5
parent d27927aba5
commit 1afcf46f4d
6 changed files with 37 additions and 140 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ add_executable(logos-cpp-generator
main.cpp
generator_lib.cpp
lidl_to_json.cpp
legacy/main.cpp
plugin_introspect.cpp
experimental/lidl_emit_common.cpp
experimental/lidl_gen_client.cpp
experimental/lidl_gen_cdylib.cpp
+1 -1
View File
@@ -33,7 +33,7 @@ enum class ApiStyle { Qt, Lp };
// 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
// umbrella mode in main.cpp and the plugin-introspection 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).
-6
View File
@@ -1,6 +0,0 @@
#ifndef LEGACY_MAIN_H
#define LEGACY_MAIN_H
int legacy_main(int argc, char* argv[]);
#endif // LEGACY_MAIN_H
+9 -9
View File
@@ -1,4 +1,4 @@
#include "legacy/legacy_main.h"
#include "plugin_introspect.h"
#include "generator_lib.h"
#include "lidl_to_json.h"
#include "experimental/lidl_gen_client.h"
@@ -26,11 +26,11 @@
// its interface dependencies, and the per-dependency / per-interface wrappers
// those aggregate.
//
// This is NOT a legacy mode, despite having lived in `legacy/main.cpp` until
// This is NOT a legacy mode, despite having lived in `plugin_introspect.cpp` until
// now: `LogosModuleContext::modules()` returns `LogosModules&`, so every
// `interface: "universal"` module that calls a declared dependency goes
// through it, and LogosModule.cmake runs it for every module build. Only the
// QPluginLoader-introspection path in `legacy/main.cpp` is legacy.
// QPluginLoader-introspection path in `plugin_introspect.cpp` is legacy.
//
// `--general-only` is kept as an exact alias — it is what LogosModule.cmake,
// buildPlugin.nix and buildHeaders.nix all pass today — so there is ONE
@@ -217,7 +217,7 @@ static int runUmbrellaMode(const QStringList& args, const QString& progName,
outputDir = stripAt(args.at(outDirIdx + 1));
}
// `--api-style qt|lp` — the one parser, shared with legacy_main's plugin
// `--api-style qt|lp` — the one parser, shared with runPluginIntrospectMode's plugin
// path (generator_lib.h, next to the ApiStyle enum).
ApiStyle apiStyle = ApiStyle::Qt;
if (!parseApiStyleFlag(args, apiStyle, err)) return 1;
@@ -428,7 +428,7 @@ static int runUmbrellaMode(const QStringList& args, const QString& progName,
int main(int argc, char* argv[])
{
// Check for --lidl / --from-header / --header-to-lidl mode before
// initializing QCoreApplication, since legacy_main creates its own.
// initializing QCoreApplication, since runPluginIntrospectMode creates its own.
bool hasLidl = false;
bool hasFromHeader = false;
bool hasHeaderToLidl = false;
@@ -446,8 +446,8 @@ int main(int argc, char* argv[])
}
// Umbrella mode. `--general-only` routes here too — ONE implementation,
// no second copy in legacy/main.cpp to drift — but only in the shape
// legacy_main ever honoured it: inside the `--metadata` branch. Without
// no second copy in plugin_introspect.cpp to drift — but only in the shape
// runPluginIntrospectMode ever honoured it: inside the `--metadata` branch. Without
// `--metadata` the flag was never a mode at all (it fell through to the
// plugin path and reported the flag itself as a missing plugin file), so
// that case still falls through, unchanged.
@@ -471,7 +471,7 @@ int main(int argc, char* argv[])
const QStringList args = app.arguments();
// Strip a leading '@' from path arguments — some build drivers pass
// `@/abs/path`. Matches the legacy_main path handling.
// `@/abs/path`. Matches the runPluginIntrospectMode path handling.
auto stripAt = [](QString p) { if (p.startsWith('@')) p.remove(0, 1); return p; };
const int idx = args.indexOf("--header-to-lidl");
@@ -769,5 +769,5 @@ int main(int argc, char* argv[])
return lidlGenerateClientStubs(lidlPath, outputDir, moduleOnly, out, err);
}
return legacy_main(argc, argv);
return runPluginIntrospectMode(argc, argv);
}
@@ -14,20 +14,10 @@
#include <QRegularExpression>
#include <QtGlobal>
#include "logos_provider_interface.h"
#include "../generator_lib.h"
#include "../metadata_dependencies.h"
#include "../experimental/lidl_compat.h"
#include "../lidl_to_json.h" // ModuleDecl -> the JSON surface generator_lib consumes
// Escape a string for safe embedding inside a generated C++ string literal.
static QString cppStringEscape(const QString& s)
{
QString out = s;
out.replace('\\', "\\\\");
out.replace('"', "\\\"");
out.replace('\n', "\\n");
return out;
}
#include "generator_lib.h"
#include "metadata_dependencies.h"
#include "experimental/lidl_compat.h"
#include "lidl_to_json.h" // ModuleDecl -> the JSON surface generator_lib consumes
// Load events from a `.lidl` sidecar shipped alongside a module's
// pre-built headers. Returns a JSON array of
@@ -114,97 +104,7 @@ static QJsonArray enumerateMethods(QObject* moduleInstance)
// makeSource -> generator_lib.h/cpp
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 (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>`).
//
// `core_manager_api.h` (if present in the gen dir from an older
// run) is intentionally filtered out — universal modules access
// only the deps they explicitly declared in `metadata.json#
// dependencies`. Apps that need to manage the core use the C API
// in liblogos directly, not the typed `LogosModules` aggregate.
QDir genDir(genDirPath);
QStringList headers = genDir.entryList(QStringList() << "*_api.h", QDir::Files | QDir::Readable);
headers.removeAll(QStringLiteral("core_manager_api.h"));
QString content;
QTextStream s(&content);
s << "#pragma once\n";
s << "#include \"logos_api.h\"\n";
s << "#include \"logos_api_client.h\"\n\n";
for (const QString& h : headers) s << "#include \"" << h << "\"\n";
s << "\n";
s << "struct LogosModules {\n";
s << " explicit LogosModules(LogosAPI* api) : api(api)";
for (const QString& h : headers) {
QString base = h;
base.chop(QString("_api.h").size());
s << ", \n " << base << "(api)";
}
s << " {}\n";
s << " LogosAPI* api;\n";
for (const QString& h : headers) {
QString base = h;
base.chop(QString("_api.h").size());
QString className = toPascalCase(base);
s << " " << className << " " << base << ";\n";
}
s << "};\n";
QFile outFile(genDir.filePath("logos_sdk.h"));
if (!outFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
err << "Failed to write umbrella header: " << outFile.fileName() << "\n";
return false;
}
outFile.write(content.toUtf8());
outFile.close();
return true;
}
static bool writeUmbrellaSource(const QString& genDirPath, QTextStream& err)
{
// Generate logos_sdk.cpp: one #include per per-module wrapper
// `.cpp` in the gen dir. There's now exactly one wrapper file per
// module (Qt or std, picked at generation time), so no de-dup or
// twin-file filtering is needed.
//
// `core_manager_api.cpp` (if present from an older run) is
// filtered out — the umbrella header no longer declares
// `CoreManager core_manager;` so including its definitions would
// produce dead code.
QDir genDir(genDirPath);
QStringList sources = genDir.entryList(QStringList() << "*_api.cpp", QDir::Files | QDir::Readable);
sources.removeAll(QStringLiteral("core_manager_api.cpp"));
QString content;
QTextStream s(&content);
s << "#include \"logos_sdk.h\"\n\n";
for (const QString& c : sources) s << "#include \"" << c << "\"\n";
s << "\n";
QFile outFile(genDir.filePath("logos_sdk.cpp"));
if (!outFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
err << "Failed to write umbrella source: " << outFile.fileName() << "\n";
return false;
}
outFile.write(content.toUtf8());
outFile.close();
return true;
}
// The deps-driven umbrella writers (writeUmbrellaHeaderFromDeps /
// writeUmbrellaSourceFromDeps) moved to ../main.cpp's umbrella mode, which is
// now the only caller of makeUmbrellaHeaderFromDeps / makeUmbrellaSourceFromDeps.
// The two directory-SCRAPING writers above stay: they belong to
// generateFromPlugin (QPluginLoader introspection) and die with it.
static int generateFromPlugin(const QString& pluginInputPath, const QString& outputDir, bool moduleOnly, ApiStyle apiStyle, const QJsonArray& events, QTextStream& out, QTextStream& err, const QJsonArray& records = {})
static int generateFromPlugin(const QString& pluginInputPath, const QString& outputDir, ApiStyle apiStyle, const QJsonArray& events, QTextStream& out, QTextStream& err, const QJsonArray& records = {})
{
QFileInfo fi(pluginInputPath);
if (!fi.exists()) {
@@ -297,19 +197,6 @@ static int generateFromPlugin(const QString& pluginInputPath, const QString& out
f.close();
}
if (!moduleOnly) {
if (!writeUmbrellaHeader(genDirPath, err)) {
loader.unload();
return 7;
}
if (!writeUmbrellaSource(genDirPath, err)) {
loader.unload();
return 8;
}
}
QJsonDocument doc(methods);
// out << doc.toJson(QJsonDocument::Indented) << "\n";
out << "Generated: " << QDir(genDirPath).filePath(headerRel) << " and " << QDir(genDirPath).filePath(sourceRel) << "\n";
out.flush();
@@ -317,7 +204,7 @@ static int generateFromPlugin(const QString& pluginInputPath, const QString& out
return 0;
}
int legacy_main(int argc, char* argv[])
int runPluginIntrospectMode(int argc, char* argv[])
{
QCoreApplication app(argc, argv);
@@ -336,12 +223,15 @@ int legacy_main(int argc, char* argv[])
}
}
// Parse --module-only option
bool moduleOnly = args.contains("--module-only");
// `--module-only` is accepted and ignored. The only thing it ever
// suppressed was the directory-scraping umbrella pair above, which is gone
// — generator_lib's deps-driven makeUmbrella*FromDeps is the sole umbrella
// emitter now. generate-module-headers.sh:60 always passes the flag, so it
// stays tolerated rather than rejected.
// `--general-only` (the umbrella) is NOT handled here any more: ../main.cpp
// intercepts it, together with its new `--umbrella` spelling, and runs the
// one non-legacy implementation. It can only reach legacy_main when it was
// one non-legacy implementation. It can only reach runPluginIntrospectMode when it was
// passed WITHOUT --metadata, which was never a mode — the plugin path
// below reports it as a missing plugin file, exactly as before.
@@ -471,5 +361,5 @@ int legacy_main(int argc, char* argv[])
}
QString argPath = args.at(1);
return generateFromPlugin(argPath, outputDir, moduleOnly, apiStyle, eventsFromSidecar, out, err, recordsFromSidecar);
return generateFromPlugin(argPath, outputDir, apiStyle, eventsFromSidecar, out, err, recordsFromSidecar);
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef PLUGIN_INTROSPECT_H
#define PLUGIN_INTROSPECT_H
// The QPluginLoader-introspection mode: given a BUILT plugin, walk its
// QMetaObject and emit a consumer wrapper for it. main() falls through to this
// when its own modes do not claim the arguments.
//
// This lived in `legacy/main.cpp` behind `legacy_main()`. The directory was not
// a legacy library — it held exactly one exported symbol and one reachable
// mode — so it was merged here rather than kept as a parallel implementation.
int runPluginIntrospectMode(int argc, char* argv[]);
#endif // PLUGIN_INTROSPECT_H