mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 09:41:06 +00:00
* Extract the protocol layer into logos-protocol; consume it as a flake input
The transport/token/IPC layer (transports incl. QRO + plain TCP/TLS,
consumer core LogosAPIClient/LogosAPIConsumer with the capability
auto-requestModule flow, ModuleProxy, token manager, QVariant<->JSON
conversion, the abstract LogosProviderObject interface) now lives in the
logos-protocol repo behind the versioned lp_* C ABI.
This SDK keeps the typed C++ developer layer (LogosAPI, provider base
classes + Qt provider glue, module context, code generator) and still
compiles the protocol sources INTO liblogos_sdk.a from the flake input,
so the installed artifact (archive symbols, include/ + include/cpp
layouts, cmake config) stays byte-compatible: existing consumers need
no changes. Public headers are unchanged; logos_provider_object.h keeps
its name and now re-exports the abstract interface from
logos_provider_interface.h.
Transport/protocol component tests moved to logos-protocol with the
code; the remaining sdk/generator/experimental suites are unchanged
(432/432 green against the local protocol checkout).
* lock: add logos-protocol input
* Make the base SDK Qt-free: move the Qt developer layer to logos-qt-sdk
LogosAPI, LogosAPIProvider, LogosProviderBase/LOGOS_PROVIDER macros, the
QObject provider glue (QtProviderObject) and the legacy PluginInterface
(core/interface.h) move to the new logos-qt-sdk repo. The protocol
sources are no longer compiled into a monolithic archive — consumers
link logos-qt-sdk (which layers on logos-protocol) instead.
What remains here is header-only std C++: logos_module_context.h,
logos_result.h (StdLogosResult), logos_json.h — exported as the CMake
INTERFACE target logos-cpp-sdk::logos_headers — plus the code generator
(a build-time tool; its introspection mode now includes
logos_provider_interface.h from logos-protocol, where
LogosProviderPlugin moved).
Mechanically verified Qt-free: the logos-cpp-lib / logos-cpp-include
closures contain only nlohmann_json. Tests: 245/245 (module-context std
suite + generator + experimental).
* Cdylib authoring backend: --backend cdylib emits the common C ABI wrapper + uniform Qt glue
From a module's LIDL contract the generator now emits:
- <name>_module_impl.cpp — the Qt-FREE logos_module_impl.h export
wrapper (dispatch/get_methods/set_context/set_emit_callback/
accept_token/get_protocol_version/string_free) around the universal
impl class; compiled into the module's cdylib. Tagged {"_bytes"}
bytes, StdLogosResult -> {success,value,error}, context via the
existing _logos_codegen_::maybeSet* SFINAE helpers.
- <name>_events_cdylib.cpp — typed logos_events: bodies marshalling
into nlohmann::json (the cdylib flavor of the events sidecar).
- <name>_cdylib_glue.{h,cpp} — the UNIFORM Qt-plugin glue forwarding
LogosProviderObject to the C symbols; identical regardless of the
module's source language (the Rust SDK emits the same exports).
- the .lidl sidecar.
Qt-container types are rejected at generation time (a cdylib impl is
Qt-free by definition). Verified end-to-end by dlopen smoke: typed
dispatch, typed events through the emit callback, result returns,
introspection, and the protocol-version handshake — and the SAME C
harness passes against a Rust cdylib generated by logos-lidl-gen
--provider.
* fix: accept the installed source-export layout in the protocol-root check
The fail-fast only tested <root>/cpp/logos_protocol.h, but the LP_SRC
selection right below (and the error message itself) support the
installed export layout <root>/include/cpp as well. Pointing
LOGOS_PROTOCOL_ROOT at an installed export tripped the FATAL_ERROR
before that fallback could apply.
Caught by Copilot review on #82.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* lock: pin logos-protocol to the qt-free-split branch head
The Qt-free SDK (and the cdylib backend stacked on it) reference
LogosProviderPlugin from protocol's logos_provider_interface.h, which
lands on feat/qt-free-split — the P1-branch pin no longer compiles
standalone. Temporary — drop when the chain PRs merge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* lock: pin logos-protocol to the qt-free-split branch head
The Qt-free SDK (and the cdylib backend stacked on it) reference
LogosProviderPlugin from protocol's logos_provider_interface.h, which
lands on feat/qt-free-split — the P1-branch pin no longer compiles
standalone. Temporary — drop when the chain PRs merge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* doctest: pin the logoscore runtime via its {release} placeholder
The spec built logoscore-cli at bare master with only the cpp-sdk inputs
overridden — master's stack cannot compile against the qt-free SDK, so
the suite failed on the chain branches. With the placeholder, CI's
--release-for pins expand it to the workspace's logoscore commit (and
local runs without a pin still fall back to master, unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* doctest: override the nested module builders to {release} too
capability_module (via logoscore's lock) and the cloned accounts module
resolve module-builder from their own locks — pre-split revs whose
LogosModule.cmake still detects the SDK by logos_api.h, which the
qt-free SDK no longer ships ('logos-cpp-sdk not found'). Overriding the
builder itself to the workspace-pinned chain rev (keeping the nested
cpp-sdk override) builds both modules with the split-aware builder.
Verified end-to-end locally with the exact doctest command.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* doctest: apply the {release} + nested-builder overrides to all three specs
The runtime spec got the treatment in 210eea1; the composition and
worker-thread specs have the same logoscore/module build commands and
failed identically (pre-split builders from the modules' own locks).
All executed run: blocks now pin logoscore-cli{release} and override
the nested module builders to logos-module-builder{release}; the
displayed code_block: variants stay in their generic master form.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* generator: glue-only cdylib mode for non-C++ impls
--lidl x.lidl --backend cdylib (no --impl-class) emits just the uniform
Qt-plugin glue; the C exports come from the module's own language
backend (e.g. the Rust SDK's lidl-gen --provider). This is the entry
point logos-module-builder's cdylib interface uses for Rust modules.
* cdylib glue: root plugin implements PluginInterface
logos_host's module_initializer hard-requires PluginInterface on the
root plugin before any provider detection — same bases as the qt glue
(QObject, PluginInterface, LogosProviderPlugin). Caught by the first
host-loaded run of a cdylib-authored module; the dlopen smoke harness
exercised only the C seam.
* cdylib glue: seed the cdylib's protocol stack with the host auth token
The glue's init() reads the authToken property module_initializer now
surfaces on the LogosAPI object and forwards it across
logos_module_accept_token under the initializer's own keys
(core / capability_module). Without it the cdylib's TokenManager (a
separate static copy of the singleton) is empty and every outbound
call — including the capability requestModule bootstrap — is rejected
as unauthorized.
* cdylib glue: provider derives LogosProviderBase; tokens land in BOTH stacks
informModuleToken = base save (host-stack TokenManager — what ModuleProxy
validates inbound calls against, incl. the grant the daemon pushes after
a capability requestModule) + C-ABI forward (the cdylib's own stack, for
outbound auth). init folds into onInit; the authToken property seeding
stays.
* lock: protocol integer-fidelity fix
* codegen: typed wrappers throw on call failure; dispatch catches escapes
Generated sync client wrappers call the new err-out invokeRemoteMethod
overload and throw logos::LogosCallError when the call fails (e.g. the
bound module is missing) — previously the empty QVariant silently
degraded to the return type's default and a caller could not tell
failure from a legitimate 0 / "". Both generators (legacy + LIDL),
both API styles. Async paths unchanged.
Generated provider dispatch (universal qt glue + LOGOS_PROVIDER) wraps
the method body in a catch-all that logs and returns an invalid QVariant
— an escaped exception becomes an ordinary METHOD_FAILED instead of
unwinding through Qt event dispatch and killing the module process.
* fix: restore a clean flake.lock after the merge conflict (protocol 176fbc8)
* codegen: CallError out-param instead of throwing wrappers
Per review, the generated sync wrappers expose the error channel as an
optional trailing parameter — add(a, b, &err) — rather than throwing:
explicit, stateless, works on temporaries, and existing call sites
compile unchanged (they keep default-on-failure, now with a qWarning so
failures are visible in the module log). The dispatch catch-all from the
previous commit stays: it contains author exceptions, it doesn't
introduce any.
* generator: contract-first C++ cdylib modules from --lidl
--lidl x.lidl --backend cdylib with --impl-class/--impl-header now emits
the FULL set (C-ABI export wrapper + events + uniform glue) around the
named hand-written Qt-free impl class — the C++ mirror of declaring the
contract in .lidl and implementing the Rust trait. Without --impl-class
the glue-only mode is unchanged.
* glue: fire onContextReady AFTER modules()/event wiring
The generated onInit set the context (which fires the impl's
onContextReady hook) before constructing the LogosModules aggregate and
wiring typed event emission — so an impl doing its documented one-time
setup there (typed dependency calls, event subscriptions) dereferenced
a null aggregate and crashed the module process (signal 11). Found by
the first module to subscribe to a dependency's typed event from
onContextReady. Context now goes last.
* cdylib: fire the context-ready hook at module LOAD, not first dispatch
The impl-exports wrapper used to fire maybeSetContext (stamp + hook)
immediately inside logos_module_set_context — which the glue calls during
onInit, BEFORE the auth token is seeded and BEFORE ModuleProxy wires the
emit callback. A hook that made outbound calls or emitted events ran
half-wired; and the Rust scaffold deferred its hook to first dispatch
entirely, so a Rust module couldn't subscribe/emit/act until someone
called it — a capability gap vs the universal C++ path.
Two changes, applied uniformly:
- The glue's onInit now seeds the auth token FIRST and forwards the
context LAST (the same context-last convention as the universal onInit
ordering fix).
- The impl-exports gained a ready-latch (lidlTryFireContext): the context
is stored on set_context and the hook fires ONCE as soon as both the
context AND the emit callback have been delivered — during module
registration, before the module is published for inbound calls. Hosts
that never wire an emit callback still get the hook before the first
dispatch (requireEmit=false fallback in logos_module_dispatch).
The Rust scaffold (logos-rust-sdk lidl-gen) implements the same latch, so
on_context_ready now matches C++ onContextReady semantics: subscriptions,
authenticated outbound calls and typed emission all work from the hook at
load time.
* ci: run workflows on stacked PRs + workflow_dispatch
Both workflows filtered pull_request to master-based PRs, so stacked PRs
(feat/qt-free-sdk -> feat/extract-logos-protocol, feat/cdylib-authoring
-> feat/qt-free-sdk) ran NO checks at all. Drop the base-branch filter
for pull_request and add workflow_dispatch for manual runs. Same fix as
logos-module-builder 232b8a2.
* generator: --backend ui — universal authoring for UI plugin backends
New emitters (lidl_gen_ui.{h,cpp}) for type=ui_qml + interface=universal
modules: from the author's single clean impl class (optionally deriving
LogosModuleContext), generate
<name>.rep — the view contract, one SLOT per public method
(framework hooks like onContextReady excluded)
<name>_ui_interface.h — PluginInterface subclass + IID
<name>_ui_glue.{h,cpp}— plugin deriving <Cls>SimpleSource +
<Cls>Interface + <Cls>ViewPluginBase; slots
forward to the impl (std<->Qt at the boundary);
Q_INVOKABLE initLogos(LogosAPI*) builds
LogosModules, wires modules(), stamps context
(fires onContextReady — same order as the
universal core glue), then setBackend(this).
Typed dependency callers, typed event subscriptions and bind_<interface>
binders come from the existing umbrella pass (logos_sdk.h), which already
runs for UI modules. ui-host's reflection-based initLogos call site is
unchanged; legacy LogosAPI*-based UI plugins are untouched. v1 view API
types: void/int/uint/float64/bool/string; logos_events: rejected for ui
backends (views talk to QML via .rep, not module events).
* generator: distribute the LIDL frontend for external generators
First step of moving ALL Qt glue emission out of this repo into
logos-qt-sdk's logos-qt-generator (cpp-sdk's generator keeps only the
Qt-free outputs: std typed wrappers, logos_sdk umbrella, cdylib
impl-exports, LIDL derivation).
- Shared emit helpers (lidlToPascalCase, lidlTypeToQt, lidlTypeToStd,
lidlIsStdConvertible) move to a new lidl_emit_common.{h,cpp} unit, used
by both generators.
- The frontend set (AST, lexer, parser, serializer, validator,
impl-header parser, emit-common) is installed under
share/lidl-frontend/ — the qt generator compiles these sources in
directly, so the two tools share one frontend without a binary ABI.
* generator: Qt glue emission removed — logos-qt-generator owns it
The deletion half of the generator split (counterpart: logos-qt-sdk
3b37474, builder d7a2272). This tool now emits ONLY Qt-free outputs:
kept std typed wrappers + logos_sdk umbrella (--general-only),
cdylib C-ABI impl-exports + typed event emitters
(--lidl/--from-header --backend cdylib --impl-class),
LIDL derivation/serialization (--header-to-lidl), client stubs
removed universal Qt glue (--backend qt), the uniform cdylib Qt glue,
the ui backend emitters — all relocated verbatim (byte-identical
output verified) to logos-qt-generator; invocations here now
fail with a pointer to the right tool
The provider-glue golden tests travel with the emitters (to be re-homed
in logos-qt-sdk's test suite); test_lidl_type_mapping stays — it covers
lidl_emit_common, which both generators compile.
* lock: protocol at the typed-requestModule port (3de5398)
* lock: protocol at the typed-requestModule port (3de5398)
* ci: chain pins for the doc-tests (drop at merge)
In repo CI only cpp-sdk's {release} is the commit under test —
logoscore-cli and module-builder expanded to master, which doesn't link
against the chain SDK the specs override in ('Build the CLI with the SDK
override' failed on every run since the stacked-PR triggers were
enabled). Pin both to the extraction-chain heads; the workspace pipeline
is unaffected (it pins every repo itself).
* generator: distribute the LIDL frontend for external generators
First step of moving ALL Qt glue emission out of this repo into
logos-qt-sdk's logos-qt-generator (cpp-sdk's generator keeps only the
Qt-free outputs: std typed wrappers, logos_sdk umbrella, cdylib
impl-exports, LIDL derivation).
- Shared emit helpers (lidlToPascalCase, lidlTypeToQt, lidlTypeToStd,
lidlIsStdConvertible) move to a new lidl_emit_common.{h,cpp} unit, used
by both generators.
- The frontend set (AST, lexer, parser, serializer, validator,
impl-header parser, emit-common) is installed under
share/lidl-frontend/ — the qt generator compiles these sources in
directly, so the two tools share one frontend without a binary ABI.
* docs: note the generator split (Qt glue emission lives in logos-qt-sdk)
* lock: protocol#3 merged — pin advances to protocol master
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
247 lines
15 KiB
Markdown
247 lines
15 KiB
Markdown
# Logos Code Generator — Project Description
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
cpp-generator/
|
|
├── main.cpp # Entry point — dispatches to legacy or experimental
|
|
├── CMakeLists.txt # Build config
|
|
├── compile.sh # Standalone build script
|
|
├── legacy/ # Original generator (unchanged from master)
|
|
│ ├── main.cpp # legacy_main() — plugin/metadata/provider-header modes
|
|
│ ├── generator_lib.h/cpp # Shared utilities, type mapping, header parser
|
|
│ └── legacy_main.h # Forward declaration
|
|
├── experimental/ # New LIDL + impl-header generator
|
|
│ ├── lidl_ast.h # AST types (TypeExpr, ModuleDecl, MethodDecl, etc.)
|
|
│ ├── lidl_lexer.h/cpp # LIDL tokenizer
|
|
│ ├── lidl_parser.h/cpp # LIDL recursive descent parser
|
|
│ ├── lidl_validator.h/cpp # Semantic validation
|
|
│ ├── lidl_serializer.h/cpp # AST → LIDL text pretty-printer
|
|
│ ├── lidl_gen_client.h/cpp # Client stub generation + helpers
|
|
│ ├── (Qt glue emitters live in logos-qt-sdk's logos-qt-generator)
|
|
│ └── impl_header_parser.h/cpp # C++ header → ModuleDecl parser
|
|
└── docs/ # This documentation
|
|
```
|
|
|
|
## Components
|
|
|
|
### Entry Point (`main.cpp`)
|
|
|
|
Checks for `--from-header` or `--lidl` flags before creating `QCoreApplication`. If neither is present, falls through to `legacy_main()`.
|
|
|
|
### AST (`lidl_ast.h`)
|
|
|
|
Shared data model used by all pipelines:
|
|
|
|
- **`TypeExpr`** — type expression with `Kind` (Primitive, Array, Map, Optional, Named), `name`, and `elements`
|
|
- **`ParamDecl`** — parameter name + type
|
|
- **`MethodDecl`** — method name, params, return type, `description` (doc comment above the declaration, emitted into `getMethods()`), `jsonReturn` flag (true when impl returns `LogosMap`/`LogosList`)
|
|
- **`EventDecl`** — event name, params, `description` (doc comment above the `logos_events:` declaration, emitted as a `type: "event"` entry inside `getMethods()`)
|
|
- **`FieldDecl`** — struct field name, type, optional flag
|
|
- **`TypeDecl`** — named struct type with fields
|
|
- **`ModuleDecl`** — complete module: name, version, description, category, depends, types, methods, events
|
|
|
|
All types have `operator==` for testing.
|
|
|
|
### Lexer (`lidl_lexer.h/cpp`)
|
|
|
|
Tokenizes LIDL source. Token types: `Module`, `TypeKw`, `Method`, `Event`, `Version`, `Description`, `Category`, `Depends`, `Ident`, `StringLit`, symbols (`{`, `}`, `(`, `)`, `[`, `]`, `:`, `,`, `->`, `?`), `Eof`, `Error`. Tracks line/column for error reporting.
|
|
|
|
### Parser (`lidl_parser.h/cpp`)
|
|
|
|
Recursive descent parser. Grammar:
|
|
|
|
```
|
|
module = "module" IDENT "{" body "}"
|
|
body = (metadata | type_def | method_def | event_def)*
|
|
metadata = "version" STRING | "description" STRING | "category" STRING
|
|
| "depends" "[" (IDENT ("," IDENT)*)? "]"
|
|
type_def = "type" IDENT "{" field* "}"
|
|
field = "?"? IDENT ":" type_expr
|
|
method_def = "method" IDENT "(" params ")" "->" type_expr
|
|
event_def = "event" IDENT "(" params ")"
|
|
params = (IDENT ":" type_expr ("," IDENT ":" type_expr)*)?
|
|
type_expr = IDENT | "[" type_expr "]" | "{" type_expr ":" type_expr "}"
|
|
| "?" type_expr
|
|
```
|
|
|
|
### Validator (`lidl_validator.h/cpp`)
|
|
|
|
Checks: empty module name, duplicate type/method/event names, builtin type shadowing, unknown named type references, duplicate parameter names within methods.
|
|
|
|
### Serializer (`lidl_serializer.h/cpp`)
|
|
|
|
Converts `ModuleDecl` back to LIDL text. Used for roundtrip testing (parse → serialize → parse → compare).
|
|
|
|
### Type Mapping (`lidl_gen_client.h/cpp`)
|
|
|
|
- `lidlTypeToQt(TypeExpr)` — maps LIDL types to Qt type strings
|
|
- `lidlToPascalCase(name)` — converts `snake_case` to `PascalCase`
|
|
- `lidlMakeHeader(ModuleDecl)` — generates client API header
|
|
- `lidlMakeSource(ModuleDecl)` — generates client API source
|
|
- `lidlGenerateMetadataJson(ModuleDecl)` — generates metadata.json content
|
|
|
|
### Per-build API-style choice (`legacy/generator_lib.{h,cpp}`)
|
|
|
|
The codegen exposes **one** wrapper class per module — `<Module>` — with signatures that match the API style picked at the consumer's build time. The two styles are mutually exclusive (no composite output):
|
|
|
|
| `--api-style` | Wrapper signatures |
|
|
|---|---|
|
|
| `qt` (default) | `QString` / `QStringList` / `QVariantList` / `QVariantMap` / `int` / `LogosResult` |
|
|
| `std` | `std::string` / `std::vector<std::string>` / `LogosMap` / `LogosList` / `int64_t` / `StdLogosResult` |
|
|
|
|
Both styles emit:
|
|
|
|
- A `<Module>` client class with sync method shapes + matching `<method>Async(...)` overloads.
|
|
- The std variant additionally inlines Qt↔std conversion in its `.cpp` so the caller's translation unit needs zero Qt headers.
|
|
|
|
The umbrella `logos_sdk.h` is also generated per-build and aggregates every dep into a flat `LogosModules` struct — no nested view:
|
|
|
|
```cpp
|
|
struct LogosModules {
|
|
LogosAPI* api;
|
|
SomeDep some_dep; // one accessor per `metadata.json#dependencies` entry
|
|
// ...
|
|
};
|
|
```
|
|
|
|
Only the modules explicitly listed as dependencies are exposed. The runtime's `core_manager` is intentionally NOT in `LogosModules` — apps that need to manage the core do so via liblogos' C API, not via a typed RPC wrapper.
|
|
|
|
`ApiStyle` enum + new helpers in `generator_lib`:
|
|
|
|
- `enum class ApiStyle { Qt, Std }` — passed to every wrapper-emitting function.
|
|
- File-local `mapParamTypeStd` / `mapReturnTypeStd` / `stdParamToQVariant` / `qVariantToStdReturn` — std-side type-mapping + Qt↔std conversion expressions. 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 `<name>.lidl` sidecar via `--events-from`; when non-empty, the wrapper also gets one typed `on<EventName>(callback)` adapter per declared event (callback arg types follow `apiStyle`). The std-style wrapper grows the necessary `ensureReplica()` plumbing on demand.
|
|
|
|
Flag plumbing:
|
|
|
|
1. `metadata.json#interface == "universal"` → `mkLogosModule.nix` adds `-DLOGOS_API_STYLE=std` 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 (`<name>.headers-qt` and `<name>.headers-std`) 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 and threads the resulting `ApiStyle` through `generateFromPlugin`, `writeUmbrellaHeader{,FromDeps}`. No `_api_std.{h,cpp}` files are ever emitted; each module gets a single `<name>_api.h` + `<name>_api.cpp` pair regardless of style.
|
|
|
|
### Provider Generation (`lidl_gen_provider.h/cpp`)
|
|
|
|
- `lidlTypeToStd(TypeExpr)` — maps LIDL types to C++ std type strings
|
|
- `lidlIsStdConvertible(TypeExpr)` — checks if a type has a pure C++ representation
|
|
- `lidlMakeProviderHeader(ModuleDecl, implClass, implHeader)` — generates Qt glue header
|
|
- Emits `nlohmannToQVariant()` helper when any method has `jsonReturn = true`
|
|
- Always emits an `onInit(LogosAPI*) override` that, via SFINAE'd helpers in `logos_module_context.h`, (a) copies the three runtime-injected properties (`modulePath`, `instanceId`, `instancePersistencePath`) into the impl, (b) constructs a per-module `LogosModules` aggregate and threads its pointer through the same base, and (c) installs the typed-event callback (`maybeSetEmitEvent`) consumed by `<name>_events.cpp` method bodies. Impls that don't inherit `LogosModuleContext` compile unchanged — the helper overloads collapse to no-ops. The full `LogosAPI` is never exposed past the provider boundary.
|
|
- Always emits `#include "logos_sdk.h"` and a `std::unique_ptr<LogosModules> m_logosModules` member; ownership lives on the provider, the context base sees only a non-owning `void*` reinterpreted in `LogosModuleContext::modules()` (which depends on the impl's TU having included `logos_sdk.h`).
|
|
- `lidlMakeProviderDispatch(ModuleDecl)` — generates callMethod/getMethods dispatch. `getMethods()` emits the full interface: each method tagged `type: "method"`, then each `module.events` entry tagged `type: "event"` (name, signature, parameters, escaped `description`; no returnType/isInvokable). There is no separate `getEvents()` — folding events into `getMethods()` keeps the provider vtable ABI-stable.
|
|
- `lidlMakeEventsSource(ModuleDecl, implClass, implHeader)` — generates `<name>_events.cpp`: Qt-MOC-style method bodies for prototypes declared in the impl's `logos_events:` block. Each body marshals typed args into a `QVariantList` and calls `this->emitEventImpl_("<name>", &args)` on the LogosModuleContext base.
|
|
- `lidlGenerateProviderGlue(lidlPath, ...)` — full pipeline from .lidl file. Also emits `<name>_events.cpp` and a `<name>.lidl` sidecar (via `lidlSerialize`) when the module has any events; both ride the dep's `headers-*` outputs to power consumer-side typed `on<X>()` accessors.
|
|
|
|
### Impl Header Parser (`impl_header_parser.h/cpp`)
|
|
|
|
- `parseImplHeader(headerPath, className, metadataPath, err)` — parses C++ header + metadata.json into ModuleDecl
|
|
- State machine: `LookingForClass` → `InClass` → `InPublic`/`InPrivate`/`InLogosEvents`
|
|
- The literal `logos_events:` token (defined in `logos_module_context.h` as `#define logos_events public`) opens an events section; bare prototypes inside become `EventDecl{name, params, description}` entries appended to `ModuleDecl.events` (the `description` is the doc comment immediately above the declaration, captured via `joinDocLines` exactly as for methods)
|
|
- Skips: constructors, destructors, typedefs, using, friend, enum, struct, `std::function` declarations
|
|
- Recognizes `LogosMap` and `LogosList` return types (nlohmann::json aliases) and sets `MethodDecl.jsonReturn = true`
|
|
- Template-aware parameter splitting (handles `std::vector<std::string>` correctly)
|
|
|
|
## CLI Usage
|
|
|
|
### From C++ impl header (primary use case for universal modules)
|
|
|
|
```bash
|
|
logos-cpp-generator --from-header src/my_module_impl.h \
|
|
--backend qt \
|
|
--impl-class MyModuleImpl \
|
|
--impl-header my_module_impl.h \
|
|
--metadata metadata.json \
|
|
--output-dir ./generated_code
|
|
```
|
|
|
|
Generates: `my_module_qt_glue.h`, `my_module_dispatch.cpp`
|
|
|
|
### From LIDL file — provider glue
|
|
|
|
```bash
|
|
logos-cpp-generator --lidl my_module.lidl \
|
|
--backend qt \
|
|
--impl-class MyModuleImpl \
|
|
--impl-header my_module_impl.h \
|
|
--output-dir ./generated_code
|
|
```
|
|
|
|
### From LIDL file — client stubs
|
|
|
|
```bash
|
|
logos-cpp-generator --lidl my_module.lidl \
|
|
--output-dir ./generated_code \
|
|
--module-only
|
|
```
|
|
|
|
### Legacy modes (unchanged)
|
|
|
|
```bash
|
|
logos-cpp-generator /path/to/plugin.so --output-dir ./generated
|
|
logos-cpp-generator --metadata metadata.json --general-only --output-dir ./generated
|
|
logos-cpp-generator --provider-header src/provider.h --output-dir ./generated
|
|
```
|
|
|
|
### Consumer wrapper with typed event accessors
|
|
|
|
The `--events-from <path>` flag points the legacy `<plugin>.dylib --module-only` codegen at a LIDL sidecar shipped alongside the dep's pre-built headers. When set, the generated `<name>_api.{h,cpp}` gains one typed `on<EventName>(callback)` accessor per declared event (callback arg types match `--api-style`):
|
|
|
|
```bash
|
|
logos-cpp-generator /path/to/plugin.dylib \
|
|
--module-only --api-style std \
|
|
--events-from /path/to/dep/share/logos/my_module.lidl \
|
|
--output-dir ./generated
|
|
```
|
|
|
|
In Nix builds this is wired automatically: `buildHeaders.nix` looks for `<pluginLib>/share/logos/<name>.lidl` (which `buildPlugin.nix`'s installPhase placed there) and threads it through.
|
|
|
|
## Building
|
|
|
|
The generator is built as part of logos-cpp-sdk:
|
|
|
|
```bash
|
|
ws build logos-cpp-sdk # builds everything including the generator
|
|
```
|
|
|
|
The generator binary is available as `logos-cpp-generator` in module build environments (provided by logos-module-builder's `nativeBuildInputs`).
|
|
|
|
## Testing
|
|
|
|
Tests are in `tests/experimental/`:
|
|
|
|
```bash
|
|
ws test logos-cpp-sdk # runs all tests including experimental
|
|
```
|
|
|
|
Test coverage:
|
|
|
|
| Test file | What it tests |
|
|
|-----------|---------------|
|
|
| `test_lidl_lexer.cpp` | Tokenization: keywords, identifiers, symbols, strings, escapes, comments, errors, line/column tracking |
|
|
| `test_lidl_parser.cpp` | Parsing: metadata, methods, events, types, type expressions (array, map, optional, all primitives), error cases |
|
|
| `test_lidl_validator.cpp` | Validation: duplicates, shadowing, unknown types, duplicate params |
|
|
| `test_lidl_serializer.cpp` | Serialization: all constructs, roundtrip (parse → serialize → parse → compare) |
|
|
| `test_lidl_type_mapping.cpp` | `lidlTypeToQt`, `lidlTypeToStd`, `lidlIsStdConvertible`, `lidlToPascalCase` |
|
|
| `test_lidl_gen_provider.cpp` | Provider header + dispatch generation: class names, includes, macros, wrapper methods, conversions, events |
|
|
| `test_lidl_gen_client.cpp` | Client stub generation: sync/async methods, events, metadata JSON, edge cases |
|
|
| `test_impl_header_parser.cpp` | Header parsing: type mapping, access specifiers, skipping private/protected, error cases |
|
|
|
|
Fixture files in `tests/experimental/fixtures/`:
|
|
- `sample_impl.h` — module with all supported type variations
|
|
- `sample_metadata.json` — metadata with dependencies
|
|
- `complex_impl.h` — module with multiple access specifier sections
|
|
- `empty_class_impl.h` — class with no public methods
|
|
- `empty_metadata.json` — minimal metadata
|
|
|
|
## Known Limitations
|
|
|
|
- The impl header parser is lightweight (regex + state machine). It does not handle:
|
|
- Multi-line method declarations
|
|
- Default parameter values
|
|
- Method definitions in the header (only declarations ending with `;`)
|
|
- Nested classes
|
|
- Template methods
|
|
- `std::function` members are silently skipped (never treated as methods)
|
|
- LIDL does not support generic/parameterized types or inheritance
|
|
- Only the `qt` backend is implemented for `--from-header`; future backends (CBOR, Rust) are planned
|
|
- Client stub generation (`lidlMakeHeader`/`lidlMakeSource`) is only available from LIDL files, not from `--from-header`
|