Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 60 additions & 9 deletions logos-developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down Expand Up @@ -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<Event>() 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
> `<module>/share/logos/<name>.lidl`, which `buildPlugin.nix` installed.

#### Using Generated Wrappers

After generation, you get typed wrapper classes with both synchronous and asynchronous methods:
Expand Down Expand Up @@ -1363,7 +1414,7 @@ logoscore stop # Stop daemon
### `logos-cpp-generator` -- SDK Code Generator

```bash
logos-cpp-generator <plugin-file> [--output-dir <dir>] [--module-only]
logos-cpp-generator <plugin-file> [--output-dir <dir>] [--module-only] [--events-from <name>.lidl]
logos-cpp-generator --metadata <metadata.json> --general-only --dep <name>=<name>.lidl [--output-dir <dir>]
logos-cpp-generator --metadata <metadata.json> --general-only [--output-dir <dir>]
```
Expand Down
2 changes: 1 addition & 1 deletion outputs/tutorial-composing-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand Down
2 changes: 1 addition & 1 deletion outputs/tutorial-interface-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand Down
69 changes: 46 additions & 23 deletions outputs/tutorial-wrapping-c-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<std::string>` | `QStringList` |
| `std::vector<uint8_t>` | `QByteArray` |
| `LogosMap` / `LogosList` | `QVariantMap` / `QVariantList` (from `<logos_json.h>`) |
| `StdLogosResult` | `LogosResult` (from `<logos_result.h>`) — `{ 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<std::string>` | `[tstr]` | `QStringList` |
| `std::vector<uint8_t>` | `bstr` | `QByteArray` |
| `LogosMap` / `LogosList` | `{tstr: any}` / `[any]` (from `<logos_json.h>`) | `QVariantMap` / `QVariantList` |
| `StdLogosResult` | `result` | `LogosResult` (from `<logos_result.h>`) — `{ 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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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.

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
```
Expand Down
2 changes: 1 addition & 1 deletion tests/tutorial-composing-modules.test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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`"
Expand Down
2 changes: 1 addition & 1 deletion tests/tutorial-interface-dependencies.test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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`"
Expand Down
Loading
Loading