Files
Dario LipicarandClaude Opus 5 b2a9a0ba9d feat(shared-runtime): import the runtime instead of providing it (#182)
* feat(shared-runtime): import the runtime instead of providing it

PR-4 of the shared-runtime migration. liblogos_core stops being the single
provider of types it does not own, and becomes a consumer of the libraries that
do: logos-protocol#65 and logos-plugin-qt#22.

WHAT GOES AWAY. The whole if(WIN32) block that absorbed liblogos_protocol.a and
liblogos_qt_host.a with --whole-archive and re-published their symbols through a
generated .def, plus cmake/gen-shared-exports.sh itself. That scheme existed
because PE exports nothing it is not told to export and the definitions lived in
archives that every other image also linked; now they live in shared libraries
that export their own tables, so there is nothing for this repo to re-publish.

WHAT REPLACES IT is one line: logos_sdk carries logos_qt_host_shared rather than
the static archive. logos_qt_host_shared PUBLIC-links logos_protocol_shared, so
the protocol half arrives transitively and correctly layered.

THE INVARIANT IS UNCHANGED. The runtime must still exist exactly once per
process; it is now enforced by there being one shared library per type rather
than by one image absorbing everything. Measured, aarch64-darwin:

    liblogos_core.dylib       defines 0   (was 32 TokenManager symbols)
                              imports 8
    liblogos_protocol.dylib   defines 78  (TokenManager, LogosAPIClient, ...)
    liblogos_qt_host.dylib    defines 23  (LogosAPI)

OUT-OF-PROCESS CONSUMERS ARE DELIBERATELY UNAFFECTED. Module plugins and ui_qml
backends keep linking the STATIC archive: each runs in its own process where its
own copy is the CORRECT per-process singleton, and a .lgx records an empty nix
closure so it could not carry a shared library anyway.

TWO DEPLOYMENT FAILURES THIS ALSO FIXES, both of which built green.

nix/lib.nix now STAGES liblogos_protocol and liblogos_qt_host beside
liblogos_core, and asserts it did. liblogos_core records
@rpath/liblogos_qt_host.dylib with @loader_path as its only rpath, so the loader
looks for them in that directory and nowhere else. Before this:

    nix build .#default        OK
    logos_host --help          exit 0
    dlopen liblogos_core.dylib Library not loaded: @rpath/liblogos_qt_host.dylib

A help-text smoke test never touches the library, so nothing in the build or in
a boot check would have caught it. Hence the assertion rather than trust in the
copy loop.

CMakeLists.txt adds both to CMAKE_BUILD_RPATH, mirroring what
LOGOS_PACKAGE_MANAGER_ROOT already does. Without it logos_core_tests aborted at
dyld time, before main(), while .#default had already succeeded.

VERIFIED, aarch64-darwin: .#default OK, dlopen OK, checks.tests PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(shared-runtime): install RPATH too, or the installed test binary cannot load

BUILD_RPATH covers binaries run from the build tree; the test derivation runs the
INSTALLED one, which uses INSTALL_RPATH. Only Linux said so -- on macOS the
installed test binary resolved the libraries anyway and checks.tests passed,
while the same commit on Linux died before main() with

    error while loading shared libraries: liblogos_qt_host.so

A macOS-green run is not evidence for this class of failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(tests): put the shared runtime on the hand-set Linux RPATH

nix/tests.nix applies its RPATH with `patchelf --set-rpath`, which REPLACES
whatever CMake wrote. So CMAKE_BUILD_RPATH and CMAKE_INSTALL_RPATH have no
effect on the installed test binaries on Linux, and that list is the entire
search path: anything absent from it is absent at runtime.

Measured: adding both libraries to CMAKE_*_RPATH changed nothing and the suite
still died before main() with

    error while loading shared libraries: liblogos_qt_host.so

while the same commit passed on macOS, which does not go through this code path
at all. Two platforms, two independent rpath mechanisms, and only one of them
was wired.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:51:26 -03:00

128 lines
5.3 KiB
CMake

cmake_minimum_required(VERSION 3.14)
project(SimplePluginExample LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find Qt packages
# Network is needed explicitly by the token-exchange tests (QLocalServer /
# QLocalSocket); it is also pulled transitively by RemoteObjects in the
# runtime build, but we declare it here so the test target can link it
# explicitly without depending on that transitive resolution.
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Network RemoteObjects)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Network RemoteObjects)
# Find nlohmann_json
find_package(nlohmann_json REQUIRED)
# OpenSSL needed transitively by logos_sdk (plain-C++ TCP+SSL transport)
find_package(OpenSSL REQUIRED)
# Find spdlog
find_package(spdlog REQUIRED)
# Set output directories
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
# Add external library paths to build RPATH so test binaries can find them
if(DEFINED LOGOS_PACKAGE_MANAGER_ROOT)
list(APPEND CMAKE_BUILD_RPATH "${LOGOS_PACKAGE_MANAGER_ROOT}/lib")
endif()
# The shared C++ runtime, which logos_core now IMPORTS rather than absorbing.
# Both libraries carry an @rpath install name, so a consumer that does not know
# where they live cannot load at all -- and the failure is at dyld time, before
# main(), with no hint from the build.
#
# Measured before this existed: logos_core_tests aborted with
# dyld: Library not loaded: @rpath/liblogos_qt_host.dylib
# while `nix build .#default` had already succeeded. The library builds and
# installs whether or not anything can load it.
#
# The INSTALLED liblogos_core is covered separately by INSTALL_RPATH $ORIGIN in
# src/CMakeLists.txt plus staging both libraries beside it (nix/lib.nix); this
# entry is what makes the BUILD tree -- tests especially -- resolve them.
# BUILD_RPATH covers binaries run from the build tree; INSTALL_RPATH covers the
# ones that get installed and run from $out -- which is what the test derivation
# actually executes. Both are needed, and only Linux says so: on macOS the
# installed test binary resolved the libraries anyway and the suite passed, while
# the same commit on Linux died with
# error while loading shared libraries: liblogos_qt_host.so
# A macOS-green test run is not evidence here.
#
# logos_core itself is unaffected by the INSTALL_RPATH lines: it sets its own
# INSTALL_RPATH ($ORIGIN / @loader_path in src/CMakeLists.txt), which overrides
# the directory-level default, and nix/lib.nix stages both libraries beside it.
if(DEFINED LOGOS_PROTOCOL_ROOT)
list(APPEND CMAKE_BUILD_RPATH "${LOGOS_PROTOCOL_ROOT}/lib")
list(APPEND CMAKE_INSTALL_RPATH "${LOGOS_PROTOCOL_ROOT}/lib")
endif()
if(DEFINED LOGOS_QT_HOST_ROOT)
list(APPEND CMAKE_BUILD_RPATH "${LOGOS_QT_HOST_ROOT}/lib")
list(APPEND CMAKE_INSTALL_RPATH "${LOGOS_QT_HOST_ROOT}/lib")
endif()
# Build src first to ensure logos_core is built before modules
add_subdirectory(src)
# Tests. Gated EXPLICITLY rather than left to find_package: the suite is
# POSIX-only (tests/test_process_stats.cpp uses <spawn.h>, <sys/wait.h>,
# posix_spawn/waitpid/kill and `environ`; tests/test_subprocess_manager.cpp
# spawns /bin/sh), none of which mingw-w64 provides. The default build target
# is `all`, so an ungated tests/ subdirectory would be compiled by every
# cross build.
#
# The gate wraps the GTest lookup too: dropping only `add_subdirectory(tests)`
# would still leave find_package(GTest) able to miss and fall through to the
# FetchContent download, which dies in the nix sandbox with a misleading
# "downloading ... failed".
option(LOGOS_BUILD_TESTS "Build the logos_core test suite" ON)
if(WIN32)
set(LOGOS_BUILD_TESTS OFF)
endif()
if(LOGOS_BUILD_TESTS)
# Try to find GoogleTest via find_package first (for Nix and system installations)
find_package(GTest QUIET)
if(NOT GTest_FOUND)
# Fall back to FetchContent if GoogleTest is not found
message(STATUS "GoogleTest not found via find_package, using FetchContent")
include(FetchContent)
FetchContent_Declare(googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
)
FetchContent_MakeAvailable(googletest)
else()
message(STATUS "Using system GoogleTest")
endif()
enable_testing()
include(GoogleTest)
# Add tests subdirectory
add_subdirectory(tests)
endif()
# Install rules. The logos_host_qt binary (and its logos_host symlink) now live
# in logos-module-loader-qt; liblogos installs only the core library here, and
# bin.nix re-exports the host binary from that package so frontends are
# unaffected.
# RUNTIME DESTINATION matters on Windows: a DLL is a RUNTIME artifact, and with
# only LIBRARY/ARCHIVE named CMake sends it to the DEFAULT runtime destination
# (bin) while nix/lib.nix only ever copies ${build}/lib -- which would yield a
# lib output holding the import library and no DLL, silently. Pointing RUNTIME
# at lib keeps the output shape identical on all three platforms.
install(TARGETS logos_core
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION lib
)
# Install headers
install(FILES src/logos_core/logos_core.h
DESTINATION include
)