Files
logos-module-builder/docs/getting-started.md
T

5.2 KiB

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)

Creating a New Module

1. Create the Module Directory

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:

{
  "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

{
  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, and the *Interface/*Plugin glue is generated from this header. Module code is Qt-free — use std::string, not QString.

Create src/my_module_impl.h:

#pragma once

#include <string>
#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:

#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_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

# 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 my_module_interface.h and my_module_plugin.{h,cpp} files are generated by the builder from src/my_module_impl.h — they are not part of your source tree.

Next Steps

Using Your Module

Once built, the module can be loaded by Logos Core:

// In an application using Logos Core
logos_core_load_plugin("my_module");

// Call methods via LogosAPI
auto* client = logosAPI->getClient("my_module");
QString result = client->invokeRemoteMethod("my_module", "doSomething", "test");

Or using the generated SDK wrappers:

// Using code-generated wrappers
QString result = logos.my_module.doSomething("test");