2026-03-31 16:29:35 -04:00
|
|
|
#include "legacy/legacy_main.h"
|
|
|
|
|
#include "experimental/lidl_gen_client.h"
|
2026-06-12 22:10:16 -03:00
|
|
|
#include "experimental/lidl_gen_cdylib.h"
|
2026-06-16 21:00:42 -03:00
|
|
|
#include "experimental/lidl_compat.h"
|
2026-03-31 16:29:35 -04:00
|
|
|
#include "experimental/impl_header_parser.h"
|
|
|
|
|
|
2025-09-30 14:56:47 -04:00
|
|
|
#include <QCoreApplication>
|
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QFile>
|
2026-03-31 16:29:35 -04:00
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <QTextStream>
|
2025-09-30 14:56:47 -04:00
|
|
|
|
|
|
|
|
int main(int argc, char* argv[])
|
|
|
|
|
{
|
2026-06-08 11:43:10 -03:00
|
|
|
// Check for --lidl / --from-header / --header-to-lidl mode before
|
|
|
|
|
// initializing QCoreApplication, since legacy_main creates its own.
|
2026-03-31 16:29:35 -04:00
|
|
|
bool hasLidl = false;
|
|
|
|
|
bool hasFromHeader = false;
|
2026-06-08 11:43:10 -03:00
|
|
|
bool hasHeaderToLidl = false;
|
2026-03-31 16:29:35 -04:00
|
|
|
for (int i = 1; i < argc; ++i) {
|
|
|
|
|
QString arg = QString::fromUtf8(argv[i]);
|
|
|
|
|
if (arg == "--lidl") hasLidl = true;
|
|
|
|
|
if (arg == "--from-header") hasFromHeader = true;
|
2026-06-08 11:43:10 -03:00
|
|
|
if (arg == "--header-to-lidl") hasHeaderToLidl = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --header-to-lidl: the C++ frontend of the source -> LIDL -> bindings
|
|
|
|
|
// pipeline. Parse an impl header and emit ONLY its LIDL contract (no Qt
|
|
|
|
|
// glue / dispatch), so a module can publish a cheap `lidl` artifact that
|
|
|
|
|
// consumers (any language) turn into bindings without building the module.
|
|
|
|
|
if (hasHeaderToLidl) {
|
|
|
|
|
QCoreApplication app(argc, argv);
|
|
|
|
|
QTextStream err(stderr);
|
|
|
|
|
QTextStream out(stdout);
|
|
|
|
|
const QStringList args = app.arguments();
|
|
|
|
|
|
|
|
|
|
// Strip a leading '@' from path arguments — some build drivers pass
|
|
|
|
|
// `@/abs/path`. Matches the legacy_main path handling.
|
|
|
|
|
auto stripAt = [](QString p) { if (p.startsWith('@')) p.remove(0, 1); return p; };
|
|
|
|
|
|
|
|
|
|
const int idx = args.indexOf("--header-to-lidl");
|
|
|
|
|
if (idx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --header-to-lidl requires a path to the impl header\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
const QString headerPath = stripAt(args.at(idx + 1));
|
|
|
|
|
|
|
|
|
|
const int implClassIdx = args.indexOf("--impl-class");
|
|
|
|
|
if (implClassIdx == -1 || implClassIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --header-to-lidl requires --impl-class <ClassName>\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
const QString implClass = args.at(implClassIdx + 1);
|
|
|
|
|
|
|
|
|
|
const int metadataIdx = args.indexOf("--metadata");
|
|
|
|
|
if (metadataIdx == -1 || metadataIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --header-to-lidl requires --metadata <metadata.json>\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
const QString metadataPath = stripAt(args.at(metadataIdx + 1));
|
|
|
|
|
|
|
|
|
|
ImplParseResult pr = parseImplHeader(headerPath, implClass, metadataPath, err);
|
|
|
|
|
if (pr.hasError()) {
|
|
|
|
|
err << "Error parsing impl header: " << pr.error << "\n";
|
|
|
|
|
return 4;
|
|
|
|
|
}
|
|
|
|
|
const ModuleDecl& mod = pr.module;
|
|
|
|
|
|
|
|
|
|
// Output path: explicit -o/--output <file>, else <output-dir>/<name>.lidl,
|
|
|
|
|
// else <name>.lidl in the CWD.
|
|
|
|
|
QString outPath;
|
|
|
|
|
const int oIdx = args.indexOf("-o");
|
|
|
|
|
const int outputIdx = args.indexOf("--output");
|
|
|
|
|
const int outDirIdx = args.indexOf("--output-dir");
|
|
|
|
|
if (oIdx != -1 && oIdx + 1 < args.size()) {
|
|
|
|
|
outPath = stripAt(args.at(oIdx + 1));
|
|
|
|
|
} else if (outputIdx != -1 && outputIdx + 1 < args.size()) {
|
|
|
|
|
outPath = stripAt(args.at(outputIdx + 1));
|
|
|
|
|
} else if (outDirIdx != -1 && outDirIdx + 1 < args.size()) {
|
|
|
|
|
const QString d = stripAt(args.at(outDirIdx + 1));
|
|
|
|
|
QDir().mkpath(d);
|
2026-06-16 21:00:42 -03:00
|
|
|
outPath = QDir(d).filePath(qs(mod.name) + ".lidl");
|
2026-06-08 11:43:10 -03:00
|
|
|
} else {
|
2026-06-16 21:00:42 -03:00
|
|
|
outPath = qs(mod.name) + ".lidl";
|
2026-06-08 11:43:10 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QFile f(outPath);
|
|
|
|
|
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
|
|
|
|
|
err << "Failed to write LIDL: " << outPath << "\n";
|
|
|
|
|
return 5;
|
|
|
|
|
}
|
|
|
|
|
f.write(lidlSerialize(mod).toUtf8());
|
|
|
|
|
f.close();
|
|
|
|
|
out << "Generated LIDL: " << outPath << " (" << mod.methods.size()
|
|
|
|
|
<< " methods, " << mod.events.size() << " events)\n";
|
|
|
|
|
out.flush();
|
|
|
|
|
return 0;
|
2025-10-23 10:06:50 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 16:29:35 -04:00
|
|
|
if (hasLidl || hasFromHeader) {
|
|
|
|
|
QCoreApplication app(argc, argv);
|
|
|
|
|
QTextStream err(stderr);
|
|
|
|
|
QTextStream out(stdout);
|
|
|
|
|
const QStringList args = app.arguments();
|
2025-10-23 10:15:06 -04:00
|
|
|
|
2026-03-31 16:29:35 -04:00
|
|
|
QString outputDir;
|
|
|
|
|
const int outDirIdx = args.indexOf("--output-dir");
|
|
|
|
|
if (outDirIdx != -1 && outDirIdx + 1 < args.size()) {
|
|
|
|
|
outputDir = args.at(outDirIdx + 1);
|
|
|
|
|
}
|
2025-10-23 10:45:34 -04:00
|
|
|
|
2026-03-31 16:29:35 -04:00
|
|
|
// --from-header mode: parse C++ impl header directly (no .lidl needed)
|
|
|
|
|
if (hasFromHeader) {
|
|
|
|
|
const int fromHeaderIdx = args.indexOf("--from-header");
|
|
|
|
|
if (fromHeaderIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --from-header requires a path to the impl header\n";
|
2025-09-30 14:56:47 -04:00
|
|
|
return 1;
|
|
|
|
|
}
|
2026-03-31 16:29:35 -04:00
|
|
|
QString headerPath = args.at(fromHeaderIdx + 1);
|
|
|
|
|
|
|
|
|
|
const int implClassIdx = args.indexOf("--impl-class");
|
|
|
|
|
if (implClassIdx == -1 || implClassIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --from-header requires --impl-class <ClassName>\n";
|
|
|
|
|
return 1;
|
2025-09-30 14:56:47 -04:00
|
|
|
}
|
2026-03-31 16:29:35 -04:00
|
|
|
QString implClass = args.at(implClassIdx + 1);
|
|
|
|
|
|
|
|
|
|
const int metadataIdx = args.indexOf("--metadata");
|
|
|
|
|
if (metadataIdx == -1 || metadataIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --from-header requires --metadata <metadata.json>\n";
|
|
|
|
|
return 1;
|
2025-09-30 14:56:47 -04:00
|
|
|
}
|
2026-03-31 16:29:35 -04:00
|
|
|
QString metadataPath = args.at(metadataIdx + 1);
|
|
|
|
|
|
|
|
|
|
// --impl-header: the include path for generated code (defaults to header filename)
|
|
|
|
|
QString implHeader;
|
|
|
|
|
const int implHeaderIdx = args.indexOf("--impl-header");
|
|
|
|
|
if (implHeaderIdx != -1 && implHeaderIdx + 1 < args.size()) {
|
|
|
|
|
implHeader = args.at(implHeaderIdx + 1);
|
|
|
|
|
} else {
|
|
|
|
|
implHeader = QFileInfo(headerPath).fileName();
|
2025-09-30 14:56:47 -04:00
|
|
|
}
|
2026-03-31 16:29:35 -04:00
|
|
|
|
|
|
|
|
const int backendIdx = args.indexOf("--backend");
|
|
|
|
|
if (backendIdx == -1 || backendIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --from-header requires --backend <qt>\n";
|
|
|
|
|
return 1;
|
2025-09-30 14:56:47 -04:00
|
|
|
}
|
2026-03-31 16:29:35 -04:00
|
|
|
QString backend = args.at(backendIdx + 1);
|
|
|
|
|
|
|
|
|
|
// Parse the impl header
|
|
|
|
|
ImplParseResult pr = parseImplHeader(headerPath, implClass, metadataPath, err);
|
|
|
|
|
if (pr.hasError()) {
|
|
|
|
|
err << "Error parsing impl header: " << pr.error << "\n";
|
2025-09-30 14:56:47 -04:00
|
|
|
return 4;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 16:29:35 -04:00
|
|
|
const ModuleDecl& mod = pr.module;
|
|
|
|
|
QString genDirPath = outputDir.isEmpty()
|
|
|
|
|
? QDir::current().filePath("generated")
|
|
|
|
|
: outputDir;
|
|
|
|
|
QDir().mkpath(genDirPath);
|
|
|
|
|
|
2026-06-12 22:10:16 -03:00
|
|
|
if (backend == "cdylib") {
|
|
|
|
|
// Cdylib authoring: the common module-impl C ABI exports +
|
|
|
|
|
// the uniform Qt-plugin glue (language-agnostic, forwards to
|
|
|
|
|
// the C symbols). See logos_module_impl.h in logos-protocol.
|
|
|
|
|
QString cdErr;
|
|
|
|
|
if (!lidlCdylibSupported(mod, &cdErr)) {
|
|
|
|
|
err << "Error: module not cdylib-eligible: " << cdErr << "\n";
|
|
|
|
|
return 10;
|
|
|
|
|
}
|
|
|
|
|
struct Out { QString file; QString content; };
|
|
|
|
|
QList<Out> outs;
|
2026-07-28 17:24:49 -03:00
|
|
|
// Always emitted, records or not: the exports TU and the events
|
|
|
|
|
// sidecar both reference the generated codec, and a module with
|
|
|
|
|
// no `type` decls still has containers to encode.
|
|
|
|
|
outs.append({qs(mod.name) + "_types.h", lidlMakeTypesHeaderCdylib(mod)});
|
2026-06-16 21:00:42 -03:00
|
|
|
outs.append({qs(mod.name) + "_module_impl.cpp",
|
2026-06-12 22:10:16 -03:00
|
|
|
lidlMakeModuleImplExports(mod, implClass, implHeader)});
|
2026-06-16 21:00:42 -03:00
|
|
|
if (!mod.events.empty())
|
|
|
|
|
outs.append({qs(mod.name) + "_events_cdylib.cpp",
|
2026-06-12 22:10:16 -03:00
|
|
|
lidlMakeEventsSourceCdylib(mod, implClass, implHeader)});
|
2026-06-16 21:00:42 -03:00
|
|
|
outs.append({qs(mod.name) + ".lidl", lidlSerialize(mod)});
|
2026-06-12 22:10:16 -03:00
|
|
|
for (const Out& o : outs) {
|
|
|
|
|
const QString abs = QDir(genDirPath).filePath(o.file);
|
|
|
|
|
QFile f(abs);
|
2026-03-31 16:29:35 -04:00
|
|
|
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
|
2026-06-12 22:10:16 -03:00
|
|
|
err << "Failed to write: " << abs << "\n";
|
|
|
|
|
return 11;
|
2026-03-31 16:29:35 -04:00
|
|
|
}
|
2026-06-12 22:10:16 -03:00
|
|
|
f.write(o.content.toUtf8());
|
|
|
|
|
out << "Generated: " << abs << "\n";
|
2025-10-23 10:45:34 -04:00
|
|
|
}
|
|
|
|
|
out.flush();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-12 22:10:16 -03:00
|
|
|
if (backend == "qt") {
|
|
|
|
|
err << "Error: Qt glue generation moved to logos-qt-generator "
|
|
|
|
|
"(logos-qt-sdk). Use it for --backend qt; this tool "
|
|
|
|
|
"keeps the Qt-free outputs (--header-to-lidl emits the "
|
|
|
|
|
".lidl sidecar).\n";
|
|
|
|
|
return 6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err << "Error: --from-header supports --backend cdylib (Qt glue: logos-qt-generator)\n";
|
2026-03-31 16:29:35 -04:00
|
|
|
return 1;
|
2025-09-30 14:56:47 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 16:29:35 -04:00
|
|
|
// --lidl mode
|
|
|
|
|
const int lidlIdx = args.indexOf("--lidl");
|
|
|
|
|
if (lidlIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Usage: " << QFileInfo(app.applicationFilePath()).fileName()
|
|
|
|
|
<< " --lidl /path/to/module.lidl [--output-dir /path] [--module-only]\n"
|
|
|
|
|
<< " " << QFileInfo(app.applicationFilePath()).fileName()
|
|
|
|
|
<< " --lidl /path/to/module.lidl --backend qt --impl-class Class --impl-header header.h [--output-dir /path]\n"
|
|
|
|
|
<< " " << QFileInfo(app.applicationFilePath()).fileName()
|
2026-06-12 22:10:16 -03:00
|
|
|
<< " --lidl /path/to/module.lidl --backend cdylib [--output-dir /path] (glue-only: C exports come from the module's own language backend)\n"
|
|
|
|
|
<< " " << QFileInfo(app.applicationFilePath()).fileName()
|
2026-03-31 16:29:35 -04:00
|
|
|
<< " --from-header src/impl.h --backend qt --impl-class Class --metadata metadata.json [--output-dir /path]\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
QString lidlPath = args.at(lidlIdx + 1);
|
|
|
|
|
|
|
|
|
|
// Provider glue mode: --backend qt --impl-class X --impl-header Y
|
|
|
|
|
const int backendIdx = args.indexOf("--backend");
|
|
|
|
|
if (backendIdx != -1) {
|
|
|
|
|
if (backendIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --backend requires an argument (e.g., qt)\n";
|
2026-03-23 11:45:25 -04:00
|
|
|
return 1;
|
|
|
|
|
}
|
2026-03-31 16:29:35 -04:00
|
|
|
QString backend = args.at(backendIdx + 1);
|
|
|
|
|
|
|
|
|
|
const int implClassIdx = args.indexOf("--impl-class");
|
|
|
|
|
const int implHeaderIdx = args.indexOf("--impl-header");
|
2026-06-12 22:10:16 -03:00
|
|
|
|
|
|
|
|
// Cdylib-from-LIDL (contract-first):
|
|
|
|
|
// - with no --impl-class: GLUE-ONLY — the C exports come from
|
|
|
|
|
// the module's own language backend (e.g. the Rust SDK's
|
|
|
|
|
// lidl-gen --provider); the glue only knows the C symbols.
|
|
|
|
|
// - with --impl-class/--impl-header: the FULL set — the C-ABI
|
|
|
|
|
// export wrapper around the named (hand-written, Qt-free)
|
|
|
|
|
// C++ impl class, plus the same uniform glue. The contract
|
|
|
|
|
// stays the .lidl; the author just implements the class.
|
|
|
|
|
if (backend == "cdylib") {
|
|
|
|
|
QFile f(lidlPath);
|
|
|
|
|
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
|
|
|
err << "Error: cannot read " << lidlPath << "\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
LidlParseResult pr = lidlParse(QString::fromUtf8(f.readAll()));
|
|
|
|
|
if (pr.hasError()) {
|
|
|
|
|
err << "Error parsing " << lidlPath << ": " << pr.error
|
|
|
|
|
<< " (line " << pr.errorLine << ")\n";
|
|
|
|
|
return 4;
|
|
|
|
|
}
|
|
|
|
|
const ModuleDecl& mod = pr.module;
|
|
|
|
|
QString cdErr;
|
|
|
|
|
if (!lidlCdylibSupported(mod, &cdErr)) {
|
|
|
|
|
err << "Error: module not cdylib-eligible: " << cdErr << "\n";
|
|
|
|
|
return 10;
|
|
|
|
|
}
|
|
|
|
|
QString genDirPath = outputDir.isEmpty()
|
|
|
|
|
? QDir::current().filePath("generated")
|
|
|
|
|
: outputDir;
|
|
|
|
|
QDir().mkpath(genDirPath);
|
|
|
|
|
struct Out { QString file; QString content; };
|
|
|
|
|
QList<Out> outs;
|
|
|
|
|
if (implClassIdx != -1) {
|
|
|
|
|
if (implClassIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --impl-class requires a class name\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
const QString implClass = args.at(implClassIdx + 1);
|
|
|
|
|
QString implHeader;
|
|
|
|
|
if (implHeaderIdx != -1 && implHeaderIdx + 1 < args.size())
|
|
|
|
|
implHeader = args.at(implHeaderIdx + 1);
|
|
|
|
|
else
|
2026-06-16 21:00:42 -03:00
|
|
|
implHeader = qs(mod.name) + "_impl.h";
|
2026-07-28 17:24:49 -03:00
|
|
|
outs.append({qs(mod.name) + "_types.h", lidlMakeTypesHeaderCdylib(mod)});
|
2026-06-16 21:00:42 -03:00
|
|
|
outs.append({qs(mod.name) + "_module_impl.cpp",
|
2026-06-12 22:10:16 -03:00
|
|
|
lidlMakeModuleImplExports(mod, implClass, implHeader)});
|
2026-06-16 21:00:42 -03:00
|
|
|
if (!mod.events.empty())
|
|
|
|
|
outs.append({qs(mod.name) + "_events_cdylib.cpp",
|
2026-06-12 22:10:16 -03:00
|
|
|
lidlMakeEventsSourceCdylib(mod, implClass, implHeader)});
|
|
|
|
|
} else {
|
|
|
|
|
err << "Error: the uniform cdylib Qt glue is generated by "
|
|
|
|
|
"logos-qt-generator (logos-qt-sdk); this tool emits "
|
|
|
|
|
"the Qt-free C-ABI export wrapper, which requires "
|
|
|
|
|
"--impl-class.\n";
|
|
|
|
|
return 12;
|
|
|
|
|
}
|
|
|
|
|
for (const Out& o : outs) {
|
|
|
|
|
const QString abs = QDir(genDirPath).filePath(o.file);
|
|
|
|
|
QFile of(abs);
|
|
|
|
|
if (!of.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
|
|
|
|
|
err << "Failed to write: " << abs << "\n";
|
|
|
|
|
return 11;
|
|
|
|
|
}
|
|
|
|
|
of.write(o.content.toUtf8());
|
|
|
|
|
out << "Generated: " << abs << "\n";
|
|
|
|
|
}
|
|
|
|
|
out.flush();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 16:29:35 -04:00
|
|
|
if (implClassIdx == -1 || implClassIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --backend " << backend << " requires --impl-class <ClassName>\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (implHeaderIdx == -1 || implHeaderIdx + 1 >= args.size()) {
|
|
|
|
|
err << "Error: --backend " << backend << " requires --impl-header <header.h>\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString implClass = args.at(implClassIdx + 1);
|
|
|
|
|
QString implHeader = args.at(implHeaderIdx + 1);
|
|
|
|
|
|
2026-06-12 22:10:16 -03:00
|
|
|
if (backend == "qt") {
|
|
|
|
|
err << "Error: Qt glue generation moved to logos-qt-generator "
|
|
|
|
|
"(logos-qt-sdk).\n";
|
|
|
|
|
return 6;
|
|
|
|
|
}
|
2026-03-31 16:29:35 -04:00
|
|
|
|
2026-06-12 22:10:16 -03:00
|
|
|
err << "Error: unsupported backend '" << backend << "' (supported: cdylib)\n";
|
2026-03-31 16:29:35 -04:00
|
|
|
return 1;
|
2026-03-23 11:45:25 -04:00
|
|
|
}
|
2026-03-31 16:29:35 -04:00
|
|
|
|
|
|
|
|
// Client stub mode (default)
|
|
|
|
|
bool moduleOnly = args.contains("--module-only");
|
|
|
|
|
return lidlGenerateClientStubs(lidlPath, outputDir, moduleOnly, out, err);
|
2026-03-23 11:45:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-31 16:29:35 -04:00
|
|
|
return legacy_main(argc, argv);
|
2025-09-30 14:56:47 -04:00
|
|
|
}
|