diff --git a/cpp-generator/CMakeLists.txt b/cpp-generator/CMakeLists.txt index 04f4f0b..e4a820f 100644 --- a/cpp-generator/CMakeLists.txt +++ b/cpp-generator/CMakeLists.txt @@ -20,9 +20,9 @@ find_package(logos-lidl REQUIRED) add_executable(logos-cpp-generator main.cpp + generator_lib.cpp + lidl_to_json.cpp legacy/main.cpp - legacy/generator_lib.cpp - legacy/lidl_to_json.cpp experimental/lidl_emit_common.cpp experimental/lidl_gen_client.cpp experimental/lidl_gen_cdylib.cpp @@ -58,7 +58,6 @@ endif() target_include_directories(logos-cpp-generator PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/legacy ${CMAKE_CURRENT_SOURCE_DIR}/experimental ${CMAKE_CURRENT_SOURCE_DIR}/../cpp ${LP_INCLUDE} diff --git a/cpp-generator/docs/project.md b/cpp-generator/docs/project.md index ee9d965..9cda6a8 100644 --- a/cpp-generator/docs/project.md +++ b/cpp-generator/docs/project.md @@ -4,14 +4,14 @@ ``` cpp-generator/ -├── main.cpp # Entry point — dispatches to legacy or experimental +├── main.cpp # Entry point — `--umbrella`/`--general-only` mode, dispatch to legacy or experimental ├── CMakeLists.txt # Build config ├── compile.sh # Standalone build script ├── metadata_dependencies.h # What a metadata.json `dependencies[]` array declares -├── legacy/ # Original generator (unchanged from master) +├── generator_lib.h/cpp # Shared emitter library: type mapping, wrapper + umbrella emission +├── lidl_to_json.h/cpp # ModuleDecl → the JSON surface generator_lib consumes +├── legacy/ # Original generator (plugin-introspection modes only) │ ├── main.cpp # legacy_main() — plugin/metadata modes -│ ├── generator_lib.h/cpp # Shared utilities, type mapping, header parser, umbrella emission -│ ├── lidl_to_json.h/cpp # ModuleDecl → the JSON surface generator_lib consumes │ └── legacy_main.h # Forward declaration ├── experimental/ # C++/Qt-specific generator backends │ ├── lidl_compat.h # Bridges the backends onto logos-lidl's std AST @@ -124,7 +124,7 @@ Emits the Qt-free half of a universal C++ cdylib module: - `lidlMakeModuleImplExports(...)` — the `logos_module_impl.h` C-ABI export wrapper around the universal impl class (compiled into the module's cdylib; dispatches via nlohmann::json) - `lidlMakeEventsSourceCdylib(...)` — typed `logos_events:` bodies marshalling into nlohmann::json -### Per-build API-style choice (`legacy/generator_lib.{h,cpp}`) +### Per-build API-style choice (`generator_lib.{h,cpp}`) The codegen exposes **one** wrapper class per module — `` — with signatures that match the API style picked at the consumer's build time. The two styles are mutually exclusive (no composite output): @@ -169,13 +169,13 @@ Read the array through `dependencyNames()` (`metadata_dependencies.h`) rather th - `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 `.lidl` sidecar via `--events-from`; when non-empty, the wrapper also gets one typed `on(callback)` adapter per declared event (callback arg types follow `apiStyle`). -- `makeUmbrellaHeaderFromDeps(deps, interfaceNames, apiStyle, originName)` / `makeUmbrellaSourceFromDeps(deps, interfaceNames)` — the `logos_sdk.{h,cpp}` aggregate above. They return the text; `legacy/main.cpp`'s `writeUmbrella*FromDeps` write it. That split is what lets the aggregate be asserted on directly, without a filesystem. +- `makeUmbrellaHeaderFromDeps(deps, interfaceNames, apiStyle, originName)` / `makeUmbrellaSourceFromDeps(deps, interfaceNames)` — the `logos_sdk.{h,cpp}` aggregate above. They return the text; `main.cpp`'s `runUmbrellaMode` writes it. That split is what lets the aggregate be asserted on directly, without a filesystem. Flag plumbing: 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 (`.headers-qt` and `.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 `_api.h` + `_api.cpp` pair regardless of style. +3. `parseApiStyleFlag()` in `generator_lib` parses `--api-style` once (rejecting the retired `std`); `main.cpp`'s `runUmbrellaMode` threads the resulting `ApiStyle` into `makeUmbrella*FromDeps`, and `legacy/main.cpp` threads it through `generateFromPlugin` / `writeUmbrellaHeader` (the QPluginLoader path). No per-style filenames are ever emitted; each module gets a single `_api.h` + `_api.cpp` pair regardless of style. ### Provider Generation (logos-qt-generator) diff --git a/cpp-generator/experimental/lidl_gen_client.cpp b/cpp-generator/experimental/lidl_gen_client.cpp index 799b568..76a3767 100644 --- a/cpp-generator/experimental/lidl_gen_client.cpp +++ b/cpp-generator/experimental/lidl_gen_client.cpp @@ -293,7 +293,7 @@ QString lidlMakeHeader(const ModuleDecl& module, BindMode bindMode) // followed by an optional Timeout. Both trailing and defaulted, so // existing call sites (including ones passing `&err` positionally) // compile unchanged. Mirrors the legacy emitter in - // legacy/generator_lib.cpp; the two must agree, since a consumer can + // generator_lib.cpp; the two must agree, since a consumer can // reach either (this one from a published `.lidl`, that one through the // module builder) for the same contract. if (!md.params.empty()) s << ", "; diff --git a/cpp-generator/experimental/lidl_gen_client.h b/cpp-generator/experimental/lidl_gen_client.h index 2b4a15c..814b4b6 100644 --- a/cpp-generator/experimental/lidl_gen_client.h +++ b/cpp-generator/experimental/lidl_gen_client.h @@ -2,7 +2,7 @@ #define LIDL_GEN_CLIENT_H #include "lidl_compat.h" -#include "../legacy/generator_lib.h" // BindMode +#include "../generator_lib.h" // BindMode #include #include diff --git a/cpp-generator/legacy/generator_lib.cpp b/cpp-generator/generator_lib.cpp similarity index 98% rename from cpp-generator/legacy/generator_lib.cpp rename to cpp-generator/generator_lib.cpp index fb16404..eaaf291 100644 --- a/cpp-generator/legacy/generator_lib.cpp +++ b/cpp-generator/generator_lib.cpp @@ -8,6 +8,40 @@ #include #include +bool parseApiStyleFlag(const QStringList& args, ApiStyle& outStyle, QTextStream& err) +{ + QString apiVal; + for (int i = 0; i < args.size(); ++i) { + const QString& a = args.at(i); + if (a == "--api-style") { + if (i + 1 < args.size()) apiVal = args.at(i + 1); + break; + } + if (a.startsWith("--api-style=")) { + apiVal = a.section('=', 1); + break; + } + } + 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 false; + } + if (apiVal == "lp") { + outStyle = ApiStyle::Lp; + return true; + } + if (!apiVal.isEmpty() && apiVal != "qt") { + err << "Unknown --api-style value: " << apiVal + << " (expected 'qt' or 'lp')\n"; + return false; + } + outStyle = ApiStyle::Qt; + return true; +} + QString toPascalCase(const QString& name) { QString out; diff --git a/cpp-generator/legacy/generator_lib.h b/cpp-generator/generator_lib.h similarity index 88% rename from cpp-generator/legacy/generator_lib.h rename to cpp-generator/generator_lib.h index 81a655c..a1bbbb8 100644 --- a/cpp-generator/legacy/generator_lib.h +++ b/cpp-generator/generator_lib.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,21 @@ // 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, diff --git a/cpp-generator/legacy/main.cpp b/cpp-generator/legacy/main.cpp index 6b27f4b..00a9d86 100644 --- a/cpp-generator/legacy/main.cpp +++ b/cpp-generator/legacy/main.cpp @@ -11,15 +11,13 @@ #include #include #include -#include #include #include #include "logos_provider_interface.h" -#include "generator_lib.h" -#include "metadata_dependencies.h" +#include "../generator_lib.h" +#include "../metadata_dependencies.h" #include "../experimental/lidl_compat.h" -#include "../experimental/impl_header_parser.h" -#include "lidl_to_json.h" // ModuleDecl -> the JSON surface generator_lib consumes +#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) @@ -60,182 +58,9 @@ static QJsonArray loadEventsFromLidl(const QString& lidlPath, QTextStream& err, return moduleEventsToJson(pr.module); } -// ── Dependency interfaces ─────────────────────────────────────────────────── -// -// An "interface dependency" is a method/event contract a consumer declares -// (in `metadata.json#interface_dependencies`) decoupled from any concrete -// module. The definition file is either a `.lidl` or a pure-C++ `.h` (the -// module's own language). The generator emits a BOUND wrapper class — the -// target module name is a runtime ctor argument, not baked in — so one -// interface can be bound to any module that satisfies it. - -// A single interface to generate a bound wrapper for. `path` is already -// resolved (nix resolves local `${src}/file` and remote `${input}/file` -// store paths and passes them via --interface; the generator never touches -// flake inputs). `implClass` is required for `.h` files, empty for `.lidl`. -struct InterfaceSpec { - QString name; // interface identifier → class/file name + bind_ - QString path; // resolved path to the .lidl / .h definition - QString implClass; // class inside a .h whose API defines the interface -}; - -// Parse all ` =[=]` (or `==...`) -// occurrences. Names and store paths contain no '=', so splitting on the -// first two '=' is unambiguous. Used for both `--interface` (runtime-bound -// wrappers) and `--dep` (name-baked wrappers generated from a dep's LIDL). -static QVector parseSpecFlags(const QStringList& args, const QString& flag) -{ - const QString flagEq = flag + "="; - QVector specs; - for (int i = 0; i < args.size(); ++i) { - QString value; - if (args.at(i) == flag && i + 1 < args.size()) { - value = args.at(i + 1); - } else if (args.at(i).startsWith(flagEq)) { - value = args.at(i).section('=', 1); - } else { - continue; - } - const int firstEq = value.indexOf('='); - if (firstEq <= 0) continue; // need at least name=path - InterfaceSpec spec; - spec.name = value.left(firstEq); - const int secondEq = value.indexOf('=', firstEq + 1); - if (secondEq < 0) { - spec.path = value.mid(firstEq + 1); - } else { - spec.path = value.mid(firstEq + 1, secondEq - firstEq - 1); - spec.implClass = value.mid(secondEq + 1); - } - specs.append(spec); - } - return specs; -} - -// Parse an interface definition file into a ModuleDecl. `.lidl` parses -// directly; `.h`/`.hpp` go through the impl-header parser, which needs a -// metadata.json — we feed it a synthetic one carrying only the interface -// name so the consumer's identity and events are NOT pulled in (the -// interface's events come solely from the file's own `logos_events:` block). -static bool parseInterfaceFile(const InterfaceSpec& spec, const QString& genDirPath, - ModuleDecl& outMod, QTextStream& err) -{ - QFileInfo fi(spec.path); - if (!fi.exists()) { - err << "Interface file not found for '" << spec.name << "': " << spec.path << "\n"; - return false; - } - const QString ext = fi.suffix().toLower(); - if (ext == "lidl") { - QFile f(spec.path); - if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { - err << "Failed to open interface file: " << spec.path << "\n"; - return false; - } - const QString src = QString::fromUtf8(f.readAll()); - f.close(); - LidlParseResult pr = lidlParse(src); - if (pr.hasError()) { - err << spec.path << ":" << pr.errorLine << ":" << pr.errorColumn - << ": " << pr.error << "\n"; - return false; - } - outMod = pr.module; - return true; - } - if (ext == "h" || ext == "hpp") { - if (spec.implClass.isEmpty()) { - err << "Interface '" << spec.name << "' is a C++ header but no impl_class was given " - << "(metadata.json interface_dependencies entry needs \"impl_class\")\n"; - return false; - } - // Synthetic minimal metadata: name only, no events — keeps the - // consumer's identity/events out of the interface. - const QString synthMeta = QDir(genDirPath).filePath("." + spec.name + "_iface_meta.json"); - { - QFile mf(synthMeta); - if (!mf.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { - err << "Failed to write temporary interface metadata: " << synthMeta << "\n"; - return false; - } - mf.write(QString("{\"name\":\"%1\"}").arg(spec.name).toUtf8()); - mf.close(); - } - ImplParseResult pr = parseImplHeader(spec.path, spec.implClass, synthMeta, err); - QFile::remove(synthMeta); - if (pr.hasError()) { - err << "Error parsing interface header " << spec.path << ": " << pr.error << "\n"; - return false; - } - outMod = pr.module; - return true; - } - err << "Unsupported interface file type for '" << spec.name << "': " << spec.path - << " (expected .lidl or .h)\n"; - return false; -} - -// Generate a wrapper (`_api.{h,cpp}`) per spec from its definition file. -// The wrapper class is named from the spec `name` (PascalCase), NOT the -// definition file's internal module name, so it matches the `#include` the -// umbrella header emits. `bindMode` picks the wrapper flavour: -// Bound — interface dependency: ctor takes a runtime module name; exposed -// via a `bind_(...)` factory on the umbrella. -// Static — concrete dependency: the module name is baked in; exposed as a -// `` member on the umbrella (byte-identical to the wrapper the -// dep's prebuilt headers used to ship). -static bool generateInterfaceWrappers(const QVector& ifaces, - const QString& genDirPath, ApiStyle apiStyle, - QTextStream& out, QTextStream& err, - BindMode bindMode = BindMode::Bound) -{ - for (const InterfaceSpec& spec : ifaces) { - ModuleDecl mod; - if (!parseInterfaceFile(spec, genDirPath, mod, err)) return false; - - { - QString recErr; - if (!lidlCheckRecords(mod, &recErr)) { - err << spec.path << ": " << recErr << "\n"; - return false; - } - } - - noteOptionalPositionalSlots(mod, spec.path, err); - - const QString className = toPascalCase(spec.name); - const QJsonArray methods = moduleMethodsToJson(mod); - const QJsonArray events = moduleEventsToJson(mod); - const QJsonArray records = moduleRecordsToJson(mod); - const QString headerRel = spec.name + "_api.h"; - const QString sourceRel = spec.name + "_api.cpp"; - - const QString header = makeHeader(spec.name, className, methods, apiStyle, events, bindMode, records); - const QString source = makeSource(spec.name, className, headerRel, methods, apiStyle, events, bindMode, records); - - { - QFile f(QDir(genDirPath).filePath(headerRel)); - if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { - err << "Failed to write wrapper header: " << headerRel << "\n"; - return false; - } - f.write(header.toUtf8()); - } - { - QFile f(QDir(genDirPath).filePath(sourceRel)); - if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { - err << "Failed to write wrapper source: " << sourceRel << "\n"; - return false; - } - f.write(source.toUtf8()); - } - out << "Generated " << (bindMode == BindMode::Bound ? "bound interface" : "dependency") - << " wrapper: " << headerRel << " (class " << className << ", " - << methods.size() << " methods, " << events.size() << " events)\n"; - } - out.flush(); - return true; -} +// The interface/dependency wrapper machinery (InterfaceSpec, parseSpecFlags, +// parseInterfaceFile, generateInterfaceWrappers) moved to ../main.cpp with the +// umbrella mode it exclusively serves — see the "Umbrella mode" block there. static QJsonArray enumerateMethods(QObject* moduleInstance) { @@ -342,24 +167,6 @@ static bool writeUmbrellaHeader(const QString& genDirPath, QTextStream& err) return true; } -static bool writeUmbrellaHeaderFromDeps(const QString& genDirPath, const QJsonArray& deps, const QStringList& interfaceNames, QTextStream& err, ApiStyle apiStyle = ApiStyle::Qt, const QString& originName = QString()) -{ - // Emission lives in generator_lib (makeUmbrellaHeaderFromDeps) next to the - // per-module wrapper emitters, so the aggregate can be asserted on without - // a filesystem; this writes what it returns. - QDir genDir(genDirPath); - const QString content = makeUmbrellaHeaderFromDeps(deps, interfaceNames, apiStyle, originName); - - 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 @@ -391,22 +198,11 @@ static bool writeUmbrellaSource(const QString& genDirPath, QTextStream& err) return true; } -static bool writeUmbrellaSourceFromDeps(const QString& genDirPath, const QJsonArray& deps, const QStringList& interfaceNames, QTextStream& err) -{ - // Emission lives in generator_lib (makeUmbrellaSourceFromDeps), alongside - // the header's; this writes what it returns. - QDir genDir(genDirPath); - const QString content = makeUmbrellaSourceFromDeps(deps, interfaceNames); - - 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 = {}) { @@ -543,49 +339,19 @@ int legacy_main(int argc, char* argv[]) // Parse --module-only option bool moduleOnly = args.contains("--module-only"); - // Parse --general-only option - bool generalOnly = args.contains("--general-only"); + // `--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 + // passed WITHOUT --metadata, which was never a mode — the plugin path + // below reports it as a missing plugin file, exactly as before. - // Parse --api-style option (qt | lp). Picks which type surface - // the generated `` 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 / 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. + // `--api-style qt|lp` — the type surface the generated `` wrapper + // exposes. The parser lives in generator_lib next to the ApiStyle enum + // because ../main.cpp's umbrella mode needs the identical answer; a second + // copy here is how the two surfaces would drift. ApiStyle apiStyle = ApiStyle::Qt; - { - QString apiVal; - for (int i = 0; i < args.size(); ++i) { - const QString& a = args.at(i); - if (a == "--api-style") { - if (i + 1 < args.size()) apiVal = args.at(i + 1); - break; - } - if (a.startsWith("--api-style=")) { - apiVal = a.section('=', 1); - break; - } - } - 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' or 'lp')\n"; - return 1; - } + if (!parseApiStyleFlag(args, apiStyle, err)) { + return 1; } // Support: extract dependencies from a metadata.json file @@ -625,124 +391,6 @@ int legacy_main(int argc, char* argv[]) const QJsonObject obj = doc.object(); const QJsonArray deps = obj.value("dependencies").toArray(); - // If --general-only provided, generate only the umbrella files. - // `LogosModules` exposes ONLY the modules listed in - // `metadata.json#dependencies` — apps that need to manage the - // core use liblogos' C API directly. - if (generalOnly) { - QString genDirPath = outputDir.isEmpty() ? QDir::current().filePath("logos-cpp-sdk/cpp/generated") : outputDir; - QDir().mkpath(genDirPath); - - // Collect interface dependencies. Primary source: --interface - // flags (nix resolves both local `${src}/file` and remote - // `${input}/file` store paths and passes them here, so the - // generator never touches flake inputs). Fallback: self-resolve - // LOCAL interface_dependencies entries (those without an - // `input`) from metadata.json, relative to the metadata dir — - // covers non-nix / source-tree builds. Flags win on collision. - // Dedup --interface flags by name and drop malformed specs: - // a repeated interface name would emit duplicate - // #include "_api.h" / bind_(...) into logos_sdk.h - // and fail to compile, and an empty name/path can only fail - // later in a less actionable way. - QVector ifaceSpecs; - QSet haveIface; - for (const InterfaceSpec& sp : parseSpecFlags(args, "--interface")) { - if (sp.name.isEmpty() || sp.path.isEmpty()) { - err << "Ignoring malformed --interface spec (empty name or path)\n"; - continue; - } - if (haveIface.contains(sp.name)) { - err << "Ignoring duplicate --interface '" << sp.name << "'\n"; - continue; - } - haveIface.insert(sp.name); - ifaceSpecs.append(sp); - } - - const QString metaDir = QFileInfo(metaResolvedPath).absolutePath(); - const QJsonArray ifaceDeps = obj.value("interface_dependencies").toArray(); - for (const QJsonValue& v : ifaceDeps) { - if (!v.isObject()) continue; - const QJsonObject eo = v.toObject(); - const QString name = eo.value("name").toString(); - if (name.isEmpty() || haveIface.contains(name)) continue; - // Entries with an `input` reference another repo (flake - // input); only nix can resolve those, via a --interface - // flag. If we reach here without a matching flag, skip. - if (eo.contains("input")) { - err << "Note: interface '" << name << "' has an 'input' (cross-repo) " - << "but no --interface flag was passed; skipping (nix supplies the path).\n"; - continue; - } - const QString file = eo.value("file").toString(); - if (file.isEmpty()) continue; - InterfaceSpec spec; - spec.name = name; - spec.path = QDir(metaDir).filePath(file); - spec.implClass = eo.value("impl_class").toString(); - ifaceSpecs.append(spec); - haveIface.insert(name); - } - - // Generate one bound wrapper (_api.{h,cpp}) per interface. - if (!ifaceSpecs.isEmpty()) { - if (!generateInterfaceWrappers(ifaceSpecs, genDirPath, apiStyle, out, err)) { - return 9; - } - } - - // Concrete dependencies generated from their published LIDL - // (`--dep =`). Same backend as interfaces but - // BindMode::Static — the module name is baked in and the dep is - // exposed as a `` MEMBER (the umbrella already emits it from - // `dependencies`, so no umbrella change). nix passes `--dep` only - // for deps that publish a `lidl` output; deps without one fall - // back to the header-copy path and are NOT passed here. Dedup vs - // each other and vs interface names. - QVector depSpecs; - QSet haveDep; - for (const InterfaceSpec& sp : parseSpecFlags(args, "--dep")) { - if (sp.name.isEmpty() || sp.path.isEmpty()) { - err << "Ignoring malformed --dep spec (empty name or path)\n"; - continue; - } - if (haveIface.contains(sp.name)) { - err << "Ignoring --dep '" << sp.name << "' (name already used by an interface)\n"; - continue; - } - if (haveDep.contains(sp.name)) { - err << "Ignoring duplicate --dep '" << sp.name << "'\n"; - continue; - } - haveDep.insert(sp.name); - depSpecs.append(sp); - } - if (!depSpecs.isEmpty()) { - if (!generateInterfaceWrappers(depSpecs, genDirPath, apiStyle, out, err, BindMode::Static)) { - return 9; - } - } - - QStringList interfaceNames; - for (const InterfaceSpec& sp : ifaceSpecs) interfaceNames.append(sp.name); - - // Generate umbrella headers based on dependencies + interfaces. - // 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(); - if (!writeUmbrellaHeaderFromDeps(genDirPath, deps, interfaceNames, err, apiStyle, originName)) { - return 7; - } - if (!writeUmbrellaSourceFromDeps(genDirPath, deps, interfaceNames, err)) { - return 8; - } - - out << "Generated logos_sdk.h and logos_sdk.cpp\n"; - out.flush(); - return 0; - } - // `--module-dir` (walk a directory of BUILT plugins and introspect // one per dependency) was removed. Every consumer wrapper is now // generated from a contract — `--dep =` inside the @@ -792,8 +440,8 @@ int legacy_main(int argc, char* argv[]) if (args.size() < 2) { err << "Usage: " << QFileInfo(app.applicationFilePath()).fileName() << " /absolute/path/to/plugin [--output-dir /path/to/output] [--module-only] [--events-from /path/to/.lidl]\n"; - err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /absolute/path/to/metadata.json [--output-dir /path/to/output] [--module-only] [--general-only]\n"; - err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /absolute/path/to/metadata.json --general-only [--output-dir /path/to/output]\n"; + err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /absolute/path/to/metadata.json [--output-dir /path/to/output] [--module-only]\n"; + err << " or: " << QFileInfo(app.applicationFilePath()).fileName() << " --metadata /absolute/path/to/metadata.json --umbrella (or --general-only) [--output-dir /path/to/output] [--api-style qt|lp] [--interface n=p] [--dep n=p.lidl]\n"; return 1; } diff --git a/cpp-generator/legacy/lidl_to_json.cpp b/cpp-generator/lidl_to_json.cpp similarity index 98% rename from cpp-generator/legacy/lidl_to_json.cpp rename to cpp-generator/lidl_to_json.cpp index 0902fd6..80af6fe 100644 --- a/cpp-generator/legacy/lidl_to_json.cpp +++ b/cpp-generator/lidl_to_json.cpp @@ -3,7 +3,7 @@ #include #include -#include "../experimental/lidl_emit_common.h" // lidlTypeToQt — the one Qt type mapper +#include "experimental/lidl_emit_common.h" // lidlTypeToQt — the one Qt type mapper // Convert a TypeExpr → Qt-typed string name (same surface the // metaobject-introspection path produces for methods, so generator_lib diff --git a/cpp-generator/legacy/lidl_to_json.h b/cpp-generator/lidl_to_json.h similarity index 98% rename from cpp-generator/legacy/lidl_to_json.h rename to cpp-generator/lidl_to_json.h index 9576549..b78716e 100644 --- a/cpp-generator/legacy/lidl_to_json.h +++ b/cpp-generator/lidl_to_json.h @@ -19,7 +19,7 @@ #include #include -#include "../experimental/lidl_compat.h" +#include "experimental/lidl_compat.h" // A TypeExpr -> the Qt type NAME the emitter keys off. One Qt type mapper: // this delegates to `lidlTypeToQt` (experimental/lidl_emit_common.cpp) rather diff --git a/cpp-generator/main.cpp b/cpp-generator/main.cpp index 4acbb38..c6e259c 100644 --- a/cpp-generator/main.cpp +++ b/cpp-generator/main.cpp @@ -1,4 +1,6 @@ #include "legacy/legacy_main.h" +#include "generator_lib.h" +#include "lidl_to_json.h" #include "experimental/lidl_gen_client.h" #include "experimental/lidl_gen_cdylib.h" #include "experimental/lidl_compat.h" @@ -8,7 +10,382 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include +#include + +// ─── Umbrella mode (`--umbrella`, alias `--general-only`) ──────────────────── +// +// Emits the umbrella — `logos_sdk.h` / `logos_sdk.cpp`, i.e. `struct +// LogosModules` — over a module's declared `metadata.json#dependencies` plus +// 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 +// 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. +// +// `--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 +// implementation of the mode and no second copy to drift. + +// A single interface to generate a bound wrapper for. `path` is already +// resolved (nix resolves local `${src}/file` and remote `${input}/file` +// store paths and passes them via --interface; the generator never touches +// flake inputs). `implClass` is required for `.h` files, empty for `.lidl`. +struct InterfaceSpec { + QString name; // interface identifier → class/file name + bind_ + QString path; // resolved path to the .lidl / .h definition + QString implClass; // class inside a .h whose API defines the interface +}; + +// Parse all ` =[=]` (or `==...`) +// occurrences. Names and store paths contain no '=', so splitting on the +// first two '=' is unambiguous. Used for both `--interface` (runtime-bound +// wrappers) and `--dep` (name-baked wrappers generated from a dep's LIDL). +static QVector parseSpecFlags(const QStringList& args, const QString& flag) +{ + const QString flagEq = flag + "="; + QVector specs; + for (int i = 0; i < args.size(); ++i) { + QString value; + if (args.at(i) == flag && i + 1 < args.size()) { + value = args.at(i + 1); + } else if (args.at(i).startsWith(flagEq)) { + value = args.at(i).section('=', 1); + } else { + continue; + } + const int firstEq = value.indexOf('='); + if (firstEq <= 0) continue; // need at least name=path + InterfaceSpec spec; + spec.name = value.left(firstEq); + const int secondEq = value.indexOf('=', firstEq + 1); + if (secondEq < 0) { + spec.path = value.mid(firstEq + 1); + } else { + spec.path = value.mid(firstEq + 1, secondEq - firstEq - 1); + spec.implClass = value.mid(secondEq + 1); + } + specs.append(spec); + } + return specs; +} + +// Parse an interface definition file into a ModuleDecl. `.lidl` parses +// directly; `.h`/`.hpp` go through the impl-header parser, which needs a +// metadata.json — we feed it a synthetic one carrying only the interface +// name so the consumer's identity and events are NOT pulled in (the +// interface's events come solely from the file's own `logos_events:` block). +static bool parseInterfaceFile(const InterfaceSpec& spec, const QString& genDirPath, + ModuleDecl& outMod, QTextStream& err) +{ + QFileInfo fi(spec.path); + if (!fi.exists()) { + err << "Interface file not found for '" << spec.name << "': " << spec.path << "\n"; + return false; + } + const QString ext = fi.suffix().toLower(); + if (ext == "lidl") { + QFile f(spec.path); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + err << "Failed to open interface file: " << spec.path << "\n"; + return false; + } + const QString src = QString::fromUtf8(f.readAll()); + f.close(); + LidlParseResult pr = lidlParse(src); + if (pr.hasError()) { + err << spec.path << ":" << pr.errorLine << ":" << pr.errorColumn + << ": " << pr.error << "\n"; + return false; + } + outMod = pr.module; + return true; + } + if (ext == "h" || ext == "hpp") { + if (spec.implClass.isEmpty()) { + err << "Interface '" << spec.name << "' is a C++ header but no impl_class was given " + << "(metadata.json interface_dependencies entry needs \"impl_class\")\n"; + return false; + } + // Synthetic minimal metadata: name only, no events — keeps the + // consumer's identity/events out of the interface. + const QString synthMeta = QDir(genDirPath).filePath("." + spec.name + "_iface_meta.json"); + { + QFile mf(synthMeta); + if (!mf.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + err << "Failed to write temporary interface metadata: " << synthMeta << "\n"; + return false; + } + mf.write(QString("{\"name\":\"%1\"}").arg(spec.name).toUtf8()); + mf.close(); + } + ImplParseResult pr = parseImplHeader(spec.path, spec.implClass, synthMeta, err); + QFile::remove(synthMeta); + if (pr.hasError()) { + err << "Error parsing interface header " << spec.path << ": " << pr.error << "\n"; + return false; + } + outMod = pr.module; + return true; + } + err << "Unsupported interface file type for '" << spec.name << "': " << spec.path + << " (expected .lidl or .h)\n"; + return false; +} + +// Generate a wrapper (`_api.{h,cpp}`) per spec from its definition file. +// The wrapper class is named from the spec `name` (PascalCase), NOT the +// definition file's internal module name, so it matches the `#include` the +// umbrella header emits. `bindMode` picks the wrapper flavour: +// Bound — interface dependency: ctor takes a runtime module name; exposed +// via a `bind_(...)` factory on the umbrella. +// Static — concrete dependency: the module name is baked in; exposed as a +// `` member on the umbrella (byte-identical to the wrapper the +// dep's prebuilt headers used to ship). +static bool generateInterfaceWrappers(const QVector& ifaces, + const QString& genDirPath, ApiStyle apiStyle, + QTextStream& out, QTextStream& err, + BindMode bindMode = BindMode::Bound) +{ + for (const InterfaceSpec& spec : ifaces) { + ModuleDecl mod; + if (!parseInterfaceFile(spec, genDirPath, mod, err)) return false; + + { + QString recErr; + if (!lidlCheckRecords(mod, &recErr)) { + err << spec.path << ": " << recErr << "\n"; + return false; + } + } + + noteOptionalPositionalSlots(mod, spec.path, err); + + const QString className = toPascalCase(spec.name); + const QJsonArray methods = moduleMethodsToJson(mod); + const QJsonArray events = moduleEventsToJson(mod); + const QJsonArray records = moduleRecordsToJson(mod); + const QString headerRel = spec.name + "_api.h"; + const QString sourceRel = spec.name + "_api.cpp"; + + const QString header = makeHeader(spec.name, className, methods, apiStyle, events, bindMode, records); + const QString source = makeSource(spec.name, className, headerRel, methods, apiStyle, events, bindMode, records); + + { + QFile f(QDir(genDirPath).filePath(headerRel)); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + err << "Failed to write wrapper header: " << headerRel << "\n"; + return false; + } + f.write(header.toUtf8()); + } + { + QFile f(QDir(genDirPath).filePath(sourceRel)); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + err << "Failed to write wrapper source: " << sourceRel << "\n"; + return false; + } + f.write(source.toUtf8()); + } + out << "Generated " << (bindMode == BindMode::Bound ? "bound interface" : "dependency") + << " wrapper: " << headerRel << " (class " << className << ", " + << methods.size() << " methods, " << events.size() << " events)\n"; + } + out.flush(); + return true; +} + +// The mode proper. `progName` is only used in the usage diagnostic. +static int runUmbrellaMode(const QStringList& args, const QString& progName, + QTextStream& out, QTextStream& err) +{ + // Some build drivers pass paths as `@/abs/path`. + auto stripAt = [](QString p) { if (p.startsWith('@')) p.remove(0, 1); return p; }; + + QString outputDir; + const int outDirIdx = args.indexOf("--output-dir"); + if (outDirIdx != -1 && outDirIdx + 1 < args.size()) { + outputDir = stripAt(args.at(outDirIdx + 1)); + } + + // `--api-style qt|lp` — the one parser, shared with legacy_main's plugin + // path (generator_lib.h, next to the ApiStyle enum). + ApiStyle apiStyle = ApiStyle::Qt; + if (!parseApiStyleFlag(args, apiStyle, err)) return 1; + + 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]" + " [--interface =[=]]" + " [--dep =]\n"; + return 1; + } + const QString metaPathArg = stripAt(args.at(metaIdx + 1)); + QFileInfo mfi(metaPathArg); + if (!mfi.exists()) { + err << "Metadata file does not exist: " << metaPathArg << "\n"; + return 2; + } + QString metaResolvedPath = mfi.canonicalFilePath(); + if (metaResolvedPath.isEmpty()) { + metaResolvedPath = mfi.absoluteFilePath(); + } + QFile mf(metaResolvedPath); + if (!mf.open(QIODevice::ReadOnly | QIODevice::Text)) { + err << "Failed to open metadata file: " << metaResolvedPath << "\n"; + return 3; + } + const QByteArray jsonData = mf.readAll(); + mf.close(); + QJsonParseError parseError; + const QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError); + if (parseError.error != QJsonParseError::NoError || !doc.isObject()) { + err << "Invalid metadata JSON in " << metaResolvedPath << ": " << parseError.errorString() << "\n"; + return 4; + } + const QJsonObject obj = doc.object(); + const QJsonArray deps = obj.value("dependencies").toArray(); + + // `LogosModules` exposes ONLY the modules listed in + // `metadata.json#dependencies` — apps that need to manage the core use + // liblogos' C API directly. + const QString genDirPath = outputDir.isEmpty() + ? QDir::current().filePath("logos-cpp-sdk/cpp/generated") + : outputDir; + QDir().mkpath(genDirPath); + + // Collect interface dependencies. Primary source: --interface flags (nix + // resolves both local `${src}/file` and remote `${input}/file` store paths + // and passes them here, so the generator never touches flake inputs). + // Fallback: self-resolve LOCAL interface_dependencies entries (those + // without an `input`) from metadata.json, relative to the metadata dir — + // covers non-nix / source-tree builds. Flags win on collision. + // Dedup --interface flags by name and drop malformed specs: a repeated + // interface name would emit duplicate #include "_api.h" / + // bind_(...) into logos_sdk.h and fail to compile, and an empty + // name/path can only fail later in a less actionable way. + QVector ifaceSpecs; + QSet haveIface; + for (const InterfaceSpec& sp : parseSpecFlags(args, "--interface")) { + if (sp.name.isEmpty() || sp.path.isEmpty()) { + err << "Ignoring malformed --interface spec (empty name or path)\n"; + continue; + } + if (haveIface.contains(sp.name)) { + err << "Ignoring duplicate --interface '" << sp.name << "'\n"; + continue; + } + haveIface.insert(sp.name); + ifaceSpecs.append(sp); + } + + const QString metaDir = QFileInfo(metaResolvedPath).absolutePath(); + const QJsonArray ifaceDeps = obj.value("interface_dependencies").toArray(); + for (const QJsonValue& v : ifaceDeps) { + if (!v.isObject()) continue; + const QJsonObject eo = v.toObject(); + const QString name = eo.value("name").toString(); + if (name.isEmpty() || haveIface.contains(name)) continue; + // Entries with an `input` reference another repo (flake input); only + // nix can resolve those, via a --interface flag. If we reach here + // without a matching flag, skip. + if (eo.contains("input")) { + err << "Note: interface '" << name << "' has an 'input' (cross-repo) " + << "but no --interface flag was passed; skipping (nix supplies the path).\n"; + continue; + } + const QString file = eo.value("file").toString(); + if (file.isEmpty()) continue; + InterfaceSpec spec; + spec.name = name; + spec.path = QDir(metaDir).filePath(file); + spec.implClass = eo.value("impl_class").toString(); + ifaceSpecs.append(spec); + haveIface.insert(name); + } + + // Generate one bound wrapper (_api.{h,cpp}) per interface. + if (!ifaceSpecs.isEmpty()) { + if (!generateInterfaceWrappers(ifaceSpecs, genDirPath, apiStyle, out, err)) { + return 9; + } + } + + // Concrete dependencies generated from their published LIDL + // (`--dep =`). Same backend as interfaces but BindMode::Static + // — the module name is baked in and the dep is exposed as a `` MEMBER + // (the umbrella already emits it from `dependencies`, so no umbrella + // change). nix passes `--dep` only for deps that publish a `lidl` output; + // deps without one fall back to the header-copy path and are NOT passed + // here. Dedup vs each other and vs interface names. + QVector depSpecs; + QSet haveDep; + for (const InterfaceSpec& sp : parseSpecFlags(args, "--dep")) { + if (sp.name.isEmpty() || sp.path.isEmpty()) { + err << "Ignoring malformed --dep spec (empty name or path)\n"; + continue; + } + if (haveIface.contains(sp.name)) { + err << "Ignoring --dep '" << sp.name << "' (name already used by an interface)\n"; + continue; + } + if (haveDep.contains(sp.name)) { + err << "Ignoring duplicate --dep '" << sp.name << "'\n"; + continue; + } + haveDep.insert(sp.name); + depSpecs.append(sp); + } + if (!depSpecs.isEmpty()) { + if (!generateInterfaceWrappers(depSpecs, genDirPath, apiStyle, out, err, BindMode::Static)) { + return 9; + } + } + + QStringList interfaceNames; + for (const InterfaceSpec& sp : ifaceSpecs) interfaceNames.append(sp.name); + + // The umbrella itself. Emission lives in generator_lib next to the + // per-module wrapper emitters, so the aggregate can be asserted on without + // a filesystem (tests/generator/test_make_umbrella.cpp); this only writes + // 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(); + const QDir genDir(genDirPath); + { + 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 7; + } + outFile.write(makeUmbrellaHeaderFromDeps(deps, interfaceNames, apiStyle, originName).toUtf8()); + outFile.close(); + } + { + 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 8; + } + outFile.write(makeUmbrellaSourceFromDeps(deps, interfaceNames).toUtf8()); + outFile.close(); + } + + out << "Generated logos_sdk.h and logos_sdk.cpp\n"; + out.flush(); + return 0; +} int main(int argc, char* argv[]) { @@ -17,11 +394,32 @@ int main(int argc, char* argv[]) bool hasLidl = false; bool hasFromHeader = false; bool hasHeaderToLidl = false; + bool hasUmbrella = false; + bool hasGeneralOnly = false; + bool hasMetadata = false; for (int i = 1; i < argc; ++i) { QString arg = QString::fromUtf8(argv[i]); if (arg == "--lidl") hasLidl = true; if (arg == "--from-header") hasFromHeader = true; if (arg == "--header-to-lidl") hasHeaderToLidl = true; + if (arg == "--umbrella") hasUmbrella = true; + if (arg == "--general-only") hasGeneralOnly = true; + if (arg == "--metadata") hasMetadata = true; + } + + // 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 + // `--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. + if (hasUmbrella || (hasGeneralOnly && hasMetadata)) { + QCoreApplication app(argc, argv); + QTextStream err(stderr); + QTextStream out(stdout); + return runUmbrellaMode(app.arguments(), + QFileInfo(app.applicationFilePath()).fileName(), + out, err); } // --header-to-lidl: the C++ frontend of the source -> LIDL -> bindings diff --git a/tests/experimental/test_lidl_gen_client.cpp b/tests/experimental/test_lidl_gen_client.cpp index d7012f6..92df1d0 100644 --- a/tests/experimental/test_lidl_gen_client.cpp +++ b/tests/experimental/test_lidl_gen_client.cpp @@ -489,7 +489,7 @@ TEST(LidlGenClient, BytesTagCollisionIsRefusedThroughAnOptional) // --------------------------------------------------------------------------- // Sync timeout + result-carrying async // -// This emitter and legacy/generator_lib.cpp produce the SAME consumer surface +// This emitter and cpp-generator/generator_lib.cpp produce the SAME consumer surface // for the same contract — one is reached from a published `.lidl`, the other // through the module builder — so the two must agree. tests/generator/ // test_async_result.cpp holds the legacy twin of these assertions. diff --git a/tests/generator/CMakeLists.txt b/tests/generator/CMakeLists.txt index a2b4e27..19681d1 100644 --- a/tests/generator/CMakeLists.txt +++ b/tests/generator/CMakeLists.txt @@ -5,8 +5,8 @@ find_package(logos-lidl REQUIRED) add_executable(generator_tests - ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator/legacy/generator_lib.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator/legacy/lidl_to_json.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator/generator_lib.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator/lidl_to_json.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator/experimental/lidl_emit_common.cpp test_to_pascal_case.cpp test_normalize_type.cpp @@ -23,7 +23,6 @@ add_executable(generator_tests target_include_directories(generator_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator - ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator/legacy ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp-generator/experimental ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp )