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.dylibon 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
- Add dependencies on other modules (see configuration.md)
- Wrap an external library (see examples/external-lib-module)
- Add protobuf support for messaging
- Migrate an existing module (see migration.md)
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");