diff --git a/logos-developer-guide.md b/logos-developer-guide.md index 2048f44..fc89af4 100644 --- a/logos-developer-guide.md +++ b/logos-developer-guide.md @@ -442,17 +442,49 @@ Example JSON output: ./lm/bin/lm methods ./result/lib/my_module_plugin.so --json ``` -Example JSON output: +**The type names depend on which kind of module you are inspecting**, because +two different things publish this JSON: + +- A **universal / cdylib module** (`"interface": "universal"`, the style used + throughout this guide and in the tutorials) publishes its **LIDL contract** + types — `tstr`, `int`, `uint`, `bstr`, `[tstr]`, `{tstr: any}`, `? uint`, + `result`, and a record's declared name. The module is Qt-free, so the + contract is the only vocabulary in which the question has one answer, and + a Rust module implementing the same contract answers identically. +- A **handwritten Qt plugin** publishes what its `QMetaObject` says — `QString`, + `QVariantList`, `QVariantMap` — because there the metaobject *is* the + contract. + +Example JSON output, for a universal module with +`method doSomething(input: tstr) -> tstr`: ```json [ { - "name": "initLogos", - "signature": "initLogos(LogosAPI*)", - "returnType": "void", + "name": "doSomething", + "signature": "doSomething(tstr)", + "returnType": "tstr", "isInvokable": true, - "parameters": [{ "name": "logosAPIInstance", "type": "LogosAPI*" }] + "parameters": [{ "name": "input", "type": "tstr" }] }, + { + "name": "name", + "signature": "name()", + "returnType": "tstr", + "isInvokable": true, + "description": "The module's name, as declared in its metadata." + } +] +``` + +`name` and `version` are **derived**: the generator emits them from +`metadata.json`, so every module answers them without the author writing them, +and they appear in every listing. + +The same listing from a handwritten Qt plugin would instead read: + +```json +[ { "name": "doSomething", "signature": "doSomething(QString)", @@ -1030,20 +1062,39 @@ The generator is bundled with `logos-cpp-sdk`. It is automatically available: #### Generating Wrappers ```bash -# Generate wrappers for a single module -logos-cpp-generator /path/to/my_module_plugin.so --output-dir ./generated +# Generate wrappers for a single module, from the CONTRACT it ships beside its +# plugin. `--events-from` names that contract, and the wrapper's typed methods, +# record structs and typed on() accessors all come from it. +logos-cpp-generator /path/to/my_module_plugin.so --output-dir ./generated \ + --events-from /path/to/share/logos/my_module.lidl + +# A handcrafted Qt module publishes no contract; omit the flag and the wrapper +# comes from the plugin's Qt metaobject, which is then the only description of +# its API that exists. +logos-cpp-generator /path/to/handcrafted_plugin.so --output-dir ./generated # Generate a wrapper per dependency, each from that dependency's LIDL contract logos-cpp-generator --metadata metadata.json --general-only --output-dir ./generated \ --dep waku_module=/path/to/waku_module.lidl # Generate only module files (no umbrella headers) -logos-cpp-generator /path/to/plugin.so --module-only --output-dir ./generated +logos-cpp-generator /path/to/plugin.so --module-only --output-dir ./generated \ + --events-from /path/to/share/logos/my_module.lidl # Generate only umbrella SDK files (assumes module files exist) logos-cpp-generator --metadata metadata.json --general-only --output-dir ./generated ``` +> **Why `--events-from` is not optional for a module that has a contract.** A +> module built with `interface: "universal"` or `"cdylib"` publishes its +> `getMethods()` metadata in the LIDL contract vocabulary (`tstr`, `[uint]`, +> `result`) — that listing is what `lm` and `logoscore` show a human, and Qt +> type names would be the wrong answer for a Qt-free module. The wrapper +> emitter reads Qt type names, so generating from that listing would silently +> produce a wrapper of `QVariant` / `LogosMap`. It refuses instead, naming the +> contract to pass. Nix builds pass it for you: `buildHeaders.nix` finds +> `/share/logos/.lidl`, which `buildPlugin.nix` installed. + #### Using Generated Wrappers After generation, you get typed wrapper classes with both synchronous and asynchronous methods: @@ -1363,7 +1414,7 @@ logoscore stop # Stop daemon ### `logos-cpp-generator` -- SDK Code Generator ```bash -logos-cpp-generator [--output-dir ] [--module-only] +logos-cpp-generator [--output-dir ] [--module-only] [--events-from .lidl] logos-cpp-generator --metadata --general-only --dep =.lidl [--output-dir ] logos-cpp-generator --metadata --general-only [--output-dir ] ``` diff --git a/outputs/tutorial-composing-modules.md b/outputs/tutorial-composing-modules.md index 2a38454..1642954 100644 --- a/outputs/tutorial-composing-modules.md +++ b/outputs/tutorial-composing-modules.md @@ -485,7 +485,7 @@ Dependencies: calc_module ./lm/bin/lm methods result/lib/calc_aggregator_plugin.dylib # macOS ``` -Every `public` method on the impl is here — `int64_t` shows up as `int`, `std::string` as `QString`, and `LogosMap` (from `computeReport`) as `QVariantMap`, because `lm` reports the wire types the generated glue exposes. +Every `public` method on the impl is here, published in the **LIDL contract** vocabulary rather than in C++ or Qt names: `int64_t` shows up as `int`, `std::string` as `tstr`, and `LogosMap` (from `computeReport`) as `{tstr: any}`. `lm` is reporting what the module says about itself, and what a module publishes is its contract — the same words the generated `.lidl` uses, and the same words a Rust or Nim module implementing this contract would answer with. --- diff --git a/outputs/tutorial-interface-dependencies.md b/outputs/tutorial-interface-dependencies.md index 6c2f0ac..d8d51ca 100644 --- a/outputs/tutorial-interface-dependencies.md +++ b/outputs/tutorial-interface-dependencies.md @@ -413,7 +413,7 @@ Dependencies: ./lm/bin/lm methods result/lib/calc_via_interface_plugin.dylib # macOS ``` -Every `public` method is here. `int64_t` shows up as `int` and `std::string` as `QString` — the wire types the generated glue exposes. +Every `public` method is here, published in the **LIDL contract** vocabulary rather than in C++ or Qt names: `int64_t` shows up as `int` and `std::string` as `tstr`. `lm` is reporting what the module says about itself, and what a module publishes is its contract. --- diff --git a/outputs/tutorial-wrapping-c-library.md b/outputs/tutorial-wrapping-c-library.md index 9c99e5a..ede38e8 100644 --- a/outputs/tutorial-wrapping-c-library.md +++ b/outputs/tutorial-wrapping-c-library.md @@ -368,8 +368,8 @@ public: ~CalcModuleImpl() = default; // ── Public API — every method here is callable over IPC ────────── - // The generator maps C++ types onto the wire automatically: - // int64_t ↔ int std::string ↔ QString bool ↔ bool + // The generator maps C++ types onto the contract automatically: + // int64_t ↔ int std::string ↔ tstr bool ↔ bool // // A doc comment directly above a method becomes that method's // `description` in the module's method introspection — surfaced @@ -424,18 +424,24 @@ logos_events: - It's a normal C++ class. Any `public` method is exposed; `private` members and helpers are not. - **Supported parameter/return types** (what the generator can translate): - | C++ type | On the wire (Qt) | - | --------------------------- | ------------------ | - | `void` | `void` | - | `bool` | `bool` | - | `int64_t` | `int` | - | `uint64_t` | `uint` | - | `double` | `double` | - | `std::string` | `QString` | - | `std::vector` | `QStringList` | - | `std::vector` | `QByteArray` | - | `LogosMap` / `LogosList` | `QVariantMap` / `QVariantList` (from ``) | - | `StdLogosResult` | `LogosResult` (from ``) — `{ success, value, error }` | + | C++ type | LIDL contract type | A Qt consumer sees | + | --------------------------- | ------------------ | ------------------ | + | `void` | `void` | `void` | + | `bool` | `bool` | `bool` | + | `int64_t` | `int` | `qlonglong` | + | `uint64_t` | `uint` | `qulonglong` | + | `double` | `float64` | `double` | + | `std::string` | `tstr` | `QString` | + | `std::vector` | `[tstr]` | `QStringList` | + | `std::vector` | `bstr` | `QByteArray` | + | `LogosMap` / `LogosList` | `{tstr: any}` / `[any]` (from ``) | `QVariantMap` / `QVariantList` | + | `StdLogosResult` | `result` | `LogosResult` (from ``) — `{ success, value, error }` | + + The **middle** column is the one your module publishes about itself — + it is what `lm` prints in Step 5, and what any other language's + binding of this contract sees. The right column is what a *C++/Qt* + caller of this module compiles against; a Rust or Nim caller gets + that language's spelling of the same middle column. - Use `int64_t` for integers (not `int`) — that's the type the parser recognizes. - **Document methods with `///`.** A doc comment (`///` or `/** … */`) directly above a method becomes its `description` in the module's introspection, surfaced by `lm`, `logoscore module-info`, and Basecamp. Plain `//` comments are ignored, so only intentional docs are exposed — you'll see this in action in Step 5. @@ -616,9 +622,12 @@ Dependencies: (none) ``` Output — each method you declared, with its doc comment as a -`Description`. A single-line comment renders inline; a multi-line -comment (`factorial`'s two `///` lines, `libVersion`'s `/** ... */` -block, and `libVersionNotify`'s two `///` lines) keeps its line breaks: +`Description`, plus the two identity methods (`name`, `version`) the +generator derives from `metadata.json` so every module answers them +without you writing them. A single-line comment renders inline; a +multi-line comment (`factorial`'s two `///` lines, `libVersion`'s +`/** ... */` block, and `libVersionNotify`'s two `///` lines) keeps +its line breaks: ``` Plugin Methods: @@ -646,7 +655,7 @@ int fibonacci(int n) Invokable: yes Description: Returns the nth Fibonacci number (0-indexed). -QString libVersion() +tstr libVersion() Signature: libVersion() Invokable: yes Description: @@ -659,11 +668,21 @@ void libVersionNotify() Description: Looks up the library version and emits it as a `versionReady` event instead of returning it. Used by the QML tutorial (Part 2). + +tstr name() + Signature: name() + Invokable: yes + Description: The module's name, as declared in its metadata. + +tstr version() + Signature: version() + Invokable: yes + Description: The module's version, as declared in its metadata. ``` Three things to notice: -- **Signatures are Qt-typed** (`int`, `QString`) even though you wrote `int64_t` / `std::string`. That's the generated glue: `lm` reports the wire types the synthesized Qt plugin exposes, so `int64_t add(int64_t, int64_t)` shows up as `add(int,int)`. +- **Signatures are in LIDL, not C++** (`int`, `tstr`) even though you wrote `int64_t` / `std::string`. `lm` reports what the module *publishes about itself*, and a module publishes its **contract** — so `int64_t add(int64_t, int64_t)` shows up as `add(int,int)`. That is the same vocabulary as the `.lidl` the build derived from your header, and it is the only vocabulary in which this question has one right answer: your module is Qt-free, and a reader in Rust or Nim asking the same module the same question gets the same words back. Note `int` here is LIDL's `int`, which is **64-bit** — each type in the contract maps to exactly one type per language, and integers are 64-bit throughout, so a value that fits your `int64_t` cannot be silently truncated on the way across. - **Each `Description` is your doc comment**, carried through the module's method introspection. Plain `//` comments (like the type-mapping note in the header) are deliberately ignored, so only intentional docs surface; an undocumented method simply omits it. - **Line breaks are preserved** — a single-line comment renders inline; a multi-line comment (`factorial`, `libVersion`, `libVersionNotify`) keeps its breaks. The same descriptions appear in `logoscore module-info` and Basecamp's Methods list. @@ -720,8 +739,8 @@ signature and `///` description: Plugin Events: ============== -void versionReady(QString version) - Signature: versionReady(QString) +void versionReady(tstr version) + Signature: versionReady(tstr) Description: Emitted by libVersionNotify() once the library version is known. Carries the version string read from libcalc. @@ -813,15 +832,19 @@ Methods: Defined as n * (n-1) * ... * 1, with 0! = 1. fibonacci(n: int) -> int Returns the nth Fibonacci number (0-indexed). - libVersion() -> QString + libVersion() -> tstr Returns the version string of the wrapped libcalc C library. Read straight from the linked native library, not metadata.json. libVersionNotify() -> void Looks up the library version and emits it as a `versionReady` event instead of returning it. Used by the QML tutorial (Part 2). + name() -> tstr + The module's name, as declared in its metadata. + version() -> tstr + The module's version, as declared in its metadata. Events: - versionReady(version: QString) + versionReady(version: tstr) Emitted by libVersionNotify() once the library version is known. Carries the version string read from libcalc. ``` diff --git a/tests/tutorial-composing-modules.test.yaml b/tests/tutorial-composing-modules.test.yaml index bbb2707..9ddd048 100644 --- a/tests/tutorial-composing-modules.test.yaml +++ b/tests/tutorial-composing-modules.test.yaml @@ -482,7 +482,7 @@ sections: - "bumpRunCount" - "persistenceDir" post_text: | - Every `public` method on the impl is here — `int64_t` shows up as `int`, `std::string` as `QString`, and `LogosMap` (from `computeReport`) as `QVariantMap`, because `lm` reports the wire types the generated glue exposes. + Every `public` method on the impl is here, published in the **LIDL contract** vocabulary rather than in C++ or Qt names: `int64_t` shows up as `int`, `std::string` as `tstr`, and `LogosMap` (from `computeReport`) as `{tstr: any}`. `lm` is reporting what the module says about itself, and what a module publishes is its contract — the same words the generated `.lidl` uses, and the same words a Rust or Nim module implementing this contract would answer with. # ── Step 6: Run it with logoscore ─────────────────────────────────────────── - title: "Run it with `logoscore`" diff --git a/tests/tutorial-interface-dependencies.test.yaml b/tests/tutorial-interface-dependencies.test.yaml index 957d0d5..a2c4463 100644 --- a/tests/tutorial-interface-dependencies.test.yaml +++ b/tests/tutorial-interface-dependencies.test.yaml @@ -416,7 +416,7 @@ sections: - "startFibVia" - "watchVersion" post_text: | - Every `public` method is here. `int64_t` shows up as `int` and `std::string` as `QString` — the wire types the generated glue exposes. + Every `public` method is here, published in the **LIDL contract** vocabulary rather than in C++ or Qt names: `int64_t` shows up as `int` and `std::string` as `tstr`. `lm` is reporting what the module says about itself, and what a module publishes is its contract. # ── Step 7: Run it with logoscore ─────────────────────────────────────────── - title: "Run it with `logoscore`" diff --git a/tests/tutorial-wrapping-c-library.test.yaml b/tests/tutorial-wrapping-c-library.test.yaml index 29c764d..3cbd994 100644 --- a/tests/tutorial-wrapping-c-library.test.yaml +++ b/tests/tutorial-wrapping-c-library.test.yaml @@ -367,8 +367,8 @@ sections: ~CalcModuleImpl() = default; // ── Public API — every method here is callable over IPC ────────── - // The generator maps C++ types onto the wire automatically: - // int64_t ↔ int std::string ↔ QString bool ↔ bool + // The generator maps C++ types onto the contract automatically: + // int64_t ↔ int std::string ↔ tstr bool ↔ bool // // A doc comment directly above a method becomes that method's // `description` in the module's method introspection — surfaced @@ -422,18 +422,24 @@ sections: - It's a normal C++ class. Any `public` method is exposed; `private` members and helpers are not. - **Supported parameter/return types** (what the generator can translate): - | C++ type | On the wire (Qt) | - | --------------------------- | ------------------ | - | `void` | `void` | - | `bool` | `bool` | - | `int64_t` | `int` | - | `uint64_t` | `uint` | - | `double` | `double` | - | `std::string` | `QString` | - | `std::vector` | `QStringList` | - | `std::vector` | `QByteArray` | - | `LogosMap` / `LogosList` | `QVariantMap` / `QVariantList` (from ``) | - | `StdLogosResult` | `LogosResult` (from ``) — `{ success, value, error }` | + | C++ type | LIDL contract type | A Qt consumer sees | + | --------------------------- | ------------------ | ------------------ | + | `void` | `void` | `void` | + | `bool` | `bool` | `bool` | + | `int64_t` | `int` | `qlonglong` | + | `uint64_t` | `uint` | `qulonglong` | + | `double` | `float64` | `double` | + | `std::string` | `tstr` | `QString` | + | `std::vector` | `[tstr]` | `QStringList` | + | `std::vector` | `bstr` | `QByteArray` | + | `LogosMap` / `LogosList` | `{tstr: any}` / `[any]` (from ``) | `QVariantMap` / `QVariantList` | + | `StdLogosResult` | `result` | `LogosResult` (from ``) — `{ success, value, error }` | + + The **middle** column is the one your module publishes about itself — + it is what `lm` prints in Step 5, and what any other language's + binding of this contract sees. The right column is what a *C++/Qt* + caller of this module compiles against; a Rust or Nim caller gets + that language's spelling of the same middle column. - Use `int64_t` for integers (not `int`) — that's the type the parser recognizes. - **Document methods with `///`.** A doc comment (`///` or `/** … */`) directly above a method becomes its `description` in the module's introspection, surfaced by `lm`, `logoscore module-info`, and Basecamp. Plain `//` comments are ignored, so only intentional docs are exposed — you'll see this in action in Step 5. @@ -595,47 +601,50 @@ sections: # macOS ./lm/bin/lm methods result/lib/calc_module_plugin.dylib expect_contains: - - "qlonglong add(qlonglong a, qlonglong b)" - - "qlonglong multiply(qlonglong a, qlonglong b)" - - "qlonglong factorial(qlonglong n)" - - "qlonglong fibonacci(qlonglong n)" - - "QString libVersion()" + - "int add(int a, int b)" + - "int multiply(int a, int b)" + - "int factorial(int n)" + - "int fibonacci(int n)" + - "tstr libVersion()" - "Description: Adds two integers and returns the sum." - "Defined as n * (n-1) * ... * 1, with 0! = 1." - "Read straight from the linked native library, not metadata.json." post_text: | Output — each method you declared, with its doc comment as a - `Description`. A single-line comment renders inline; a multi-line - comment (`factorial`'s two `///` lines, `libVersion`'s `/** ... */` - block, and `libVersionNotify`'s two `///` lines) keeps its line breaks: + `Description`, plus the two identity methods (`name`, `version`) the + generator derives from `metadata.json` so every module answers them + without you writing them. A single-line comment renders inline; a + multi-line comment (`factorial`'s two `///` lines, `libVersion`'s + `/** ... */` block, and `libVersionNotify`'s two `///` lines) keeps + its line breaks: ``` Plugin Methods: =============== - qlonglong add(qlonglong a, qlonglong b) - Signature: add(qlonglong,qlonglong) + int add(int a, int b) + Signature: add(int,int) Invokable: yes Description: Adds two integers and returns the sum. - qlonglong multiply(qlonglong a, qlonglong b) - Signature: multiply(qlonglong,qlonglong) + int multiply(int a, int b) + Signature: multiply(int,int) Invokable: yes Description: Multiplies two integers and returns the product. - qlonglong factorial(qlonglong n) - Signature: factorial(qlonglong) + int factorial(int n) + Signature: factorial(int) Invokable: yes Description: Computes the factorial n! of a non-negative integer. Defined as n * (n-1) * ... * 1, with 0! = 1. - qlonglong fibonacci(qlonglong n) - Signature: fibonacci(qlonglong) + int fibonacci(int n) + Signature: fibonacci(int) Invokable: yes Description: Returns the nth Fibonacci number (0-indexed). - QString libVersion() + tstr libVersion() Signature: libVersion() Invokable: yes Description: @@ -648,11 +657,21 @@ sections: Description: Looks up the library version and emits it as a `versionReady` event instead of returning it. Used by the QML tutorial (Part 2). + + tstr name() + Signature: name() + Invokable: yes + Description: The module's name, as declared in its metadata. + + tstr version() + Signature: version() + Invokable: yes + Description: The module's version, as declared in its metadata. ``` Three things to notice: - - **Signatures are Qt-typed** (`qlonglong`, `QString`) even though you wrote `int64_t` / `std::string`. That's the generated glue: `lm` reports the wire types the synthesized Qt plugin exposes, so `int64_t add(int64_t, int64_t)` shows up as `add(qlonglong,qlonglong)`. Note the width is **preserved** — `qlonglong` is Qt's 64-bit integer, not `int`. Each type in the contract maps to exactly one type per language, and integers are 64-bit throughout, so a value that fits your `int64_t` cannot be silently truncated on the way across. + - **Signatures are in LIDL, not C++** (`int`, `tstr`) even though you wrote `int64_t` / `std::string`. `lm` reports what the module *publishes about itself*, and a module publishes its **contract** — so `int64_t add(int64_t, int64_t)` shows up as `add(int,int)`. That is the same vocabulary as the `.lidl` the build derived from your header, and it is the only vocabulary in which this question has one right answer: your module is Qt-free, and a reader in Rust or Nim asking the same module the same question gets the same words back. Note `int` here is LIDL's `int`, which is **64-bit** — each type in the contract maps to exactly one type per language, and integers are 64-bit throughout, so a value that fits your `int64_t` cannot be silently truncated on the way across. - **Each `Description` is your doc comment**, carried through the module's method introspection. Plain `//` comments (like the type-mapping note in the header) are deliberately ignored, so only intentional docs surface; an undocumented method simply omits it. - **Line breaks are preserved** — a single-line comment renders inline; a multi-line comment (`factorial`, `libVersion`, `libVersionNotify`) keeps its breaks. The same descriptions appear in `logoscore module-info` and Basecamp's Methods list. @@ -705,7 +724,7 @@ sections: # macOS ./lm/bin/lm events result/lib/calc_module_plugin.dylib expect_contains: - - "void versionReady(QString version)" + - "void versionReady(tstr version)" - "Emitted by libVersionNotify() once the library version is known." - "Carries the version string read from libcalc." post_text: | @@ -713,8 +732,8 @@ sections: Plugin Events: ============== - void versionReady(QString version) - Signature: versionReady(QString) + void versionReady(tstr version) + Signature: versionReady(tstr) Description: Emitted by libVersionNotify() once the library version is known. Carries the version string read from libcalc. @@ -786,15 +805,19 @@ sections: Defined as n * (n-1) * ... * 1, with 0! = 1. fibonacci(n: int) -> int Returns the nth Fibonacci number (0-indexed). - libVersion() -> QString + libVersion() -> tstr Returns the version string of the wrapped libcalc C library. Read straight from the linked native library, not metadata.json. libVersionNotify() -> void Looks up the library version and emits it as a `versionReady` event instead of returning it. Used by the QML tutorial (Part 2). + name() -> tstr + The module's name, as declared in its metadata. + version() -> tstr + The module's version, as declared in its metadata. Events: - versionReady(version: QString) + versionReady(version: tstr) Emitted by libVersionNotify() once the library version is known. Carries the version string read from libcalc. ```