# Getting Started This guide walks you through creating a new Logos module using `logos-module-builder`. ## Prerequisites - Nix with flakes enabled - Basic familiarity with C++ and Qt - Understanding of the Logos module architecture (see [specs](https://github.com/logos-co/logos-core-poc/blob/main/docs/specs.md)) ## Creating a New Module ### 1. Create the Module Directory ```bash mkdir logos-my-module cd logos-my-module ``` ### 2. Create `metadata.json` This is the single configuration file for your module. It is read by the Nix build system for derivation configuration, and embedded into the Qt plugin at compile time via `Q_PLUGIN_METADATA`: ```json { "name": "my_module", "display_name": "My Module", "version": "1.0.0", "type": "core", "interface": "universal", "category": "general", "description": "My awesome Logos module", "main": "my_module_plugin", "dependencies": [], "nix": { "packages": { "build": [], "runtime": [] }, "external_libraries": [], "cmake": { "find_packages": [], "extra_sources": [] } } } ``` `"interface": "universal"` selects the universal authoring model: you write only an impl class and the Qt plugin glue is generated for you. The top-level fields are embedded into the Qt plugin binary at compile time via `Q_PLUGIN_METADATA`. The `"nix"` block is used by the build system for derivations and CMake generation — Qt ignores it. ### 3. Create `flake.nix` ```nix { description = "My Logos Module"; inputs = { logos-module-builder.url = "github:logos-co/logos-module-builder"; }; outputs = inputs@{ logos-module-builder, ... }: logos-module-builder.lib.mkLogosModule { src = ./.; configFile = ./metadata.json; flakeInputs = inputs; }; } ``` ### 4. Create the Impl Header In the universal model you write a single impl class. Its public methods are the module's API; the LIDL contract and all the Qt plugin glue are generated from this header. Module code is Qt-free — use `std::string`, not `QString`. Create `src/my_module_impl.h`: ```cpp #pragma once #include #include "logos_module_context.h" class MyModuleImpl : public LogosModuleContext { public: /// Processes the input and returns a result. std::string doSomething(const std::string& input); logos_events: /// Emitted by doSomething() with the result it produced. Other modules /// subscribe with `modules().my_module.onProcessed(...)`. void processed(const std::string& result); }; ``` Deriving `LogosModuleContext` gives you `modules()` (typed callers and event subscriptions for anything in `dependencies`) and the `onContextReady()` hook, which runs once the module is wired. ### 5. Create the Impl Implementation Create `src/my_module_impl.cpp`: ```cpp #include "my_module_impl.h" std::string MyModuleImpl::doSomething(const std::string& input) { std::string result = "Processed: " + input; // The generated event body routes the typed payload to every subscriber. processed(result); return result; } ``` ### 6. Create `CMakeLists.txt` You list only the impl sources. The generated glue is compiled automatically. ```cmake cmake_minimum_required(VERSION 3.14) project(MyModulePlugin LANGUAGES CXX) if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT}) include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake) else() message(FATAL_ERROR "LogosModule.cmake not found") endif() logos_module( NAME my_module SOURCES src/my_module_impl.h src/my_module_impl.cpp ) ``` ### 7. Build the Module ```bash # Track all files with git (Nix only sees tracked files) git init && git add -A # Build everything (lib + generated headers) nix build # Build just the library nix build .#lib # Build just the generated headers nix build .#include # Build .lgx packages nix build .#lgx nix build .#lgx-portable ``` The output will be in `result/`: - `result/lib/my_module_plugin.so` (or `.dylib` on macOS) - `result/include/` - Generated headers for SDK ## Module Structure Summary ``` logos-my-module/ ├── flake.nix # Nix flake (10 lines) ├── metadata.json # Module config (30 lines) ├── CMakeLists.txt # CMake config (15 lines) └── src/ # Source files (universal model) ├── my_module_impl.h └── my_module_impl.cpp ``` The builder generates the rest from `src/my_module_impl.h` into `generated_code/` — none of it is part of your source tree: ``` generated_code/ ├── my_module.lidl # the contract, derived from the impl header ├── my_module_cdylib_glue.h # the Qt plugin logos_host loads ├── my_module_cdylib_glue.cpp # (Q_PLUGIN_METADATA, onInit wiring) ├── my_module_types.h # C-ABI types for the impl ├── my_module_module_impl.cpp # Qt-free C-ABI exports around MyModuleImpl ├── my_module_events_cdylib.cpp # only when the header declares logos_events: └── logos_sdk.h/.cpp + _api.h/.cpp # typed wrappers for `dependencies` ``` (Earlier builder revisions emitted `my_module_interface.h` + `my_module_plugin.{h,cpp}` here; those names are gone.) ## Next Steps - Add dependencies on other modules (see [configuration.md](./configuration.md)) - Wrap an external library (see [templates/external-lib-module](../templates/external-lib-module) and the [External Libraries Guide](./external-libraries.md)) - Add protobuf support for messaging - Migrate an existing module (see [migration.md](./migration.md)) ## Using Your Module Once built, the module can be loaded by Logos Core: ```cpp // In an application using Logos Core (liblogos' C API). // `logos_core_load_plugin` no longer exists; the entry point takes a // with-dependencies flag. logos_core_load_module("my_module", /*with_dependencies=*/true); // Call methods via LogosAPI auto* client = logosAPI->getClient("my_module"); QString result = client->invokeRemoteMethod("my_module", "doSomething", "test"); ``` Or using the generated SDK wrappers: ```cpp // Using code-generated wrappers QString result = logos.my_module.doSomething("test"); ```