Files
Dario Gabriel LipicarandClaude Opus 5 fe4f26cecf feat: build ui_qml glue with logos-view-generator, not logos-qt-generator
modulePreConfigure.nix switches the `--backend ui` invocation to
logos-view-generator (logos-view-module), which now owns that emitter and
the logos_ui_plugin_context.h it pairs with.

THE REASON THIS IS NOT COSMETIC. This builder pins logos-qt-sdk at 4a1104c,
which predates b7b82e5 — the commit that added the module teardown hook to
the ui emitter. At 4a1104c BOTH halves lack it, so every build has been
green and self-consistent, and the hook has NEVER REACHED A SINGLE SHIPPED
ui_qml MODULE. Measured on the same module, before and after, by loading the
built plugin with QPluginLoader and dumping its QMetaObject:

    BASELINE                      MIGRATED
      initLogos(LogosAPI*)          unloadFinished()      [signal]
                                    initLogos(LogosAPI*)
                                    int aboutToUnload()   [invokable]

So this delivers the hook for the first time rather than preserving it.

LOGOS_VIEW_INCLUDE_DIR is added BEFORE the qt-sdk root in
LogosModule.cmake, so the emitter and its header resolve from ONE pin. That
ordering is load-bearing until logos-qt-sdk stops installing its copy of
logos_ui_plugin_context.h — anything going through logos_module() is safe;
a hand-run cmake putting LOGOS_QT_SDK_ROOT first would silently get the
wrong header. Called out in the code rather than left implicit.

Blast radius is 7 modules, not one: package_manager_ui, chat_ui, wallet_ui,
test_uiqml_probe, test_fullapi_ui, calc_ui_cpp and templates/ui-qml-backend
all match ui_qml + interface:universal. None override aboutToUnload(), and
the base default is Synchronous, so every one answers 0 and no host waits —
the change is additive. Only package_manager_ui was built end to end.

Three assertions in tests/test-module-pre-configure.nix pin the binary
choice; repointing at logos-qt-generator fails them. Also corrects a comment
there that claimed "the ui backend still legitimately uses qt-sdk's".

7/7 checks green, 399 unit tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 19:59:50 -03:00

12 KiB

CMake Reference

Complete reference for LogosModule.cmake functions and options.

Overview

LogosModule.cmake is a CMake module that handles all the boilerplate for building Logos plugins. It provides:

  • Automatic SDK and liblogos detection
  • Qt6/Qt5 finding and configuration
  • Code generation setup
  • External library handling
  • Platform-specific RPATH configuration
  • Install targets

Including LogosModule.cmake

# Method 1: Via environment variable (recommended for nix builds)
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)

# Method 2: Local copy
include(cmake/LogosModule.cmake)

# Method 3: Vendor directory
include(vendor/logos-module-builder/cmake/LogosModule.cmake)

logos_module()

The main function to define a Logos module.

Syntax

logos_module(
    NAME <module_name>
    SOURCES <source_files>...
    [REP_FILE <rep_file>]
    [QML_URI <uri>]
    [QML_TYPE_NAME <type_name>]
    [INCLUDE_DIRS <dirs>...]
    [EXTERNAL_LIBS <library_names>...]
    [FIND_PACKAGES <package_names>...]
    [LINK_LIBRARIES <library_names>...]
    [LINK_TARGETS <target_names>...]
    [AUTOGEN_DEPENDS <target_names>...]
)

Parameters

NAME (required)

The module name. Used for:

  • Output filename: {NAME}_plugin.so / {NAME}_plugin.dylib
  • CMake target name: {NAME}_module_plugin
logos_module(
    NAME my_module
    ...
)

SOURCES (required)

List of source files for the module. In the universal authoring model you list only your impl (or backend) sources — the generated glue is picked up from generated_code/ automatically (see below) and must not be listed. For a core module that glue is {name}_cdylib_glue.{h,cpp} (the Qt plugin) plus {name}_module_impl.cpp / {name}_types.h (the Qt-free C ABI around your impl); the older {name}_interface.h + {name}_plugin.{h,cpp} file names are no longer emitted.

For a core module, this is the impl class:

  • src/{name}_impl.h - Impl class declaration (public methods = API)
  • src/{name}_impl.cpp - Impl class implementation

plus any extra helpers you add:

logos_module(
    NAME my_module
    SOURCES 
        src/my_module_impl.h
        src/my_module_impl.cpp
        src/helper.cpp
        src/utils.cpp
)

Classic modules used to omit "interface" from metadata.json and list the hand-written src/{name}_interface.h, src/{name}_plugin.h and src/{name}_plugin.cpp here. A core module that ships a plugin (declares main) and names no interface is now refused at evaluation — no glue would be generated and every call into it would fail at runtime instead of at build time. Use "universal", or "cdylib" if you bring your own C ABI. Legacy type: "ui" widget modules still build this way.

REP_FILE (optional)

Path to a .rep Qt Remote Objects contract for a universal C++ UI backend ("type": "ui_qml" + "interface": "universal"). repc is run on it and the generated source (rep_<name>_source.h) is made available to your *Backend class. Pair it with INCLUDE_DIRS src so the generated header resolves.

logos_module(
    NAME my_ui
    REP_FILE src/my_ui.rep
    SOURCES
        src/my_ui_backend.h
        src/my_ui_backend.cpp
    INCLUDE_DIRS
        src
)

INCLUDE_DIRS (optional)

Additional include directories added to the plugin target. Commonly src for universal UI backends so the generated rep_*_source.h is found.

logos_module(
    NAME my_module
    SOURCES ...
    INCLUDE_DIRS
        src
        vendor/include
)

EXTERNAL_LIBS (optional)

External libraries to link. Libraries are searched in lib/ directory.

logos_module(
    NAME my_module
    SOURCES ...
    EXTERNAL_LIBS
        libfoo
        libbar
)

The function will:

  1. Search for lib/libfoo.so or lib/libfoo.dylib
  2. Add lib/ to include directories
  3. Link the library
  4. Copy the library to the output directory
  5. Fix install names on macOS

FIND_PACKAGES (optional)

CMake packages to find via find_package().

logos_module(
    NAME my_module
    SOURCES ...
    FIND_PACKAGES
        Protobuf
        Threads
        ZLIB
)

Additional libraries to link (after find_package).

logos_module(
    NAME my_module
    SOURCES ...
    FIND_PACKAGES Threads
    LINK_LIBRARIES
        Threads::Threads
        ${ZLIB_LIBRARIES}
)

generated_code/ (automatic)

If generated_code/ exists next to CMakeLists.txt, all *.cpp and *.h files there are added to the plugin target, except logos_sdk.cpp and every per-dependency *_api.cpp — those are #included by logos_sdk.cpp rather than compiled separately. You do not need to list glue or dispatch sources manually. (core_manager_api.cpp used to be excluded by name; it is not generated at all any more — a universal module exposes only its declared dependencies, and an app that must manage the core uses liblogos' C API directly.)

metadata.json (automatic)

metadata.json is copied to CMAKE_CURRENT_BINARY_DIR so Q_PLUGIN_METADATA can resolve it during the build.

Go static archives (CMake cache variable)

When mkLogosModule passes -DLOGOS_MODULE_GO_STATIC_LIBS=name1;name2 (from go_build: true entries in metadata.json), LogosModule.cmake finds lib/lib<name>.a under lib/, links with whole-archive (Linux) or -force_load (macOS), and adds CoreFoundation/Security frameworks on Apple platforms.

CMake targets to link directly, as opposed to LINK_LIBRARIES, which takes names resolved after find_package. Use this for a target you define yourself in the same CMakeLists.txt — e.g. a protobuf library you build. Each target must already be defined when logos_module() runs; an undefined one is a FATAL_ERROR rather than a silently dropped link.

AUTOGEN_DEPENDS (optional)

Sets AUTOGEN_TARGET_DEPENDS on the plugin target, so AUTOMOC waits for the named targets. Needed when something in LINK_TARGETS generates headers the plugin's own sources include.

PROTO_FILES no longer exists. logos_module() used to accept it and run find_package(Protobuf) + protoc for you. It is not among the keywords logos_module() parses today, so passing it is silently ignored. Compile .proto files in your own CMakeLists.txt (declare protobuf under nix.packages.build and Protobuf under nix.cmake.find_packages, then add the generated sources with nix.cmake.extra_sources / LINK_LIBRARIES).

Helper Functions

logos_find_dependencies()

Find and configure the Logos SDK and logos-module.

logos_find_dependencies()

Sets variables:

  • LOGOS_MODULE_ROOT - Path to logos-module (this is the plugin interface.h root; there is no LOGOS_LIBLOGOS_ROOT — the plugin never links liblogos)
  • LOGOS_CPP_SDK_ROOT - Path to logos-cpp-sdk
  • LOGOS_MODULE_IS_SOURCE - TRUE if source layout
  • LOGOS_CPP_SDK_IS_SOURCE - TRUE if source layout
  • LOGOS_QT_HOST_ROOT - Path the Qt host runtime is taken from
  • LOGOS_QT_HOST_IS_SOURCE - TRUE if that root is a repo checkout
  • LOGOS_QT_HOST_PACKAGE / LOGOS_QT_HOST_TARGET - the CMake package and imported target the plugin links for the host runtime

The Qt host runtime — LogosAPI, LogosAPIProvider, LogosProviderBase and the legacy PluginInterface — lives in logos-plugin-qt and ships as the logos-qt-host package. Point LOGOS_QT_HOST_ROOT at it (nix builds do). logos-qt-sdk still forwards the same code, so a build that supplies only LOGOS_QT_SDK_ROOT keeps working, with a message saying it took the legacy package; a build that supplies neither is a FATAL_ERROR. LOGOS_QT_SDK_ROOT stays required regardless — it is where logos_qt_lp_bridge.h and logos_qt_wire.h come from.

logos_ui_plugin_context.h comes from LOGOS_VIEW_INCLUDE_DIR (logos-view-module), which is placed on the include path AHEAD of LOGOS_QT_SDK_ROOT. That header and the view glue emitter are one matched pair — the emitted glue calls maybeUiPluginAboutToUnload(), which only that header declares — so both ship from one pin. logos-qt-sdk may still install an older copy of the same header name; the ordering is what keeps it from being found.

logos_find_qt()

Find Qt6 (or Qt5 fallback) with required components.

logos_find_qt()

Sets:

  • QT_VERSION_MAJOR - 5 or 6

Environment Variables

LOGOS_MODULE_BUILDER_ROOT

Path to logos-module-builder. Set automatically by nix builds.

export LOGOS_MODULE_BUILDER_ROOT=/path/to/logos-module-builder

LOGOS_CPP_SDK_ROOT

Override path to logos-cpp-sdk.

export LOGOS_CPP_SDK_ROOT=/path/to/logos-cpp-sdk

LOGOS_MODULE_ROOT

Override path to logos-module (source checkout or installed prefix).

export LOGOS_MODULE_ROOT=/path/to/logos-module

LOGOS_QT_SDK_ROOT

Override path to logos-qt-sdk. Required — see logos_find_dependencies() above.

LOGOS_PROTOCOL_ROOT

Override path to logos-protocol (transports + the lp_* C ABI).

LOGOS_QT_HOST_ROOT

Path to the Qt host runtime — an installed logos-qt-host prefix, or a logos-plugin-qt checkout.

export LOGOS_QT_HOST_ROOT=/path/to/logos-plugin-qt

LOGOS_VIEW_TEMPLATE_DIR

Directory holding the four LogosView*.in templates that REP_FILE instantiates. Required when — and only when — logos_module() is given a REP_FILE; logos_module() hard-errors rather than guessing.

The templates are owned by logos-view-module (cmake/), not by this repo, even though LogosModule.cmake is what instantiates them: that repo owns the whole ui_qml authoring flavour (LogosViewModule.cmake, the view glue generator, and the rep-file-plugin fixture that instantiates the templates and proves the built plugin still loads and casts). It is a leaf — its only input is logos-nix — so every consumer can read one copy from it, which is the property logos-plugin-qt did not have. See logos-view-module/cmake/README.md.

Set automatically by this repo's nix builds and module dev shells — lib/mkLogosModule.nix and lib/buildCppPlugin.nix pass both the cache variable and the environment variable. Also accepted as a CMake cache variable (-DLOGOS_VIEW_TEMPLATE_DIR=...), which takes precedence.

export LOGOS_VIEW_TEMPLATE_DIR=/path/to/logos-view-module/cmake

Generated Targets

For a module named my_module, the following are created:

Target Description
my_module_module_plugin Main library target
run_cpp_generator_my_module Code generation target (source layout)
my_module_replica_factory QML replica-factory plugin (only if REP_FILE)

Output Files

build/
└── modules/
    ├── my_module_plugin.so      # or .dylib
    ├── libfoo.so                # external libs copied here
    └── ...

Complete Example

cmake_minimum_required(VERSION 3.14)
project(ChatModulePlugin LANGUAGES CXX)

# Include the helper
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)

# Define the module (universal model: list only the impl + helpers)
logos_module(
    NAME chat
    SOURCES 
        src/chat_impl.h
        src/chat_impl.cpp
        src/chat_api.cpp
        src/chat_api.h
    FIND_PACKAGES
        Protobuf
        Threads
    LINK_LIBRARIES
        absl::base
        absl::strings
)

Customization

For advanced customization, you can use the helper functions directly:

cmake_minimum_required(VERSION 3.14)
project(CustomModulePlugin LANGUAGES CXX)

# Include helpers
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)

# Find dependencies manually
logos_find_dependencies()
logos_find_qt()

# Create library manually
add_library(my_plugin SHARED
    my_plugin.cpp
    # ... more sources
)

# Custom configuration
target_compile_definitions(my_plugin PRIVATE MY_CUSTOM_DEFINE)
target_include_directories(my_plugin PRIVATE ${CUSTOM_INCLUDE_DIR})

# Link Qt (required)
target_link_libraries(my_plugin PRIVATE 
    Qt${QT_VERSION_MAJOR}::Core 
    Qt${QT_VERSION_MAJOR}::RemoteObjects
)