From f5bb23c105515dfd48d9abb9d318ccc7f531ed09 Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Wed, 25 Mar 2026 10:37:15 +0100 Subject: [PATCH] feat: update and streamline the guides --- README.md | 29 +- logos-calc-cpp-ui/CMakeLists.txt | 3 +- logos-calc-cpp-ui/flake.lock | 31 +- logos-calc-cpp-ui/interfaces/IComponent.h | 17 - logos-calc-cpp-ui/src/calc_ui_cpp_plugin.cpp | 7 +- logos-calc-cpp-ui/src/calc_ui_cpp_plugin.h | 24 +- logos-calc-module/flake.lock | 12 +- logos-calc-ui/flake.lock | 453 ++++++++--- logos-developer-guide.md | 805 +++++++++---------- tutorial-cpp-ui-app.md | 248 +++--- tutorial-qml-ui-app.md | 160 ++-- tutorial-wrapping-c-library.md | 73 +- 12 files changed, 1066 insertions(+), 796 deletions(-) delete mode 100644 logos-calc-cpp-ui/interfaces/IComponent.h diff --git a/README.md b/README.md index 692d719..315e2ae 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,29 @@ # logos-tutorial -- Part 1: [Wrapping a C Library as a Logos Module](tutorial-wrapping-c-library.md) -- Part 2: [Building a QML UI App](tutorial-qml-ui-app.md) \ No newline at end of file +Tutorial series and reference documentation for building Logos modules. + +## Start Here + +**New to Logos?** Start with the developer guide -- it walks through creating, building, packaging, and running your first module: + +- [Logos Developer Guide](logos-developer-guide.md) + +## Next Tutorials + +Step-by-step tutorials that build on each other. Each creates a working module you can run. + +- **Part 1:** [Wrapping a C Library](tutorial-wrapping-c-library.md) -- build `calc_module`, a core module that wraps a C library (`libcalc`). Covers external library configuration, CMake integration, building, inspecting with `lm`, testing with `logoscore`, and packaging with `nix-bundle-lgx`. + +- **Part 2:** [Building a QML UI App](tutorial-qml-ui-app.md) -- build `calc_ui`, a QML-only UI plugin that calls `calc_module` through the `logos.callModule()` bridge. No compilation needed. + +- **Part 3:** [Building a C++ UI Module](tutorial-cpp-ui-app.md) -- build `calc_ui_cpp`, a native C++ Qt widget plugin with typed backend calls to `calc_module` via `LogosAPI*`. + +## Example Modules + +Working module source code used by the tutorials: + +| Directory | Module | Type | Tutorial | +|-----------|--------|------|----------| +| `logos-calc-module/` | `calc_module` | Core (wraps libcalc) | Part 1 | +| `logos-calc-ui/` | `calc_ui` | QML UI | Part 2 | +| `logos-calc-cpp-ui/` | `calc_ui_cpp` | C++ UI | Part 3 | diff --git a/logos-calc-cpp-ui/CMakeLists.txt b/logos-calc-cpp-ui/CMakeLists.txt index 8e51270..6f06a40 100644 --- a/logos-calc-cpp-ui/CMakeLists.txt +++ b/logos-calc-cpp-ui/CMakeLists.txt @@ -10,12 +10,11 @@ endif() logos_module( NAME calc_ui_cpp SOURCES + src/calc_ui_cpp_interface.h src/calc_ui_cpp_plugin.h src/calc_ui_cpp_plugin.cpp src/calc_backend.h src/calc_backend.cpp - INCLUDE_DIRS - ${CMAKE_CURRENT_SOURCE_DIR}/interfaces ) find_package(Qt6 REQUIRED COMPONENTS Widgets Quick QuickWidgets) diff --git a/logos-calc-cpp-ui/flake.lock b/logos-calc-cpp-ui/flake.lock index 75561d8..79f7b79 100644 --- a/logos-calc-cpp-ui/flake.lock +++ b/logos-calc-cpp-ui/flake.lock @@ -2,20 +2,15 @@ "nodes": { "calc_module": { "inputs": { - "logos-module-builder": "logos-module-builder", - "nixpkgs": [ - "calc_module", - "logos-module-builder", - "nixpkgs" - ] + "logos-module-builder": "logos-module-builder" }, "locked": { "dir": "logos-calc-module", - "lastModified": 1773840224, - "narHash": "sha256-1YoXGd+i6SdmO5Iy2pgMcpHvBXrqEARZ07Myv6HIhHY=", + "lastModified": 1774455371, + "narHash": "sha256-UeFBVVbjiykns00DL/Hypa4qH94KFUCPt/EBJLRGie4=", "owner": "logos-co", "repo": "logos-tutorial", - "rev": "88bb37fcaff770a0220701de3760f582435a91ee", + "rev": "364dbe596edbed4abd91e3852988aab0da0b8c84", "type": "github" }, "original": { @@ -151,11 +146,11 @@ ] }, "locked": { - "lastModified": 1773956385, - "narHash": "sha256-CV0Lo1FrosBt/MSP+GWQGWXnYobxRGXGOREylNuwZ58=", + "lastModified": 1774280725, + "narHash": "sha256-RJ0QgIXg7XZp9DbcZruTHKifxqYItT+ek9XMiJNt/R0=", "owner": "logos-co", "repo": "logos-cpp-sdk", - "rev": "4b66dac015e4b977d33cfae80a4c8e1d518679f3", + "rev": "4197ee183041c33e90d8f90e80d810e5b7ed04bf", "type": "github" }, "original": { @@ -462,11 +457,11 @@ ] }, "locked": { - "lastModified": 1774011097, - "narHash": "sha256-oemw2RD/GJqSjZNy8GoLr4z0zK2NsRS6Kqs9uWaYAG8=", + "lastModified": 1774468631, + "narHash": "sha256-pwv7KVUbGMidRQ2Omt7mTORJDtaAxynD9bvC5zRpJaQ=", "owner": "logos-co", "repo": "logos-module-builder", - "rev": "b178098cc4aae12ec7bf8c68a5ba5ecf7755611e", + "rev": "1b8929cd8b57abe4a7e5450d7ac4877942b49b50", "type": "github" }, "original": { @@ -671,11 +666,11 @@ ] }, "locked": { - "lastModified": 1773946611, - "narHash": "sha256-awLDFRzTxgEaB1Fuorag+y6Oz1pNE1joWYgA0ocu55k=", + "lastModified": 1774455360, + "narHash": "sha256-hf10epVwnxOALnx2lRlfl4d9xW9BN/5GKxMIyRkhi6g=", "owner": "logos-co", "repo": "logos-standalone-app", - "rev": "d35bbf3a559172faa1ef0a6e243230f6d0fb11dd", + "rev": "bcf255b7df3a1cbb7be1e2daf0a694f0270dc1b2", "type": "github" }, "original": { diff --git a/logos-calc-cpp-ui/interfaces/IComponent.h b/logos-calc-cpp-ui/interfaces/IComponent.h deleted file mode 100644 index 06ad57a..0000000 --- a/logos-calc-cpp-ui/interfaces/IComponent.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include -#include - -class LogosAPI; - -class IComponent { -public: - virtual ~IComponent() = default; - virtual QWidget* createWidget(LogosAPI* logosAPI = nullptr) = 0; - virtual void destroyWidget(QWidget* widget) = 0; -}; - -#define IComponent_iid "com.logos.component.IComponent" -Q_DECLARE_INTERFACE(IComponent, IComponent_iid) diff --git a/logos-calc-cpp-ui/src/calc_ui_cpp_plugin.cpp b/logos-calc-cpp-ui/src/calc_ui_cpp_plugin.cpp index 9fc293c..32b1c24 100644 --- a/logos-calc-cpp-ui/src/calc_ui_cpp_plugin.cpp +++ b/logos-calc-cpp-ui/src/calc_ui_cpp_plugin.cpp @@ -2,14 +2,19 @@ #include "calc_backend.h" #include "logos_api.h" #include +#include #include #include -#include #include CalcUiCppPlugin::CalcUiCppPlugin(QObject* parent) : QObject(parent) {} CalcUiCppPlugin::~CalcUiCppPlugin() {} +void CalcUiCppPlugin::initLogos(LogosAPI* api) +{ + m_logosAPI = api; +} + QWidget* CalcUiCppPlugin::createWidget(LogosAPI* logosAPI) { auto* backend = new CalcBackend(logosAPI); diff --git a/logos-calc-cpp-ui/src/calc_ui_cpp_plugin.h b/logos-calc-cpp-ui/src/calc_ui_cpp_plugin.h index b75c5ef..5cdb9eb 100644 --- a/logos-calc-cpp-ui/src/calc_ui_cpp_plugin.h +++ b/logos-calc-cpp-ui/src/calc_ui_cpp_plugin.h @@ -3,22 +3,34 @@ #include #include -#include +#include +#include "calc_ui_cpp_interface.h" class LogosAPI; -class CalcUiCppPlugin : public QObject, public IComponent +class CalcUiCppPlugin : public QObject, public CalcUiCppInterface { Q_OBJECT - Q_PLUGIN_METADATA(IID IComponent_iid FILE "metadata.json") - Q_INTERFACES(IComponent) + Q_PLUGIN_METADATA(IID CalcUiCppInterface_iid FILE "metadata.json") + Q_INTERFACES(CalcUiCppInterface PluginInterface) public: explicit CalcUiCppPlugin(QObject* parent = nullptr); ~CalcUiCppPlugin() override; - Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr) override; - void destroyWidget(QWidget* widget) override; + QString name() const override { return "calc_ui_cpp"; } + QString version() const override { return "1.0.0"; } + + Q_INVOKABLE void initLogos(LogosAPI* api); + + Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr); + Q_INVOKABLE void destroyWidget(QWidget* widget); + +signals: + void eventResponse(const QString& eventName, const QVariantList& args); + +private: + LogosAPI* m_logosAPI = nullptr; }; #endif // CALC_UI_CPP_PLUGIN_H diff --git a/logos-calc-module/flake.lock b/logos-calc-module/flake.lock index 703653d..9870883 100644 --- a/logos-calc-module/flake.lock +++ b/logos-calc-module/flake.lock @@ -11,11 +11,11 @@ ] }, "locked": { - "lastModified": 1773956385, - "narHash": "sha256-CV0Lo1FrosBt/MSP+GWQGWXnYobxRGXGOREylNuwZ58=", + "lastModified": 1774280725, + "narHash": "sha256-RJ0QgIXg7XZp9DbcZruTHKifxqYItT+ek9XMiJNt/R0=", "owner": "logos-co", "repo": "logos-cpp-sdk", - "rev": "4b66dac015e4b977d33cfae80a4c8e1d518679f3", + "rev": "4197ee183041c33e90d8f90e80d810e5b7ed04bf", "type": "github" }, "original": { @@ -60,11 +60,11 @@ ] }, "locked": { - "lastModified": 1774011097, - "narHash": "sha256-oemw2RD/GJqSjZNy8GoLr4z0zK2NsRS6Kqs9uWaYAG8=", + "lastModified": 1774468631, + "narHash": "sha256-pwv7KVUbGMidRQ2Omt7mTORJDtaAxynD9bvC5zRpJaQ=", "owner": "logos-co", "repo": "logos-module-builder", - "rev": "b178098cc4aae12ec7bf8c68a5ba5ecf7755611e", + "rev": "1b8929cd8b57abe4a7e5450d7ac4877942b49b50", "type": "github" }, "original": { diff --git a/logos-calc-ui/flake.lock b/logos-calc-ui/flake.lock index deaa98f..79f7b79 100644 --- a/logos-calc-ui/flake.lock +++ b/logos-calc-ui/flake.lock @@ -1,8 +1,28 @@ { "nodes": { + "calc_module": { + "inputs": { + "logos-module-builder": "logos-module-builder" + }, + "locked": { + "dir": "logos-calc-module", + "lastModified": 1774455371, + "narHash": "sha256-UeFBVVbjiykns00DL/Hypa4qH94KFUCPt/EBJLRGie4=", + "owner": "logos-co", + "repo": "logos-tutorial", + "rev": "364dbe596edbed4abd91e3852988aab0da0b8c84", + "type": "github" + }, + "original": { + "dir": "logos-calc-module", + "owner": "logos-co", + "repo": "logos-tutorial", + "type": "github" + } + }, "logos-capability-module": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_4", + "logos-cpp-sdk": "logos-cpp-sdk_5", "logos-liblogos": "logos-liblogos_2", "nixpkgs": [ "logos-standalone-app", @@ -28,7 +48,7 @@ }, "logos-capability-module_2": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_5", + "logos-cpp-sdk": "logos-cpp-sdk_6", "logos-liblogos": "logos-liblogos_3", "nixpkgs": [ "logos-standalone-app", @@ -57,6 +77,67 @@ "logos-cpp-sdk": { "inputs": { "logos-nix": "logos-nix", + "nixpkgs": [ + "calc_module", + "logos-module-builder", + "logos-cpp-sdk", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1773956385, + "narHash": "sha256-CV0Lo1FrosBt/MSP+GWQGWXnYobxRGXGOREylNuwZ58=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "4b66dac015e4b977d33cfae80a4c8e1d518679f3", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_10": { + "inputs": { + "nixpkgs": "nixpkgs_16" + }, + "locked": { + "lastModified": 1773672219, + "narHash": "sha256-p+kv2WHokT40rScnSFSIDp/EeFLaDUsTHW6QT6ejKuc=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "a4bd66cd6eb04ee7140bb940b1d49d72d60248de", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_11": { + "inputs": { + "nixpkgs": "nixpkgs_17" + }, + "locked": { + "lastModified": 1773672219, + "narHash": "sha256-p+kv2WHokT40rScnSFSIDp/EeFLaDUsTHW6QT6ejKuc=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "a4bd66cd6eb04ee7140bb940b1d49d72d60248de", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_2": { + "inputs": { + "logos-nix": "logos-nix_4", "nixpkgs": [ "logos-module-builder", "logos-cpp-sdk", @@ -78,79 +159,7 @@ "type": "github" } }, - "logos-cpp-sdk_10": { - "inputs": { - "nixpkgs": "nixpkgs_14" - }, - "locked": { - "lastModified": 1773672219, - "narHash": "sha256-p+kv2WHokT40rScnSFSIDp/EeFLaDUsTHW6QT6ejKuc=", - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "rev": "a4bd66cd6eb04ee7140bb940b1d49d72d60248de", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "type": "github" - } - }, - "logos-cpp-sdk_2": { - "inputs": { - "nixpkgs": "nixpkgs_4" - }, - "locked": { - "lastModified": 1773672219, - "narHash": "sha256-p+kv2WHokT40rScnSFSIDp/EeFLaDUsTHW6QT6ejKuc=", - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "rev": "a4bd66cd6eb04ee7140bb940b1d49d72d60248de", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "type": "github" - } - }, "logos-cpp-sdk_3": { - "inputs": { - "nixpkgs": "nixpkgs_5" - }, - "locked": { - "lastModified": 1773672219, - "narHash": "sha256-p+kv2WHokT40rScnSFSIDp/EeFLaDUsTHW6QT6ejKuc=", - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "rev": "a4bd66cd6eb04ee7140bb940b1d49d72d60248de", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "type": "github" - } - }, - "logos-cpp-sdk_4": { - "inputs": { - "nixpkgs": "nixpkgs_6" - }, - "locked": { - "lastModified": 1773672219, - "narHash": "sha256-p+kv2WHokT40rScnSFSIDp/EeFLaDUsTHW6QT6ejKuc=", - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "rev": "a4bd66cd6eb04ee7140bb940b1d49d72d60248de", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "type": "github" - } - }, - "logos-cpp-sdk_5": { "inputs": { "nixpkgs": "nixpkgs_7" }, @@ -168,16 +177,16 @@ "type": "github" } }, - "logos-cpp-sdk_6": { + "logos-cpp-sdk_4": { "inputs": { "nixpkgs": "nixpkgs_8" }, "locked": { - "lastModified": 1761230734, - "narHash": "sha256-CMRUwXH7pJZ1OI6bd/TDDDXKqQ1tQZHQEOOwK8TgYHI=", + "lastModified": 1773672219, + "narHash": "sha256-p+kv2WHokT40rScnSFSIDp/EeFLaDUsTHW6QT6ejKuc=", "owner": "logos-co", "repo": "logos-cpp-sdk", - "rev": "4b143922c190df00bb3835441c9f0075cb28283b", + "rev": "a4bd66cd6eb04ee7140bb940b1d49d72d60248de", "type": "github" }, "original": { @@ -186,7 +195,7 @@ "type": "github" } }, - "logos-cpp-sdk_7": { + "logos-cpp-sdk_5": { "inputs": { "nixpkgs": "nixpkgs_9" }, @@ -204,28 +213,10 @@ "type": "github" } }, - "logos-cpp-sdk_8": { + "logos-cpp-sdk_6": { "inputs": { "nixpkgs": "nixpkgs_10" }, - "locked": { - "lastModified": 1767724329, - "narHash": "sha256-UPkqxqxbKwU5Dmu00TnjiJVXUmfVylF3p1qziEuYwIE=", - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "rev": "32f1d7080d784ff044d91d076ef2f0c7305d4784", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "type": "github" - } - }, - "logos-cpp-sdk_9": { - "inputs": { - "nixpkgs": "nixpkgs_13" - }, "locked": { "lastModified": 1773672219, "narHash": "sha256-p+kv2WHokT40rScnSFSIDp/EeFLaDUsTHW6QT6ejKuc=", @@ -240,9 +231,63 @@ "type": "github" } }, + "logos-cpp-sdk_7": { + "inputs": { + "nixpkgs": "nixpkgs_11" + }, + "locked": { + "lastModified": 1761230734, + "narHash": "sha256-CMRUwXH7pJZ1OI6bd/TDDDXKqQ1tQZHQEOOwK8TgYHI=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "4b143922c190df00bb3835441c9f0075cb28283b", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_8": { + "inputs": { + "nixpkgs": "nixpkgs_12" + }, + "locked": { + "lastModified": 1773672219, + "narHash": "sha256-p+kv2WHokT40rScnSFSIDp/EeFLaDUsTHW6QT6ejKuc=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "a4bd66cd6eb04ee7140bb940b1d49d72d60248de", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_9": { + "inputs": { + "nixpkgs": "nixpkgs_13" + }, + "locked": { + "lastModified": 1767724329, + "narHash": "sha256-UPkqxqxbKwU5Dmu00TnjiJVXUmfVylF3p1qziEuYwIE=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "32f1d7080d784ff044d91d076ef2f0c7305d4784", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, "logos-design-system": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_3", + "logos-cpp-sdk": "logos-cpp-sdk_4", "nixpkgs": [ "logos-standalone-app", "logos-design-system", @@ -267,8 +312,8 @@ "logos-liblogos": { "inputs": { "logos-capability-module": "logos-capability-module", - "logos-cpp-sdk": "logos-cpp-sdk_9", - "logos-module": "logos-module_3", + "logos-cpp-sdk": "logos-cpp-sdk_10", + "logos-module": "logos-module_4", "nix-bundle-appimage": "nix-bundle-appimage_2", "nix-bundle-dir": "nix-bundle-dir_4", "nixpkgs": [ @@ -294,8 +339,8 @@ "logos-liblogos_2": { "inputs": { "logos-capability-module": "logos-capability-module_2", - "logos-cpp-sdk": "logos-cpp-sdk_7", - "logos-module": "logos-module_2", + "logos-cpp-sdk": "logos-cpp-sdk_8", + "logos-module": "logos-module_3", "nix-bundle-appimage": "nix-bundle-appimage", "nix-bundle-dir": "nix-bundle-dir_2", "nixpkgs": [ @@ -323,7 +368,7 @@ }, "logos-liblogos_3": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_6", + "logos-cpp-sdk": "logos-cpp-sdk_7", "nixpkgs": [ "logos-standalone-app", "logos-liblogos", @@ -353,6 +398,7 @@ "inputs": { "logos-nix": "logos-nix_2", "nixpkgs": [ + "calc_module", "logos-module-builder", "logos-module", "logos-nix", @@ -379,17 +425,43 @@ "logos-module": "logos-module", "logos-nix": "logos-nix_3", "nixpkgs": [ + "calc_module", "logos-module-builder", "logos-nix", "nixpkgs" ] }, "locked": { - "lastModified": 1774288906, - "narHash": "sha256-8BlCpTMFGlRUxyAzlE54O2XAOVb5XKd5uMe78uCfGSk=", + "lastModified": 1774011097, + "narHash": "sha256-oemw2RD/GJqSjZNy8GoLr4z0zK2NsRS6Kqs9uWaYAG8=", "owner": "logos-co", "repo": "logos-module-builder", - "rev": "71631418f8e7df7e3eac395597415ad2eaf14747", + "rev": "b178098cc4aae12ec7bf8c68a5ba5ecf7755611e", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-module-builder", + "type": "github" + } + }, + "logos-module-builder_2": { + "inputs": { + "logos-cpp-sdk": "logos-cpp-sdk_2", + "logos-module": "logos-module_2", + "logos-nix": "logos-nix_6", + "nixpkgs": [ + "logos-module-builder", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1774468631, + "narHash": "sha256-pwv7KVUbGMidRQ2Omt7mTORJDtaAxynD9bvC5zRpJaQ=", + "owner": "logos-co", + "repo": "logos-module-builder", + "rev": "1b8929cd8b57abe4a7e5450d7ac4877942b49b50", "type": "github" }, "original": { @@ -400,7 +472,31 @@ }, "logos-module_2": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_8", + "logos-nix": "logos-nix_5", + "nixpkgs": [ + "logos-module-builder", + "logos-module", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1773963329, + "narHash": "sha256-zdvDHoYWQDse0eJ/UCKIJcfuYJ8NMgl6QfxRcyDEovI=", + "owner": "logos-co", + "repo": "logos-module", + "rev": "ac5a4f06ea94b01dd9c5fbb9ed4f20620beab88d", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-module", + "type": "github" + } + }, + "logos-module_3": { + "inputs": { + "logos-cpp-sdk": "logos-cpp-sdk_9", "nixpkgs": [ "logos-standalone-app", "logos-liblogos", @@ -425,9 +521,9 @@ "type": "github" } }, - "logos-module_3": { + "logos-module_4": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_10", + "logos-cpp-sdk": "logos-cpp-sdk_11", "nixpkgs": [ "logos-standalone-app", "logos-liblogos", @@ -504,9 +600,63 @@ "type": "github" } }, + "logos-nix_4": { + "inputs": { + "nixpkgs": "nixpkgs_4" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_5": { + "inputs": { + "nixpkgs": "nixpkgs_5" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_6": { + "inputs": { + "nixpkgs": "nixpkgs_6" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, "logos-standalone-app": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_2", + "logos-cpp-sdk": "logos-cpp-sdk_3", "logos-design-system": "logos-design-system", "logos-liblogos": "logos-liblogos", "nixpkgs": [ @@ -516,11 +666,11 @@ ] }, "locked": { - "lastModified": 1774263116, - "narHash": "sha256-KI21iegJ1ogHHyPeU8tVbmsSpctVnnxSmj7iAgiop/Y=", + "lastModified": 1774455360, + "narHash": "sha256-hf10epVwnxOALnx2lRlfl4d9xW9BN/5GKxMIyRkhi6g=", "owner": "logos-co", "repo": "logos-standalone-app", - "rev": "97c21a1e4d1e87a34a7e055cfe3949b4e352fcdb", + "rev": "bcf255b7df3a1cbb7be1e2daf0a694f0270dc1b2", "type": "github" }, "original": { @@ -532,7 +682,7 @@ "nix-bundle-appimage": { "inputs": { "nix-bundle-dir": "nix-bundle-dir", - "nixpkgs": "nixpkgs_11" + "nixpkgs": "nixpkgs_14" }, "locked": { "lastModified": 1772047346, @@ -551,7 +701,7 @@ "nix-bundle-appimage_2": { "inputs": { "nix-bundle-dir": "nix-bundle-dir_3", - "nixpkgs": "nixpkgs_15" + "nixpkgs": "nixpkgs_18" }, "locked": { "lastModified": 1772881966, @@ -594,7 +744,7 @@ }, "nix-bundle-dir_2": { "inputs": { - "nixpkgs": "nixpkgs_12" + "nixpkgs": "nixpkgs_15" }, "locked": { "lastModified": 1771971384, @@ -635,7 +785,7 @@ }, "nix-bundle-dir_4": { "inputs": { - "nixpkgs": "nixpkgs_16" + "nixpkgs": "nixpkgs_19" }, "locked": { "lastModified": 1771971384, @@ -685,11 +835,11 @@ }, "nixpkgs_11": { "locked": { - "lastModified": 1771848320, - "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2fc6539b481e1d2569f25f8799236694180c0993", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", "type": "github" }, "original": { @@ -701,11 +851,11 @@ }, "nixpkgs_12": { "locked": { - "lastModified": 1770562336, - "narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=", + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", "type": "github" }, "original": { @@ -732,6 +882,38 @@ } }, "nixpkgs_14": { + "locked": { + "lastModified": 1771848320, + "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2fc6539b481e1d2569f25f8799236694180c0993", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_15": { + "locked": { + "lastModified": 1770562336, + "narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_16": { "locked": { "lastModified": 1759036355, "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", @@ -747,7 +929,23 @@ "type": "github" } }, - "nixpkgs_15": { + "nixpkgs_17": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_18": { "locked": { "lastModified": 1771848320, "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", @@ -763,7 +961,7 @@ "type": "github" } }, - "nixpkgs_16": { + "nixpkgs_19": { "locked": { "lastModified": 1770562336, "narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=", @@ -909,7 +1107,8 @@ }, "root": { "inputs": { - "logos-module-builder": "logos-module-builder", + "calc_module": "calc_module", + "logos-module-builder": "logos-module-builder_2", "logos-standalone-app": "logos-standalone-app" } } diff --git a/logos-developer-guide.md b/logos-developer-guide.md index 2e8ee93..10befb8 100644 --- a/logos-developer-guide.md +++ b/logos-developer-guide.md @@ -13,32 +13,29 @@ A comprehensive guide to creating, building, testing, packaging, and distributin - [1.3 The metadata.json Configuration](#13-the-metadatajson-configuration) - [1.4 Writing Module Code](#14-writing-module-code) - [1.5 Building Your Module](#15-building-your-module) -- [Part 2: Inspecting and Testing Your Module](#part-2-inspecting-and-testing-your-module) +- [Part 2: Inspecting Your Module](#part-2-inspecting-your-module) - [2.1 The lm CLI Tool](#21-the-lm-cli-tool) - - [2.2 Running with logoscore](#22-running-with-logoscore) - - [2.3 The logos-module-viewer](#23-the-logos-module-viewer) + - [2.2 The logos-module-viewer](#22-the-logos-module-viewer) - [Part 3: Packaging Your Module](#part-3-packaging-your-module) - [3.1 The LGX Package Format](#31-the-lgx-package-format) - - [3.2 Creating a Package with lgx](#32-creating-a-package-with-lgx) - - [3.3 Verifying Packages](#33-verifying-packages) + - [3.2 Bundling with nix-bundle-lgx](#32-bundling-with-nix-bundle-lgx) - [Part 4: Installing and Managing Modules](#part-4-installing-and-managing-modules) - [4.1 The lgpm CLI](#41-the-lgpm-cli) - [4.2 Installing from Local Files](#42-installing-from-local-files) - [4.3 Installing from a Registry](#43-installing-from-a-registry) -- [Part 5: Running in logos-basecamp](#part-5-running-in-logos-basecamp) - - [5.1 Building logos-basecamp](#51-building-logos-basecamp) - - [5.2 Module Types in logos-basecamp](#52-module-types-in-logos-basecamp) - - [5.3 Development Mode](#53-development-mode) -- [Part 6: Inter-Module Communication](#part-6-inter-module-communication) - - [6.1 The LogosAPI](#61-the-logosapi) - - [6.2 The C++ SDK Code Generator](#62-the-c-sdk-code-generator) - - [6.3 LogosResult](#63-logosresult) - - [6.4 Communication Modes](#64-communication-modes) -- [Part 7: Advanced Topics](#part-7-advanced-topics) - - [7.1 Wrapping External Libraries](#71-wrapping-external-libraries) - - [7.2 UI Modules (C++ Widgets)](#72-ui-modules-c-widgets) - - [7.3 UI Modules (QML)](#73-ui-modules-qml) - - [7.4 Module Dependencies](#74-module-dependencies) +- [Part 5: Running Your Module](#part-5-running-your-module) + - [5.1 Running with logoscore](#51-running-with-logoscore) +- [Part 6: Running in logos-basecamp](#part-6-running-in-logos-basecamp) + - [6.1 Building logos-basecamp](#61-building-logos-basecamp) + - [6.2 Module Types in logos-basecamp](#62-module-types-in-logos-basecamp) +- [Part 7: Inter-Module Communication](#part-7-inter-module-communication) + - [7.1 The LogosAPI](#71-the-logosapi) + - [7.2 The C++ SDK Code Generator](#72-the-c-sdk-code-generator) + - [7.3 LogosResult](#73-logosresult) + - [7.4 Communication Modes](#74-communication-modes) +- [Part 8: Advanced Topics](#part-8-advanced-topics) + - [8.1 Tutorials](#81-tutorials) + - [8.2 Module Dependencies](#82-module-dependencies) - [Reference: Repository Map](#reference-repository-map) - [Reference: CLI Tools Summary](#reference-cli-tools-summary) - [Troubleshooting](#troubleshooting) @@ -95,6 +92,7 @@ The **Logos platform** is a modular application framework built in C++ on top of | **logos-logoscore-cli** | [logos-co/logos-logoscore-cli](https://github.com/logos-co/logos-logoscore-cli) | Headless CLI runtime (`logoscore`) | | **logos-package** | [logos-co/logos-package](https://github.com/logos-co/logos-package) | LGX package format library + `lgx` CLI | | **logos-package-manager-module** | [logos-co/logos-package-manager-module](https://github.com/logos-co/logos-package-manager-module) | Package manager module + `lgpm` CLI | +| **logos-standalone-app** | [logos-co/logos-standalone-app](https://github.com/logos-co/logos-standalone-app) | Minimal shell for running/testing UI modules in isolation | | **logos-basecamp** | [logos-co/logos-basecamp](https://github.com/logos-co/logos-basecamp) | Desktop application shell | ## Prerequisites @@ -130,17 +128,36 @@ The fastest way to create a new module is using the **logos-module-builder** tem # Create a new directory for your module mkdir logos-my-module && cd logos-my-module -# Scaffold a minimal module (no external dependencies) +# Scaffold a minimal core module (no external dependencies) nix flake init -t github:logos-co/logos-module-builder # Or scaffold a module that wraps an external C/C++ library nix flake init -t github:logos-co/logos-module-builder#with-external-lib + +# For UI modules (C++ Qt widget with logos-standalone-app runner) +nix flake init -t github:logos-co/logos-module-builder#ui-module + +# For QML UI modules (with logos-standalone-app runner) +nix flake init -t github:logos-co/logos-module-builder#ui-qml-module ``` +**Available templates:** + +| Template | Use Case | +|----------|----------| +| `default` | Minimal core module (C++ backend, no UI) | +| `with-external-lib` | Core module wrapping an external C/C++ library | +| `ui-module` | C++ Qt widget UI module with `logos-standalone-app` runner | +| `ui-qml-module` | QML-based UI module with `logos-standalone-app` runner | + +The `ui-module` and `ui-qml-module` templates include `logos-standalone-app` as an input, enabling `nix run` to launch and test your UI plugin in isolation without the full logos-basecamp shell. + This generates a ready-to-build project with all the boilerplate handled for you. ### 1.2 Project Structure +> We will use the `default` template here (minimal core module). + After scaffolding, your module directory looks like this: ``` @@ -156,9 +173,32 @@ logos-my-module/ The key insight: **logos-module-builder** reduces ~600 lines of configuration across 5+ files down to ~70 lines across 2-3 files. `metadata.json` serves as the single source of truth — it contains both the runtime metadata (embedded into the plugin binary by Qt) and the build configuration (read by the builder via the `nix` section). +The `CMakeLists.txt` is minimal -- it includes `LogosModule.cmake` (provided by the builder) and calls the `logos_module()` macro, which sets up the Qt plugin target, links the SDK, configures include paths, and handles code generation. You just list your source files: + +```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) +elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/LogosModule.cmake") + include(cmake/LogosModule.cmake) +else() + message(FATAL_ERROR "LogosModule.cmake not found") +endif() + +logos_module( + NAME my_module + SOURCES + src/my_module_interface.h + src/my_module_plugin.h + src/my_module_plugin.cpp +) +``` + ### 1.3 The metadata.json Configuration -The `metadata.json` file is the single source of truth for your module. It contains both the runtime metadata and the build configuration (read by `logos-module-builder` via the `nix` section). +The `metadata.json` file is the single source of truth for your module. It is embedded into the plugin binary by Qt's `Q_PLUGIN_METADATA` macro (for runtime metadata), read by `logos-module-builder` to configure the Nix build, used by CMake to resolve external dependencies and link libraries (via the `nix` section), and used by `nix-bundle-lgx` to generate the LGX manifest. ```json { @@ -196,7 +236,7 @@ The `metadata.json` file is the single source of truth for your module. It conta | `category` | No | `general` | Category (general, network, chat, wallet, integration) | | `description` | No | `"A Logos module"` | Human-readable description | | `main` | Yes | -- | Plugin entry point (plugin name for core/ui, `Main.qml` for QML) | -| `dependencies` | No | `[]` | Other Logos module names this depends on | +| `dependencies` | No | `[]` | Other Logos module names this depends on. Each entry must match the `name` field in that dependency's `metadata.json`. | | `nix.packages.build` | No | `[]` | Nix packages for build time | | `nix.packages.runtime` | No | `[]` | Nix packages for runtime | | `nix.external_libraries` | No | `[]` | External C/C++ libraries to link | @@ -314,6 +354,9 @@ int MyModulePlugin::compute(int a, int b) ### 1.5 Building Your Module ```bash +# Nix requires all source files to be tracked by git +git init && git add -A + # Build everything (library + generated SDK headers) nix build @@ -336,16 +379,16 @@ cmake --build build ``` result/ ├── lib/ -│ └── my_module_plugin.so # (or .dylib on macOS) -├── include/ -│ └── ... # Generated SDK headers -└── share/ - └── metadata.json # Runtime metadata +│ ├── my_module_plugin.so # (or .dylib on macOS) +│ └── metadata.json # Runtime metadata +└── include/ + ├── my_module_api.h # Generated type-safe wrapper header + └── my_module_api.cpp # Generated wrapper implementation ``` --- -## Part 2: Inspecting and Testing Your Module +## Part 2: Inspecting Your Module ### 2.1 The `lm` CLI Tool @@ -415,47 +458,7 @@ Example JSON output: ] ``` -### 2.2 Running with `logoscore` - -The **`logoscore`** CLI (from `logos-logoscore-cli`) is a headless runtime that can load modules and invoke their methods from the command line. - -#### Building logoscore - -```bash -nix build 'github:logos-co/logos-logoscore-cli' --out-link ./logos -``` - -#### Running a Module - -```bash -# Load a module from a directory -./logos/bin/logoscore \ - -m ./modules \ - --load-modules my_module - -# Load a module and call a method -./logos/bin/logoscore \ - -m ./modules \ - --load-modules my_module \ - -c "my_module.doSomething(hello)" - -# Load a module and call a method with a JSON config file -./logos/bin/logoscore \ - -m ./modules \ - --load-modules my_module \ - -c "my_module.configure(@config.json)" -``` - -**Flags:** - -| Flag | Description | -|------|-------------| -| `-m ` | Directory containing module libraries | -| `--load-modules ` | Comma-separated list of modules to load | -| `-c ".(args)"` | Command to execute after loading | -| `@file.json` | Pass a JSON file as a method argument | - -### 2.3 The logos-module-viewer +### 2.2 The logos-module-viewer The **logos-module-viewer** is a graphical tool for inspecting loaded modules. @@ -473,122 +476,63 @@ This opens a window showing the module's metadata, methods, and allows interacti ## Part 3: Packaging Your Module +Before you can run your module with `logoscore` or install it into `logos-basecamp`, you need to package the build output into an `.lgx` package and install it into a `modules/` directory. + ### 3.1 The LGX Package Format Logos modules are distributed as **`.lgx` packages**. An LGX file is a gzip-compressed tar archive with a specific internal structure: ``` -manifest.json # Package metadata (required) -manifest.cose # Optional cryptographic signature -variants/ # Platform-specific builds (required) - linux-x86_64/ - my_module_plugin.so - darwin-arm64/ - my_module_plugin.dylib -docs/ # Optional documentation -licenses/ # Optional license files +mymodule.lgx (tar.gz) +├── manifest.json # Package metadata +├── variants/ +│ ├── linux-amd64/ +│ │ └── my_module_plugin.so +│ ├── darwin-arm64/ +│ │ └── my_module_plugin.dylib +│ └── darwin-arm64-dev/ +│ └── my_module_plugin.dylib +├── docs/ # Optional +└── licenses/ # Optional ``` -The **manifest.json** declares the package name, version, and maps each variant to its main entry point (the shared library file): +The **manifest.json** is auto-generated from your module's `metadata.json` by the bundler. It maps each variant to its main entry point. -```json -{ - "name": "my_module", - "version": "1.0.0", - "description": "My first Logos module", - "author": "Developer Name", - "type": "core", - "category": "general", - "manifestVersion": "0.1", - "main": { - "linux-x86_64": "my_module_plugin.so", - "darwin-arm64": "my_module_plugin.dylib" - }, - "dependencies": [] -} -``` +### 3.2 Bundling with nix-bundle-lgx -### 3.2 Creating a Package with `lgx` - -The **`lgx`** CLI tool (from `logos-package`) creates and manages LGX packages. - -#### Building lgx +The recommended way to create `.lgx` packages is using **nix-bundle-lgx**, which automatically bundles your `nix build` output into an `.lgx` file with the correct manifest and variant structure. ```bash -nix build 'github:logos-co/logos-package#lgx' --out-link ./lgx +# Dev variant (uses /nix/store references, for local development) +nix bundle --bundler github:logos-co/nix-bundle-lgx .#lib + +# Portable variant (self-contained, all dependencies bundled) +nix bundle --bundler github:logos-co/nix-bundle-lgx#portable .#lib + +# Dual variant (both dev and portable in one .lgx file) +nix bundle --bundler github:logos-co/nix-bundle-lgx#dual .#lib ``` -#### Creating a New Package +This produces a `my_module-.lgx` file in the current directory. -```bash -# Create an empty package skeleton -./lgx/bin/lgx create my_module.lgx --name my_module -``` +**Bundling modes:** -#### Adding Platform Variants +| Mode | Command | Variant Created | Use Case | +|------|---------|----------------|----------| +| **Dev** | `nix bundle --bundler ...#default .#lib` | `darwin-arm64-dev` | Local development (requires Nix store) | +| **Portable** | `nix bundle --bundler ...#portable .#lib` | `darwin-arm64` | Distribution (self-contained, no Nix needed) | +| **Dual** | `nix bundle --bundler ...#dual .#lib` | Both dev and portable | One package for both environments | -```bash -# Add a single-file variant (the library binary) -./lgx/bin/lgx add-variant my_module.lgx \ - --variant linux-x86_64 \ - --files ./result/lib/my_module_plugin.so +**Variant naming:** -# Add a macOS variant -./lgx/bin/lgx add-variant my_module.lgx \ - --variant darwin-arm64 \ - --files ./result-macos/lib/my_module_plugin.dylib +| Nix System | Dev Variant | Portable Variant | +|-----------|------------|-----------------| +| `aarch64-darwin` | `darwin-arm64-dev` | `darwin-arm64` | +| `x86_64-darwin` | `darwin-amd64-dev` | `darwin-amd64` | +| `aarch64-linux` | `linux-arm64-dev` | `linux-arm64` | +| `x86_64-linux` | `linux-amd64-dev` | `linux-amd64` | -# Add a directory variant (if your module has multiple files) -./lgx/bin/lgx add-variant my_module.lgx \ - --variant linux-x86_64 \ - --files ./result/lib/ \ - --main my_module_plugin.so -``` - -**Variant naming convention:** `-` (lowercase). Common variants: - -| Variant | Platform | -|---------|----------| -| `linux-x86_64` | Linux Intel/AMD 64-bit | -| `linux-arm64` | Linux ARM 64-bit | -| `darwin-arm64` | macOS Apple Silicon | -| `darwin-x86_64` | macOS Intel | - -#### Removing a Variant - -```bash -./lgx/bin/lgx remove-variant my_module.lgx --variant linux-x86_64 -``` - -#### Listing Package Contents - -```bash -./lgx/bin/lgx list my_module.lgx -``` - -#### Extracting a Package - -```bash -# Extract a specific variant -./lgx/bin/lgx extract my_module.lgx --variant linux-x86_64 --output ./extracted/ - -# Extract all variants -./lgx/bin/lgx extract my_module.lgx --all --output ./extracted/ -``` - -### 3.3 Verifying Packages - -```bash -./lgx/bin/lgx verify my_module.lgx -``` - -This checks: -- Package structure is valid (manifest.json exists, variants/ directory exists) -- Manifest fields are present and valid -- Every variant listed in `main` has a corresponding directory and file -- Every variant directory has a corresponding `main` entry -- No forbidden files (symlinks, special files) are present -- All paths are valid (no `..` traversal, no absolute paths) +> **Important:** The variant type matters when installing into `logos-basecamp`. A dev build of basecamp expects dev variants, and a portable build expects portable variants. Use the `dual` bundler to produce packages that work with both. --- @@ -596,7 +540,7 @@ This checks: ### 4.1 The `lgpm` CLI -The **`lgpm`** CLI (Logos Package Manager) installs, searches, and manages module packages. +The **`lgpm`** CLI (Logos Package Manager) installs, searches, and manages module packages. Installing a package extracts it into a `modules/` directory that `logoscore` and `logos-basecamp` can load from. #### Building lgpm @@ -608,31 +552,31 @@ nix build 'github:logos-co/logos-package-manager-module#cli' --out-link ./packag ```bash # Search for packages -lgpm search waku +./package-manager/bin/lgpm search waku # List all available packages -lgpm list +./package-manager/bin/lgpm list # List only installed packages -lgpm list --installed +./package-manager/bin/lgpm list --installed # List packages in a category -lgpm list --category networking +./package-manager/bin/lgpm list --category networking # Show package details -lgpm info my_module +./package-manager/bin/lgpm info my_module # List available categories -lgpm categories +./package-manager/bin/lgpm categories # Install a package (with dependency resolution) -lgpm install my_module +./package-manager/bin/lgpm --modules-dir ./modules install my_module # Install multiple packages -lgpm install my_module another_module +./package-manager/bin/lgpm --modules-dir ./modules install my_module another_module # Install from a local .lgx file -lgpm install --file ./my_module.lgx +./package-manager/bin/lgpm --modules-dir ./modules install --file ./my_module.lgx ``` #### Global Options @@ -648,8 +592,18 @@ lgpm install --file ./my_module.lgx ### 4.2 Installing from Local Files ```bash -# Install a locally built .lgx package -./package-manager/bin/lgpm --modules-dir ./modules install --file ./my_module.lgx +# Install a locally built .lgx package into a modules/ directory +./package-manager/bin/lgpm --modules-dir ./modules install --file ./my_module-1.0.0.lgx +``` + +After installation, the `modules/` directory contains your extracted module: + +``` +modules/ +└── my_module/ + ├── manifest.json + ├── my_module_plugin.dylib # (or .so on Linux) + └── variant ``` ### 4.3 Installing from a Registry @@ -670,24 +624,136 @@ The package manager automatically: --- -## Part 5: Running in logos-basecamp +## Part 5: Running Your Module -### 5.1 Building logos-basecamp +Once your module is packaged and installed into a `modules/` directory (see Parts 3 and 4), you can run it with `logoscore`. + +### 5.1 Running with `logoscore` + +The **`logoscore`** CLI (from `logos-liblogos`) is a headless runtime that can load modules and invoke their methods from the command line. + +#### Building logoscore ```bash -# Build the full application -nix build 'github:logos-co/logos-basecamp#app' --out-link ./logos-basecamp - -# Run it -./logos-basecamp/bin/logos-basecamp - -# Or build platform-specific distributions: -nix build 'github:logos-co/logos-basecamp#bin-appimage' # Linux AppImage -nix build 'github:logos-co/logos-basecamp#bin-macos-app' # macOS .app bundle -nix build 'github:logos-co/logos-basecamp#bin-macos-dmg' # macOS DMG +nix build 'github:logos-co/logos-logoscore-cli' --out-link ./logos ``` -### 5.2 Module Types in logos-basecamp +#### Daemon Mode + +`logoscore` runs as a daemon that stays alive to host modules. Start it with `-D`: + +```bash +# Start the daemon with a modules directory +./logos/bin/logoscore -D -m ./modules +``` + +Once the daemon is running, use commands from another terminal: + +```bash +# Load a module +./logos/bin/logoscore load-module my_module + +# Call a method on a loaded module +./logos/bin/logoscore call my_module doSomething hello + +# List loaded modules +./logos/bin/logoscore list-modules --loaded + +# Show module details +./logos/bin/logoscore module-info my_module + +# Watch events from a module +./logos/bin/logoscore watch my_module + +# Show daemon and module health +./logos/bin/logoscore status + +# Stop the daemon +./logos/bin/logoscore stop +``` + +#### Inline Mode (Legacy) + +For one-shot execution (load, call, exit), use the legacy inline flags: + +```bash +# Load a module and call a method +./logos/bin/logoscore \ + -m ./modules \ + --load-modules my_module \ + -c "my_module.doSomething(hello)" + +# Multiple sequential calls +./logos/bin/logoscore \ + -m ./modules \ + -l my_module \ + -c "my_module.init(@config.json)" \ + -c "my_module.start()" + +# Exit immediately after calls complete +./logos/bin/logoscore \ + -m ./modules -l my_module \ + -c "my_module.doSomething(hello)" \ + --quit-on-finish +``` + +> **Note:** Without `-c` or `--quit-on-finish`, logoscore enters the Qt event loop and stays running (daemon behavior). Always use `-c` for one-shot execution. + +**Inline mode flags:** + +| Flag | Description | +|------|-------------| +| `-m, --modules-dir ` | Directory containing module libraries (repeatable) | +| `-l, --load-modules ` | Comma-separated list of modules to load | +| `-c ".(args)"` | Call a method after loading (repeatable, sequential) | +| `--quit-on-finish` | Exit after all `-c` calls complete | +| `@file.json` | Pass a file's contents as a method argument | + +**Daemon commands:** + +| Command | Description | +|---------|-------------| +| `status` | Show daemon and module health | +| `load-module ` | Load a module into the daemon | +| `unload-module ` | Unload a module | +| `reload-module ` | Reload (unload + load) a module | +| `list-modules [--loaded]` | List available or loaded modules | +| `module-info ` | Show detailed module information | +| `call [args]` | Call a method on a loaded module | +| `watch [--event]` | Watch events from a module | +| `stats` | Show module resource usage | +| `stop` | Stop the daemon | + +--- + +## Part 6: Running in logos-basecamp + +### 6.1 Building logos-basecamp + +logos-basecamp produces two binary variants: + +- **`logos-basecamp`** -- development build (shell wrapper that sets Qt environment variables, depends on `/nix/store`) +- **`LogosBasecamp`** -- portable binary (self-contained, used in distributed builds and `.app` bundles) + +```bash +# Build the development version +nix build 'github:logos-co/logos-basecamp#app' --out-link ./logos-basecamp + +# Run the dev binary +./logos-basecamp/bin/logos-basecamp + +# Build the portable/distributed version +nix build 'github:logos-co/logos-basecamp#portable' --out-link ./logos-basecamp-portable + +# Or build platform-specific distributions: +nix build 'github:logos-co/logos-basecamp#bin-bundle-dir' # Flat directory bundle +nix build 'github:logos-co/logos-basecamp#bin-appimage' # Linux AppImage +nix build 'github:logos-co/logos-basecamp#bin-macos-app' # macOS .app bundle +``` + +> **Note:** When installing modules into logos-basecamp, the LGX variant type must match the build type. Dev builds of basecamp expect **dev** LGX variants (e.g., `darwin-arm64-dev`), while portable builds expect **portable** variants (e.g., `darwin-arm64`). Use the `dual` bundler (see [3.2](#32-bundling-with-nix-bundle-lgx)) to produce packages that work with both. + +### 6.2 Module Types in logos-basecamp The application supports three types of modules: @@ -726,25 +792,11 @@ These provide QML-based UIs in a sandboxed environment: - Filesystem access is restricted to the module's own directory - Can call core modules via the `logos` bridge: `logos.callModule("module", "method", [args])` -### 5.3 Development Mode - -For rapid iteration on QML UI modules, use the development mode launcher: - -```bash -# Build once -nix build 'github:logos-co/logos-basecamp' - -# Run with live QML reloading (edits to .qml files take effect immediately) -./run-dev.sh -``` - -This sets `QML_UI` to point to the source directory and disables QML caching, so you can edit QML files and see changes without rebuilding. - --- -## Part 6: Inter-Module Communication +## Part 7: Inter-Module Communication -### 6.1 The LogosAPI +### 7.1 The LogosAPI Every module receives a `LogosAPI*` pointer when `initLogos()` is called. This is your gateway to communicating with other modules. @@ -756,19 +808,45 @@ void MyModulePlugin::initLogos(LogosAPI* logosAPIInstance) // Get a client for calling another module LogosAPIClient* client = logosAPI->getClient("other_module"); - // Call a method on that module + // Synchronous call (blocks until result is returned) QVariant result = client->invokeRemoteMethod( "other_module", // target module name "someMethod", // method name arg1, arg2 // arguments (up to 5 positional args) ); + + // Async call (preferred -- non-blocking, result delivered via callback) + client->invokeRemoteMethodAsync( + "other_module", + "someMethod", + [](QVariant result) { + // Handle result (called on the main thread) + if (result.isValid()) { + qDebug() << "Got result:" << result; + } + }, + arg1, arg2 + ); } ``` -### 6.2 The C++ SDK Code Generator +> **Prefer async calls.** Synchronous `invokeRemoteMethod` blocks the caller's thread until the remote module responds. Use `invokeRemoteMethodAsync` to avoid blocking, especially in UI modules. + +### 7.2 The C++ SDK Code Generator The `logos-cpp-generator` tool (from `logos-cpp-sdk`) inspects a compiled module and generates typed C++ wrapper classes, so you get compile-time type safety instead of raw `invokeRemoteMethod` calls. +#### Getting logos-cpp-generator + +The generator is bundled with `logos-cpp-sdk`. It is automatically available: + +- **In `nix develop`** -- the module dev shell includes the SDK on PATH +- **Build it directly:** + ```bash + nix build 'github:logos-co/logos-cpp-sdk#cpp-generator' --out-link ./cpp-gen + ./cpp-gen/bin/logos-cpp-generator --help + ``` + #### Generating Wrappers ```bash @@ -787,7 +865,7 @@ logos-cpp-generator --metadata metadata.json --general-only --output-dir ./gener #### Using Generated Wrappers -After generation, you get typed wrapper classes: +After generation, you get typed wrapper classes with both synchronous and asynchronous methods: ```cpp #include "logos_sdk.h" // Umbrella header @@ -799,15 +877,21 @@ void MyModulePlugin::initLogos(LogosAPI* api) { // Create the typed SDK wrapper LogosModules* logos = new LogosModules(api); - // Call other modules with type safety + // Synchronous call (blocks until result) QString result = logos->other_module.doSomething("hello"); - bool ok = logos->core_manager.loadPlugin("another_module"); + + // Async call (preferred -- non-blocking) + logos->other_module.doSomethingAsync("hello", [](QVariant result) { + qDebug() << "Got:" << result; + }); } ``` -The generated `LogosModules` struct provides a member for each module, with methods matching the module's `Q_INVOKABLE` methods. +The generated `LogosModules` struct provides a member for each module, with methods matching the module's `Q_INVOKABLE` methods. For every method `foo()`, an async variant `fooAsync()` is also generated that takes a callback parameter. -### 6.3 LogosResult +> **Prefer async wrappers.** Use `doSomethingAsync(...)` instead of `doSomething(...)` to avoid blocking the caller's thread. Synchronous calls can cause hangs if the target module is slow to respond. + +### 7.3 LogosResult Many module methods return `LogosResult` for structured success/error handling: @@ -818,12 +902,16 @@ if (result.success) { // Access the value QString value = result.getString(); int number = result.getInt(); + bool flag = result.getBool(); QVariantMap map = result.getMap(); QVariantList list = result.getList(); - // Access nested values + // Access nested values by key (for map results) QString name = result.getString("name"); int count = result.getInt("count", 0); // with default + + // Generic typed access + auto custom = result.getValue(); } else { // Access the error QString error = result.getError(); @@ -846,7 +934,7 @@ Q_INVOKABLE LogosResult MyModulePlugin::fetchData(const QString& id) { } ``` -### 6.4 Communication Modes +### 7.4 Communication Modes The SDK supports two communication modes: @@ -867,182 +955,17 @@ LogosModeConfig::setMode(LogosMode::Remote); --- -## Part 7: Advanced Topics +## Part 8: Advanced Topics -### 7.1 Wrapping External Libraries +### 8.1 Tutorials -To create a module that wraps an external C/C++ library, use the external library template: +For hands-on walkthroughs of module development patterns, see the dedicated tutorials: -```bash -nix flake init -t github:logos-co/logos-module-builder#with-external-lib -``` +- **[Wrapping a C Library](tutorial-wrapping-c-library.md)** — create `calc_module` wrapping a vendored C library. Covers external library configuration in `metadata.json`. +- **[Building a QML UI App](tutorial-qml-ui-app.md)** — create `calc_ui`, a QML-only UI plugin that calls a core module via the `logos.callModule()` bridge. +- **[Building a C++ UI Module](tutorial-cpp-ui-app.md)** — create `calc_ui_cpp`, a native C++ Qt widget plugin using `LogosAPI*` and the generated SDK. -Then configure the external library in the `nix` section of `metadata.json`: - -```json -{ - "name": "my_wrapper_module", - "version": "1.0.0", - "description": "Wraps libfoo for Logos", - "main": "my_wrapper_module_plugin", - "dependencies": [], - - "nix": { - "external_libraries": [ - { - "name": "libfoo", - "flake_input": "github:example/libfoo", - "output_pattern": "lib/libfoo.*" - } - ] - } -} -``` - -For a vendored library, use `vendor_path` and `build_command`: - -```json -{ - "nix": { - "external_libraries": [ - { - "name": "libfoo", - "vendor_path": "vendor/libfoo", - "build_command": "make", - "output_pattern": "build/lib/libfoo.*" - } - ] - } -} -``` - -For a Go library with C bindings: - -```json -{ - "nix": { - "external_libraries": [ - { - "name": "libfoo", - "vendor_path": "vendor/libfoo", - "go_build": true, - "output_pattern": "libfoo.*" - } - ] - } -} -``` - -The builder handles downloading, building, and linking the external library into your module. - -### 7.2 UI Modules (C++ Widgets) - -To create a module with a native Qt widget UI: - -1. Implement the `IComponent` interface -2. Set `"type": "ui"` in your metadata -3. Return a `QWidget*` from `createWidget()` - -`IComponent.h` is not part of the SDK — each UI module vendors its own copy in `interfaces/IComponent.h`: - -```cpp -// interfaces/IComponent.h (copy verbatim into your module) -#pragma once -#include -#include -#include - -class LogosAPI; - -class IComponent { -public: - virtual ~IComponent() = default; - virtual QWidget* createWidget(LogosAPI* logosAPI = nullptr) = 0; - virtual void destroyWidget(QWidget* widget) = 0; -}; - -#define IComponent_iid "com.logos.component.IComponent" -Q_DECLARE_INTERFACE(IComponent, IComponent_iid) -``` - -Expose it via `INCLUDE_DIRS` in your `CMakeLists.txt`: - -```cmake -logos_module( - NAME my_ui_module - SOURCES src/my_ui_plugin.h src/my_ui_plugin.cpp - INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces -) -``` - -Then implement the plugin: - -```cpp -#include - -class MyUIPlugin : public QObject, public IComponent -{ - Q_OBJECT - Q_INTERFACES(IComponent) - Q_PLUGIN_METADATA(IID IComponent_iid FILE "metadata.json") - -public: - Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr) override { - auto* widget = new QWidget(); - // Build your UI here - return widget; - } - - void destroyWidget(QWidget* widget) override { - delete widget; - } -}; -``` - -### 7.3 UI Modules (QML) - -For a QML-based UI module, create a directory with: - -``` -my_qml_module/ -├── manifest.json -├── metadata.json -└── Main.qml -``` - -**manifest.json:** -```json -{ - "type": "ui_qml", - "main": "Main.qml", - "name": "my_qml_module", - "version": "1.0.0" -} -``` - -**Main.qml:** -```qml -import QtQuick 2.15 -import QtQuick.Controls 2.15 - -Item { - width: 400 - height: 300 - - Button { - text: "Call Core Module" - onClicked: { - // logos bridge is injected by the host - var result = logos.callModule("my_module", "doSomething", ["hello"]) - console.log("Result:", result) - } - } -} -``` - -QML modules are sandboxed: no network access, no filesystem access outside the module directory. - -### 7.4 Module Dependencies +### 8.2 Module Dependencies Declare dependencies in your `metadata.json`: @@ -1070,6 +993,7 @@ When your module is installed via `lgpm`, its dependencies are automatically res | [logos-logoscore-cli](https://github.com/logos-co/logos-logoscore-cli) | Headless CLI runtime | `logoscore` (CLI) | | [logos-package](https://github.com/logos-co/logos-package) | Package format | `lgx` (CLI), `liblgx` (library) | | [logos-package-manager-module](https://github.com/logos-co/logos-package-manager-module) | Package management | `lgpm` (CLI), `package_manager_plugin` | +| [logos-standalone-app](https://github.com/logos-co/logos-standalone-app) | Minimal UI module runner | `logos-standalone-app` (loads a single UI plugin for testing) | | [logos-basecamp](https://github.com/logos-co/logos-basecamp) | Desktop app shell | `LogosApp` (GUI), MDI workspace, plugin loader | ## Reference: CLI Tools Summary @@ -1077,36 +1001,36 @@ When your module is installed via `lgpm`, its dependencies are automatically res ### `lm` -- Module Inspector ```bash -lm metadata [--json] # View module metadata -lm methods [--json] # List Q_INVOKABLE methods +lm # Show metadata + methods +lm metadata [--json] # View module metadata +lm methods [--json] # List Q_INVOKABLE methods ``` ### `logoscore` -- Headless Runtime ```bash -logoscore -m --load-modules [-c ".(args)"] -``` +# Daemon mode +logoscore -D -m # Start daemon +logoscore load-module # Load a module +logoscore call [args] # Call a method +logoscore list-modules [--loaded] # List modules +logoscore module-info # Show module details +logoscore status # Daemon health +logoscore stop # Stop daemon -### `lgx` -- Package Tool - -```bash -lgx create --name # Create empty package -lgx add-variant --variant --files [--main ] -lgx remove-variant --variant -lgx list # List contents -lgx verify # Validate structure -lgx extract --variant --output # Extract +# Inline mode (legacy) +logoscore -m -l -c ".(args)" [--quit-on-finish] ``` ### `lgpm` -- Package Manager ```bash -lgpm search # Search packages -lgpm list [--category ] [--installed] # List packages -lgpm install [pkgs...] # Install with dependency resolution -lgpm install --file # Install local file -lgpm info # Package details -lgpm categories # List categories +./package-manager/bin/lgpm search # Search packages +./package-manager/bin/lgpm list [--category ] [--installed] # List packages +./package-manager/bin/lgpm install [pkgs...] # Install with dependency resolution +./package-manager/bin/lgpm install --file # Install local file +./package-manager/bin/lgpm info # Package details +./package-manager/bin/lgpm categories # List categories ``` ### `logos-cpp-generator` -- SDK Code Generator @@ -1117,6 +1041,14 @@ logos-cpp-generator --metadata --module-dir [--output-dir logos-cpp-generator --metadata --general-only [--output-dir ] ``` +### `nix-bundle-lgx` -- LGX Bundler + +```bash +nix bundle --bundler github:logos-co/nix-bundle-lgx .#lib # Dev variant +nix bundle --bundler github:logos-co/nix-bundle-lgx#portable .#lib # Portable variant +nix bundle --bundler github:logos-co/nix-bundle-lgx#dual .#lib # Both variants +``` + --- ## Troubleshooting @@ -1158,20 +1090,57 @@ Check that: ### lgpm install fails - Check your internet connection (lgpm fetches from GitHub Releases) -- Try specifying a release: `lgpm --release v1.0.0 install my_module` -- For local files: `lgpm install --file ./my_module.lgx` -- Check the target directory is writable: `lgpm --modules-dir ./modules install my_module` +- Try specifying a release: `./package-manager/bin/lgpm --release v1.0.0 install my_module` +- For local files: `./package-manager/bin/lgpm install --file ./my_module.lgx` +- Check the target directory is writable: `./package-manager/bin/lgpm --modules-dir ./modules install my_module` + +### Checking if a module loaded successfully + +Use `logoscore` to verify your module loads and its methods are callable: + +```bash +# Start daemon and load the module +./logos/bin/logoscore -D -m ./modules & + +# Check if the module is listed as loaded +./logos/bin/logoscore list-modules --loaded + +# Inspect the module +./logos/bin/logoscore module-info my_module + +# Or use inline mode for a quick check +./logos/bin/logoscore -m ./modules -l my_module -c "my_module.greet(test)" --quit-on-finish +``` + +If the module doesn't appear, check: +1. The `modules/` directory contains a subdirectory for your module with `manifest.json` and the plugin binary +2. The variant in the manifest matches your platform (e.g., `darwin-arm64-dev` for dev builds on Apple Silicon) +3. Use `lm` to verify the plugin binary is a valid Qt plugin: `./lm/bin/lm ./modules/my_module/my_module_plugin.dylib` + +### Capability module not found + +logos-basecamp requires the `capability` module to be installed. It is bundled as a preinstall `.lgx` package and installed on first launch. If you see errors about it: + +1. Check that the preinstall directory exists: `ls ./logos-basecamp/preinstall/` +2. Check that the capability module was extracted to the modules directory +3. Verify the LGX variant type matches your basecamp build (dev variant for dev build, portable for portable build) + +### LGX variant mismatch + +If a module installs but fails to load, the variant type may not match: + +- **Dev build** of logos-basecamp needs **dev** LGX variants (`darwin-arm64-dev`) +- **Portable build** needs **portable** variants (`darwin-arm64`) +- Use `nix bundle --bundler github:logos-co/nix-bundle-lgx#dual .#lib` to produce packages with both variants ### Cross-platform builds -Build on each target platform separately, then add each binary as a variant to the same `.lgx` package: +Build on each target platform separately, then use `nix-bundle-lgx` to create `.lgx` packages: ```bash -# On Linux x86_64: -nix build .#lib -lgx add-variant my_module.lgx --variant linux-x86_64 --files ./result/lib/my_module_plugin.so +# On each platform, bundle produces the correct variant automatically: +nix bundle --bundler github:logos-co/nix-bundle-lgx#dual .#lib -# On macOS arm64: -nix build .#lib -lgx add-variant my_module.lgx --variant darwin-arm64 --files ./result/lib/my_module_plugin.dylib +# Then merge platform-specific .lgx files into one: +./lgx/bin/lgx merge my_module-linux.lgx my_module-macos.lgx -o my_module.lgx ``` diff --git a/tutorial-cpp-ui-app.md b/tutorial-cpp-ui-app.md index 5664617..0359ab6 100644 --- a/tutorial-cpp-ui-app.md +++ b/tutorial-cpp-ui-app.md @@ -106,6 +106,15 @@ mv src/ui_example_plugin.cpp src/calc_ui_cpp_plugin.cpp } ``` +Create the icon directory and add a placeholder icon. The icon is displayed in the `logos-basecamp` sidebar when the module is loaded: + +```bash +mkdir -p icons +# Copy any PNG here — or use a placeholder: +convert -size 64x64 xc:'#4a90d9' icons/calc.png 2>/dev/null \ + || printf '\x89PNG\r\n\x1a\n' > icons/calc.png +``` + > **Naming convention:** Each entry in `dependencies` must match the `name` field in that module's own `metadata.json`. When adding a dependency as a flake input, the **input attribute name** must also match — e.g., `calc_module.url = "github:logos-co/logos-tutorial?dir=logos-calc-module"`. The URL can point to any repo, but the attribute name is how the builder resolves dependencies. --- @@ -136,7 +145,7 @@ find_package(Qt6 REQUIRED COMPONENTS Widgets) target_link_libraries(calc_ui_cpp_module_plugin PRIVATE Qt6::Widgets) ``` -> For Option A (QML inside the plugin) you will add `Quick QuickWidgets` and `qt_add_resources` — covered in [Step 6](#step-6-option-a--qml-loaded-from-c). +> For Option A (QML inside the plugin) you will add `Quick QuickWidgets` and `qt_add_resources` — covered in [Step 7](#step-7-option-a--qml-loaded-from-c). --- @@ -146,6 +155,8 @@ target_link_libraries(calc_ui_cpp_module_plugin PRIVATE Qt6::Widgets) #ifndef CALC_UI_CPP_INTERFACE_H #define CALC_UI_CPP_INTERFACE_H +#include +#include #include "interface.h" class CalcUiCppInterface : public PluginInterface @@ -162,7 +173,52 @@ Q_DECLARE_INTERFACE(CalcUiCppInterface, CalcUiCppInterface_iid) --- -## Step 5: Backend Class +## Step 5: Plugin Header (`src/calc_ui_cpp_plugin.h`) + +Replace the scaffolded plugin header. This header is the same for both Option A and Option B — only the `.cpp` implementation differs: + +```cpp +#ifndef CALC_UI_CPP_PLUGIN_H +#define CALC_UI_CPP_PLUGIN_H + +#include +#include +#include +#include "calc_ui_cpp_interface.h" + +class LogosAPI; + +class CalcUiCppPlugin : public QObject, public CalcUiCppInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID CalcUiCppInterface_iid FILE "metadata.json") + Q_INTERFACES(CalcUiCppInterface PluginInterface) + +public: + explicit CalcUiCppPlugin(QObject* parent = nullptr); + ~CalcUiCppPlugin() override; + + QString name() const override { return "calc_ui_cpp"; } + QString version() const override { return "1.0.0"; } + + Q_INVOKABLE void initLogos(LogosAPI* api); + + Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr); + Q_INVOKABLE void destroyWidget(QWidget* widget); + +signals: + void eventResponse(const QString& eventName, const QVariantList& args); + +private: + LogosAPI* m_logosAPI = nullptr; +}; + +#endif // CALC_UI_CPP_PLUGIN_H +``` + +--- + +## Step 6: Backend Class The backend class is the key addition over the QML plugin. It holds a `LogosModules*` wrapper — a typed C++ SDK generated at build time from `metadata.json` — and exposes `Q_INVOKABLE` methods that call `calc_module` through it. Because the calls go through a generated typed class, argument types are preserved — no `QString`/`int` coercion issues. @@ -179,8 +235,6 @@ When `metadata.json` declares `"dependencies": ["calc_module"]` and `calc_module m_logos->calc_module.add(3, 5) // typed: int add(int, int) over IPC ``` -This is the same pattern used in production modules such as `logos-storage-ui`. - ### `src/calc_backend.h` ```cpp @@ -232,11 +286,11 @@ QString CalcBackend::libVersion() { return m_logos->calc_module.libVer --- -## Step 6: Option A — QML Loaded from C++ +## Step 7: Option A — QML Loaded from C++ The plugin loads `src/qml/Main.qml` into a `QQuickWidget` and exposes `CalcBackend` as a QML context property. The QML is identical in structure to `logos-calc-ui/Main.qml` (Part 2), but calls `backend.*` methods directly instead of routing through the `logos.callModule()` IPC bridge — so argument types are preserved and there is no sandboxing overhead. -### 6.1 Add the QML file +### 7.1 Add the QML file Create `src/qml/Main.qml`. The structure mirrors `logos-calc-ui/Main.qml` exactly; the only difference is that buttons call `backend.*` methods directly instead of routing through `logos.callModule(...)`: @@ -314,7 +368,7 @@ Item { } ``` -### 6.2 Update `CMakeLists.txt` +### 7.2 Update `CMakeLists.txt` Add `Quick` and `QuickWidgets`, and embed the QML as a Qt resource: @@ -352,7 +406,7 @@ qt_add_resources(calc_ui_cpp_module_plugin "qml_resources" ) ``` -### 6.3 `createWidget()` — load QML +### 7.3 `createWidget()` — load QML Replace `calc_ui_cpp_plugin.cpp` with: @@ -406,15 +460,14 @@ void CalcUiCppPlugin::destroyWidget(QWidget* widget) } ``` -### 6.4 Dev Mode +### 7.4 Dev Mode When `QML_PATH` is set, the plugin loads `Main.qml` from disk instead of the embedded resource. You can edit QML layout, styling, and property bindings without a Nix rebuild — just restart the app to pick up changes. ```bash # Run with dev mode enabled QML_PATH=$PWD/src/qml \ - nix run . --override-input calc_module path:../logos-calc-module -- \ - --modules-dir ./modules + nix run . ``` > **What still requires a rebuild:** @@ -426,50 +479,11 @@ QML_PATH=$PWD/src/qml \ --- -## Step 7: Option B — Pure Qt Widget +## Step 8: Option B — Pure Qt Widget The plugin creates a standard Qt widget using layouts and connects button clicks to the backend. No QML, no additional Qt modules — just `Qt6::Widgets`. -### `src/calc_ui_cpp_plugin.h` - -```cpp -#ifndef CALC_UI_CPP_PLUGIN_H -#define CALC_UI_CPP_PLUGIN_H - -#include -#include -#include -#include "calc_ui_cpp_interface.h" - -class LogosAPI; - -class CalcUiCppPlugin : public QObject, public CalcUiCppInterface -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID CalcUiCppInterface_iid FILE "metadata.json") - Q_INTERFACES(CalcUiCppInterface PluginInterface) - -public: - explicit CalcUiCppPlugin(QObject* parent = nullptr); - ~CalcUiCppPlugin() override; - - QString name() const override { return "calc_ui_cpp"; } - QString version() const override { return "1.0.0"; } - - Q_INVOKABLE void initLogos(LogosAPI* api); - - Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr); - Q_INVOKABLE void destroyWidget(QWidget* widget); - -signals: - void eventResponse(const QString& eventName, const QVariantList& args); - -private: - LogosAPI* m_logosAPI = nullptr; -}; - -#endif // CALC_UI_CPP_PLUGIN_H -``` +Replace `src/calc_ui_cpp_plugin.cpp` with: ### `src/calc_ui_cpp_plugin.cpp` @@ -569,7 +583,7 @@ void CalcUiCppPlugin::destroyWidget(QWidget* widget) --- -## Step 8: `flake.nix` +## Step 9: `flake.nix` Pass `logosStandalone` to `mkLogosModule` and you get `apps.default` (i.e. `nix run`) for free — no manual `apps` block required. @@ -599,24 +613,25 @@ Pass `logosStandalone` to `mkLogosModule` and you get `apps.default` (i.e. `nix --- -## Step 9: Build and Test +## Step 10: Build and Test -### 9.1 Build +### 10.1 Build ```bash git add -A nix build --override-input calc_module path:../logos-calc-module ``` -Inspect the output: +Inspect the output with `lm` (the module inspector from `logos-module`): ```bash -lm ./result/lib/calc_ui_cpp_plugin.dylib +nix build 'github:logos-co/logos-module#cli' --out-link ./lm-cli +./lm-cli/bin/lm ./result/lib/calc_ui_cpp_plugin.dylib ``` You should see `createWidget` and `destroyWidget` in the methods list. -### 9.2 UI only (layout preview) +### 10.2 UI only (layout preview) ```bash nix run . --override-input calc_module path:../logos-calc-module @@ -624,9 +639,9 @@ nix run . --override-input calc_module path:../logos-calc-module The widget opens. No backend connected yet, so button clicks will silently return 0 (CalcBackend logs a warning when `calc_module` is not connected). -> **Why `--override-input`?** `calc_module.url` in `flake.nix` points to the published GitHub URL. For local development, `--override-input` redirects it to the local sibling directory. This is the same mechanism `ws build --local` / `ws build --auto-local` uses throughout the workspace. +> **When do you need `--override-input`?** `calc_module.url` in `flake.nix` points to the published GitHub URL. If your local `logos-calc-module` has unpushed changes or differs from what is on GitHub, you must use `--override-input calc_module path:../logos-calc-module` so nix uses your local copy. If your `calc_module` is already pushed and matches the GitHub URL, you can run `nix build` / `nix run` without the override. This is the same mechanism `ws build --local` / `ws build --auto-local` uses throughout the workspace. -### 9.3 Full functionality (with modules) +### 10.3 Full functionality (with modules) ```bash nix build 'github:logos-co/logos-package-manager-module#cli' --out-link ./pm @@ -648,23 +663,69 @@ nix run . --override-input calc_module path:../logos-calc-module -- --modules-di --- -## Step 10: Load in `logos-basecamp` +## Step 11: Load in `logos-basecamp` -### 10.1 Create LGX packages +### 11.1 Create LGX packages ```bash # Package calc_module (from Part 1) cd ../logos-calc-module -nix bundle --bundler 'github:logos-co/nix-bundle-lgx#portable' '.#lib' -o lgx-calc-module -cd ../logos-calc-ui-cpp +nix bundle --bundler 'github:logos-co/nix-bundle-lgx#dual' '.#lib' -o lgx-calc-module # Package the C++ UI plugin -nix bundle --bundler 'github:logos-co/nix-bundle-lgx#portable' '.' -o lgx-calc-ui-cpp +cd ../logos-calc-ui-cpp +nix bundle --bundler 'github:logos-co/nix-bundle-lgx#dual' '.' -o lgx-calc-ui-cpp ``` -### 10.2 Install via logos-basecamp UI +### 11.2 Build and run logos-basecamp -1. Open `logos-basecamp` +Build logos-basecamp, launch it once to preinstall its bundled modules, then install your modules. + +> **Note:** `logos-basecamp` does not accept `--modules-dir` or `--ui-plugins-dir` CLI flags. It manages its own data directory and preinstalls bundled modules (main_ui, package_manager, etc.) on first launch. + +```bash +# Build logos-basecamp +nix build 'github:logos-co/logos-basecamp' -o basecamp-result + +# Launch once to preinstall bundled modules, then close it +./basecamp-result/bin/logos-basecamp +``` + +Basecamp creates its data directory on first launch. To find where it is, check the log output for `plugins directory` or look for the directory that contains `modules/` and `plugins/` subdirectories: + +```bash +# macOS (typical path, may vary): +ls ~/Library/Application\ Support/Logos/ + +# Linux (typical path, may vary): +ls ~/.local/share/Logos/ +``` + +The dev build directory is named `LogosBasecampDev` (portable builds use `LogosBasecamp`). + +Install your modules using `lgpm` (substitute `BASECAMP_DIR` with the actual path you found above): + +```bash +# Build lgpm CLI +nix build 'github:logos-co/logos-package-manager-module#cli' --out-link ./pm + +# Install core module +./pm/bin/lgpm --modules-dir BASECAMP_DIR/modules \ + install --file ../logos-calc-module/lgx-calc-module/*.lgx + +# Install UI plugin +./pm/bin/lgpm --modules-dir BASECAMP_DIR/plugins \ + install --file lgx-calc-ui-cpp/*.lgx + +# Launch basecamp -- your modules appear alongside the built-in ones +./basecamp-result/bin/logos-basecamp +``` + +### 11.3 Install via logos-basecamp UI + +Instead of using `lgpm` on the command line, you can install modules through the basecamp UI: + +1. Launch `logos-basecamp` 2. Go to **Package Manager** 3. Click **Install from file** 4. Select `lgx-calc-module/*.lgx` — installs `calc_module` @@ -672,50 +733,34 @@ nix bundle --bundler 'github:logos-co/nix-bundle-lgx#portable' '.' -o lgx-calc-u The "Calculator" tab appears in the sidebar. -### 10.3 Install via CLI (alternative) +--- + +## Known Limitations + +### QML changes not appearing after rebuild (Option A only) + +Qt caches compiled QML on disk. If you update your `Main.qml`, rebuild and reinstall the `.lgx`, but the old UI still appears, the cache is stale. Fix by disabling the cache before launching: ```bash -nix build 'github:logos-co/logos-package-manager-module#cli' --out-link ./pm -./pm/bin/lgpm install --file lgx-calc-module/*.lgx -./pm/bin/lgpm install --file lgx-calc-ui-cpp/*.lgx +QML_DISABLE_DISK_CACHE=1 ./basecamp-result/bin/logos-basecamp ``` -### 10.4 Build logos-basecamp from source +### UI module not loading or basecamp behaving unexpectedly -Build a local `logos-basecamp` binary, then use `lgpm` to populate a modules directory and run it: +When switching between portable and dev builds of basecamp, or running multiple basecamp instances, the data directory can get into a bad state (stale modules, mixed variants, corrupted preinstall). Clear it and let basecamp re-preinstall on next launch: ```bash -# Build logos-basecamp -nix build 'github:logos-co/logos-basecamp' -o basecamp-result +# Remove basecamp's data directory (find yours under Application Support or .local/share) +# macOS (typical): +rm -rf ~/Library/Application\ Support/Logos/LogosBasecampDev +# Linux (typical): +# rm -rf ~/.local/share/Logos/LogosBasecampDev -# Create module directories -mkdir -p modules ui-plugins - -# Build lgpm CLI -nix build 'github:logos-co/logos-package-manager-module#cli' --out-link ./pm - -# Install capability_module (required by all UI plugins) -nix bundle --bundler 'github:logos-co/nix-bundle-lgx' \ - 'github:logos-co/logos-capability-module' -o lgx-capability -./pm/bin/lgpm --modules-dir ./modules install --file lgx-capability/*.lgx - -# Bundle and install calc_module (local, not portable) -cd ../logos-calc-module -nix bundle --bundler 'github:logos-co/nix-bundle-lgx' '.#lib' -o lgx-calc-module-local -cd ../logos-calc-ui-cpp -./pm/bin/lgpm --modules-dir ./modules install --file ../logos-calc-module/lgx-calc-module-local/*.lgx - -# Bundle and install the C++ UI plugin -nix bundle --bundler 'github:logos-co/nix-bundle-lgx' '.' -o lgx-calc-ui-cpp-local -./pm/bin/lgpm --modules-dir ./ui-plugins install --file lgx-calc-ui-cpp-local/*.lgx - -# Run basecamp with the populated directories -./basecamp-result/bin/logos-basecamp \ - --modules-dir ./modules \ - --ui-plugins-dir ./ui-plugins +# Relaunch — basecamp will re-preinstall its bundled modules +./basecamp-result/bin/logos-basecamp ``` -> **Local vs portable:** A locally-built `logos-basecamp` (via `nix build`) expects **local** `.lgx` packages (built without `#portable`). Portable builds (AppImage, macOS app bundle) expect **portable** `.lgx` packages. +Then reinstall your custom modules. --- @@ -727,6 +772,7 @@ nix bundle --bundler 'github:logos-co/nix-bundle-lgx' '.' -o lgx-calc-ui-cpp-loc | Compilation | Yes | No | Yes | | Backend calls | Exposed via `Q_INVOKABLE` | `logos.callModule()` IPC | `LogosAPI*` → `invokeRemoteMethod()` | | Type safety | Strong | Weak (QVariant/QString) | Strong | +| Async support | — | `logos.callModuleAsync()` | `LogosAPIClient::invokeRemoteMethodAsync()` | | Sandboxed | No | Yes | No | | QML support | — | Native | Via `QQuickWidget` | | Template | `#default` | `#ui-qml-module` | `#ui-module` | diff --git a/tutorial-qml-ui-app.md b/tutorial-qml-ui-app.md index 4e8a0bb..57ceb4f 100644 --- a/tutorial-qml-ui-app.md +++ b/tutorial-qml-ui-app.md @@ -330,94 +330,95 @@ Available theme tokens via `Theme.palette`: - Spacing: `Theme.spacing.radiusSmall`, `Theme.spacing.radiusXlarge` - Typography: `Theme.typography.secondaryText`, `Theme.typography.weightMedium` -> **Note:** `Logos.Theme` and `Logos.Controls` are only available when running inside `logos-basecamp`. They are not available in `logos-standalone-app`. Use them only if you know your module will run in basecamp, or guard the import. - --- ## Step 7: Load in `logos-basecamp` -### 7.1 Create LGX packages +### 7.1 Bundle as LGX packages -Bundle both modules as portable LGX files: +Create `.lgx` packages using the `#dual` bundler (includes both dev and portable variants, so they work with any basecamp build): ```bash # Package calc_module (from Part 1) cd ../logos-calc-module -nix bundle --bundler 'github:logos-co/nix-bundle-lgx#portable' '.#lib' -o lgx-calc-module -cd ../logos-calc-ui +nix bundle --bundler 'github:logos-co/nix-bundle-lgx#dual' '.#lib' -o lgx-calc-module # Package the QML UI plugin -nix bundle --bundler 'github:logos-co/nix-bundle-lgx#portable' '.' -o lgx-calc-ui +cd ../logos-calc-ui +nix bundle --bundler 'github:logos-co/nix-bundle-lgx#dual' '.#lib' -o lgx-calc-ui ``` -### 7.2 Install via logos-basecamp UI +### 7.2 Build and run logos-basecamp -1. Open `logos-basecamp` -2. Go to **Package Manager** -3. Click **Install from file** -4. Select `lgx-calc-module/*.lgx` — installs `calc_module` -5. Repeat for `lgx-calc-ui/*.lgx` — installs `calc_ui` +Build logos-basecamp, launch it once to preinstall its bundled modules, then install your modules. -The "Calculator UI" tab appears in the sidebar. Clicking it loads your `Main.qml`. - -### 7.3 Install via CLI (alternative) - -```bash -nix build 'github:logos-co/logos-package-manager-module#cli' --out-link ./pm -./pm/bin/lgpm install --file lgx-calc-module/*.lgx -./pm/bin/lgpm install --file lgx-calc-ui/*.lgx -``` - -### 7.4 Build logos-basecamp from source - -Build a local `logos-basecamp` binary, then use `lgpm` to populate a modules directory and run it: +> **Note:** `logos-basecamp` does not accept `--modules-dir` or `--ui-plugins-dir` CLI flags. It manages its own data directory and preinstalls bundled modules (main_ui, package_manager, etc.) on first launch. ```bash # Build logos-basecamp nix build 'github:logos-co/logos-basecamp' -o basecamp-result -# Create module directories -mkdir -p modules ui-plugins +# Launch once to preinstall bundled modules, then close it +./basecamp-result/bin/logos-basecamp +``` +Basecamp creates its data directory on first launch. To find where it is, check the log output for `plugins directory` or look for the directory that contains `modules/` and `plugins/` subdirectories: + +```bash +# macOS (typical path, may vary): +ls ~/Library/Application\ Support/Logos/ + +# Linux (typical path, may vary): +ls ~/.local/share/Logos/ +``` + +The dev build directory is named `LogosBasecampDev` (portable builds use `LogosBasecamp`). + +Install your modules using `lgpm` (substitute `BASECAMP_DIR` with the actual path you found above): + +```bash # Build lgpm CLI nix build 'github:logos-co/logos-package-manager-module#cli' --out-link ./pm -# Install capability_module (required by all UI plugins) -nix bundle --bundler 'github:logos-co/nix-bundle-lgx' \ - 'github:logos-co/logos-capability-module' -o lgx-capability -./pm/bin/lgpm --modules-dir ./modules install --file lgx-capability/*.lgx +# Install core module +./pm/bin/lgpm --modules-dir BASECAMP_DIR/modules \ + install --file ../logos-calc-module/lgx-calc-module/*.lgx -# Bundle and install calc_module (local, not portable) -cd ../logos-calc-module -nix bundle --bundler 'github:logos-co/nix-bundle-lgx' '.#lib' -o lgx-calc-module-local -cd ../logos-calc-ui -./pm/bin/lgpm --modules-dir ./modules install --file ../logos-calc-module/lgx-calc-module-local/*.lgx +# Install UI plugin +./pm/bin/lgpm --modules-dir BASECAMP_DIR/plugins \ + install --file lgx-calc-ui/*.lgx -# Bundle and install the QML UI plugin -nix bundle --bundler 'github:logos-co/nix-bundle-lgx' '.' -o lgx-calc-ui-local -./pm/bin/lgpm --modules-dir ./ui-plugins install --file lgx-calc-ui-local/*.lgx - -# Run basecamp with the populated directories -./basecamp-result/bin/logos-basecamp \ - --modules-dir ./modules \ - --ui-plugins-dir ./ui-plugins +# Launch basecamp -- your modules appear alongside the built-in ones +./basecamp-result/bin/logos-basecamp ``` -> **Local vs portable:** A locally-built `logos-basecamp` (via `nix build`) expects **local** `.lgx` packages (built without `#portable`). Portable builds (AppImage, macOS app bundle) expect **portable** `.lgx` packages. +### 7.3 Install via logos-basecamp UI -### 7.5 Live reloading +Instead of using `lgpm` on the command line, you can install modules through the basecamp UI: -For rapid iteration on QML, use development mode. This watches your source files and reloads on change: +1. Launch `logos-basecamp` +2. Go to **Package Manager** +3. Click **Install from file** +4. Select `lgx-calc-module/*.lgx` -- installs `calc_module` +5. Repeat for `lgx-calc-ui/*.lgx` -- installs `calc_ui` + +The "Calculator UI" tab appears in the sidebar. Clicking it loads your `Main.qml`. + +### 7.4 Live reloading with `logos-standalone-app` + +For rapid iteration on QML without rebuilding, set `QML_PATH` to your QML source directory: ```bash -QML_UI=$(pwd) logos-basecamp +QML_PATH=$PWD/src/qml nix run . ``` -Edit `Main.qml`, save, and the UI updates without rebuilding. +Edit `Main.qml`, close and re-run — changes appear immediately without `nix build`. When `QML_PATH` is set, the plugin loads QML files from the filesystem instead of from Qt resources, so your edits are picked up on each launch. -### 7.6 Testing without `logos-basecamp` +> This does not work with `logos-basecamp`. Basecamp loads QML plugins from its own data directory, so changes to your source files are not reflected until you rebuild and reinstall the `.lgx` package. -You can open `Main.qml` in any QML viewer (e.g., `qml` from Qt) to test the layout. The `logos` bridge won't be available, so clicking buttons will show "Logos bridge not available" — but you can verify the layout and styling work correctly. +### 7.5 Testing without any runtime + +You can open `Main.qml` in any QML viewer (e.g., `qml` from Qt) to test the layout. The `logos` bridge won't be available, so clicking buttons will show "Logos bridge not available" -- but you can verify the layout and styling work correctly. ```bash # If you have Qt installed @@ -426,29 +427,54 @@ qml Main.qml --- -## Step 8: Package for Distribution (Optional) +## Known Limitations -The LGX packages created in Step 7.2 are **local** packages — they contain `/nix/store` references and work on the machine that built them. To create **portable** packages for distribution to other machines, use the `#portable` bundler: +### QML-to-C++ type coercion: `int` parameters called as `QString` -```bash -# Portable core module -cd ../logos-calc-module -nix bundle --bundler 'github:logos-co/nix-bundle-lgx#portable' '.#lib' -o lgx-portable +When calling C++ module methods from QML via `logos.callModule()`, all arguments are passed as strings. If the target method expects `int`, `bool`, or other non-string types, the call will fail with: -# Portable QML UI plugin -cd ../logos-calc-ui -nix bundle --bundler 'github:logos-co/nix-bundle-lgx#portable' '.' -o lgx-portable +``` +QMetaObject::invokeMethod: No such method CalcModulePlugin::add(QString,QString) +Candidates are: + add(int,int) ``` -Portable LGX packages are fully self-contained and can be installed on any machine with the Logos Package Manager: +**Workaround:** Define your C++ methods to accept `QString` parameters and convert inside the implementation: -```bash -nix build 'github:logos-co/logos-package-manager-module#cli' --out-link ./pm -./pm/bin/lgpm --modules-dir ./modules install --file calc_module.lgx -./pm/bin/lgpm --modules-dir ./ui-plugins install --file calc_ui.lgx +```cpp +// Instead of: Q_INVOKABLE int add(int a, int b); +// Use: +Q_INVOKABLE QString add(const QString& a, const QString& b) { + return QString::number(a.toInt() + b.toInt()); +} ``` -> **Local vs portable:** Local builds of `logos-basecamp` (via `nix build`) expect **local** `.lgx` packages. Portable builds (AppImage, macOS app bundle) expect **portable** `.lgx` packages. See the [logos-basecamp README](https://github.com/logos-co/logos-basecamp/blob/master/README.md) for details. +This ensures the QML bridge can match the method signature. Methods that already use `QString` parameters work without any changes. + +### QML changes not appearing after rebuild + +Qt caches compiled QML on disk. If you update your `Main.qml`, rebuild and reinstall the `.lgx`, but the old UI still appears, the cache is stale. Fix by disabling the cache before launching: + +```bash +QML_DISABLE_DISK_CACHE=1 ./basecamp-result/bin/logos-basecamp +``` + +### UI module not loading or basecamp behaving unexpectedly + +When switching between portable and dev builds of basecamp, or running multiple basecamp instances, the data directory can get into a bad state (stale modules, mixed variants, corrupted preinstall). Clear it and let basecamp re-preinstall on next launch: + +```bash +# Remove basecamp's data directory (find yours under Application Support or .local/share) +# macOS (typical): +rm -rf ~/Library/Application\ Support/Logos/LogosBasecampDev +# Linux (typical): +# rm -rf ~/.local/share/Logos/LogosBasecampDev + +# Relaunch — basecamp will re-preinstall its bundled modules +./basecamp-result/bin/logos-basecamp +``` + +Then reinstall your custom modules. --- diff --git a/tutorial-wrapping-c-library.md b/tutorial-wrapping-c-library.md index 0c39f08..4e366ac 100644 --- a/tutorial-wrapping-c-library.md +++ b/tutorial-wrapping-c-library.md @@ -42,10 +42,10 @@ This generates the skeleton files (`flake.nix`, `metadata.json`, `CMakeLists.txt > **Alternative approach:** You can also create the C library as a separate project, build it there, then copy the resulting `.so`/`.dylib` and header files into the module's `lib/` directory. This can be cleaner for larger libraries with their own build systems. -### 1.2 Create the lib and src directories +### 1.2 Create the lib directory ```bash -mkdir -p lib src +mkdir -p lib ``` ### 1.3 Write the C header @@ -172,7 +172,7 @@ T calc_version ## Step 2: Configure the Logos Module -If you used the template in Step 1.1, you already have the skeleton files. Now customize them for your library. A Logos module is a **Qt plugin** that wraps your C library functions as `Q_INVOKABLE` methods. You need five files: +If you used the template in Step 1.1, you already have the skeleton files. Now customize them for your library. Change cpp headers and source files and the other files as below: ``` logos-calc-module/ @@ -191,7 +191,7 @@ logos-calc-module/ ### 2.1 `metadata.json` — Module Configuration -This is the single source of truth for your module. It is both embedded into the plugin binary by Qt's `Q_PLUGIN_METADATA` macro (for runtime metadata) and read by `logos-module-builder` to configure the Nix build (via the `nix` section). +This is the single source of truth for your module. It is embedded into the plugin binary by Qt's `Q_PLUGIN_METADATA` macro (for runtime metadata), read by `logos-module-builder` to configure the Nix build, used by CMake to resolve external dependencies and link libraries (via the `nix` section), and used by `nix-bundle-lgx` to generate the LGX manifest. ```json { @@ -261,6 +261,17 @@ logos_module( ) ``` +The template generates this with default names (e.g., `external_lib`). You **must** update: + +- **`project()`** — rename to match your module (e.g., `CalcModulePlugin`) +- **`NAME`** — your module name (must match `name` in `metadata.json`, e.g., `calc_module`) +- **`SOURCES`** — your renamed source files +- **`EXTERNAL_LIBS`** — names of external libraries to link (must match `nix.external_libraries[].name` in `metadata.json`) + +The `if/elseif/else` block above it is boilerplate — don't change it. + +> **Common mistake:** If `NAME` doesn't match `name` in `metadata.json`, the build will succeed but the install phase will fail because it looks for `_plugin.dylib` based on `metadata.json`. + **How `EXTERNAL_LIBS calc` works:** The `logos_module()` CMake function searches `lib/` for `libcalc.so` (Linux) or `libcalc.dylib` (macOS), links it to your plugin, and sets up RPATH so the library is found at runtime. ### 2.3 `flake.nix` — Nix Build Config @@ -282,7 +293,9 @@ logos_module( } ``` -That's it — `mkLogosModule` handles all the Nix complexity (fetching Qt, the SDK, the code generator, setting up include paths, etc.). Note that `configFile` points to `metadata.json` (the single source of truth) and `flakeInputs = inputs` passes all flake inputs to the builder. +That's it — `mkLogosModule` handles all the Nix complexity (fetching Qt, the SDK, the code generator, setting up include paths, etc.). Note that `configFile` points to `metadata.json` (the single source of truth) and `flakeInputs = inputs` passes all flake inputs to the builder so that dependencies declared in `metadata.json` are resolved automatically. + +> **Naming flake inputs:** When adding module dependencies, the flake input attribute name **must match** the `name` field in that dependency's `metadata.json`. For example, if you depend on a module whose `metadata.json` has `"name": "waku_module"`, your flake input must be `waku_module.url = "github:logos-co/logos-waku-module"`. The URL can point to any repo, but the attribute name is how the builder resolves dependencies. ### 2.4 `src/calc_module_interface.h` — Interface Declaration @@ -464,7 +477,6 @@ Nix flakes require a git repository: cd logos-calc-module git init git add -A -git commit -m "Initial commit" ``` ### 3.2 Build with Nix @@ -493,13 +505,15 @@ You should see two files (extensions depend on your platform): # Linux calc_module_plugin.so # Your Logos module plugin libcalc.so # The C library (copied alongside) +metadata.json # Runtime metadata # macOS calc_module_plugin.dylib libcalc.dylib +metadata.json ``` -Both files are placed together so the plugin can find the C library at runtime via RPATH. +Both library files are placed together so the plugin can find the C library at runtime via RPATH. The `metadata.json` is copied alongside for runtime discovery by `logoscore` and `logos-basecamp`. --- @@ -641,44 +655,41 @@ This extracts the plugin, external libraries, and manifest into the correct dire modules/calc_module/ ├── calc_module_plugin.dylib # (or .so on Linux) ├── libcalc.dylib # (or .so on Linux) -└── manifest.json # Auto-generated +├── metadata.json # Embedded in binary, also on disk +├── manifest.json # Auto-generated by lgx +└── variant # Platform variant identifier ``` ### 5.3 Call methods +Start the daemon and call methods: + ```bash -# Call add(3, 5) -./logos/bin/logoscore \ - -m ./modules \ - --load-modules calc_module \ - -c "calc_module.add(3, 5)" +# Start logoscore daemon with modules directory +./logos/bin/logoscore -D -m ./modules & -# Call factorial(5) -./logos/bin/logoscore \ - -m ./modules \ - --load-modules calc_module \ - -c "calc_module.factorial(5)" +# Load the module +./logos/bin/logoscore load-module calc_module -# Call fibonacci(10) -./logos/bin/logoscore \ - -m ./modules \ - --load-modules calc_module \ - -c "calc_module.fibonacci(10)" +# Call methods +./logos/bin/logoscore call calc_module add 3 5 +./logos/bin/logoscore call calc_module factorial 5 +./logos/bin/logoscore call calc_module fibonacci 10 +./logos/bin/logoscore call calc_module libVersion -# Call libVersion() -./logos/bin/logoscore \ - -m ./modules \ - --load-modules calc_module \ - -c "calc_module.libVersion()" +# Stop the daemon when done +./logos/bin/logoscore stop ``` -**What happens under the hood:** +> For inline (legacy) mode and other logoscore options, see the [Developer Guide -- Running with logoscore](logos-developer-guide.md#51-running-with-logoscore). + + **What happens under the hood:** 1. `logoscore` scans `./modules/` for subdirectories containing `manifest.json` 2. It finds `calc_module` and extracts metadata from the plugin binary 3. It spawns a `logos_host` process that loads `calc_module_plugin.so` -4. `logos_host` calls `initLogos()` on the plugin, providing a `LogosAPI`* for inter-module communication -5. The `-c` command is parsed: module name `calc_module`, method `add`, args `[3, 5]` +4. `logos_host` calls `initLogos()` on the plugin, providing a `LogosAPI*` for inter-module communication +5. The call command is parsed: module name `calc_module`, method `add`, args `[3, 5]` 6. `logoscore` sends the call to `logos_host` via Qt Remote Objects (IPC) 7. `logos_host` invokes `CalcModulePlugin::add(3, 5)` which calls `calc_add(3, 5)` from libcalc 8. The result is returned via IPC to `logoscore`