Files
logos-liblogos/CMakeLists.txt
T
503587797d feat(windows): cross-compile liblogos, and make liblogos_core the single provider (#176)
* feat(windows): cross-compile logos-liblogos for x86_64-w64-mingw32

Adds the x86_64-windows pseudo-system to `packages` (checks and devShells stay
native). liblogos's own src/ needed NO portability work at all -- verified
exhaustively, not assumed: 17 files, 2304 lines, zero POSIX headers and zero
POSIX APIs. All process, plugin and socket work already lives in
logos-container-subprocess and logos-module-loader-qt, which were ported first.

Two real blockers, one of them silent:

* install(TARGETS logos_core ...) named only LIBRARY and ARCHIVE destinations.
  A DLL is a RUNTIME artifact, so CMake SKIPPED IT WITHOUT COMMENT: the build
  succeeded and shipped a lib/ containing liblogos_core.dll.a and no DLL at
  all -- a link-only package that would have handed logosctl.exe an import
  library with nothing behind it. Proven by reverting the fix: rc=0, no DLL.
  lib.nix now refuses to produce an output with no loadable logos_core.

* The test suite is genuinely POSIX-only (spawn.h, sys/wait.h, mkdtemp, kill),
  and CMake put it in the default `all` target, so it broke the cross build
  before anything else could. Tests are now gated behind LOGOS_BUILD_TESTS,
  with the gate wrapping the gtest FetchContent fallback too -- otherwise
  dropping gtest sends CMake to the network inside the sandbox and it dies on
  a misleading "downloading ... failed".

Also: package_manager_lib needed IMPORTED_IMPLIB (mingw links against the
import library, not the DLL), and Qt's host tools come in via
logosQtCrossCmakeFlags.

Verified: liblogos_core.dll is a PE32+ DLL whose export table carries the C
ABI (logos_core_init / _start / _load_module / _get_loaded_modules). The
output also ships logos_host_qt.exe with its 15 runtime DLLs and
capability_module_plugin.dll. Native unchanged: 181 tests, 174 passed, 7
skipped -- the same 7 ProcessManagerTest cases skipped before this change.

* fix(windows): carry the package-manager DLL closure, and type the manifest

lib.nix (Windows only): copy every *.dll / *.dll.a from the package-manager
root rather than just libpackage_manager_lib*, with a loud guard if liblgx.dll
is missing -- an absent runtime DLL otherwise shows up as an executable that
exits with no output at all.

modules.nix: emit "type": "core" in the generated manifest. Native-visible
but strictly additive.

* fix(windows): export only the C API from liblogos_core

liblogos_core.dll exported 13,252 symbols. Eighteen of them were the
logos_core_* C API; the rest were the entire internal C++ surface,
LogosAPI's included. Any consumer linking both liblogos_core and the
qt-sdk static library therefore failed with

    multiple definition of `LogosAPI::LogosAPI(QString const&, QObject*)'
      ...liblogos_qt_sdk.a(logos_api.cpp.obj)
      first defined here: ...liblogos_core.dll.a(...)

which is what blocked main_ui from linking.

Root cause: LOGOS_CORE_EXPORT expanded to
__attribute__((visibility("default"))), an ELF concept that mingw-gcc
accepts and silently ignores on PE. Nothing was explicitly exported, so
an earlier fix reached for -Wl,--export-all-symbols to get the C API out
-- correct as far as it went, and the reason the whole C++ surface
leaked with it.

Using __declspec(dllexport)/dllimport on Windows fixes it at the root and
does so twice over: GNU ld disables PE auto-export image-wide as soon as
any symbol is dllexported, so the internal surface stops leaking as a
side effect. Measured: logos_core_* exports unchanged at 18, mangled C++
exports 13,673 -> 452, LogosAPI no longer exported at all.

Two copies of LogosAPI (host and plugin) is the DESIGNED arrangement, not
a regression -- see logos_qt_lp_bridge.h, which notes a Qt plugin links
its own copy of the protocol library so TokenManager::instance() inside
it is deliberately not the host's, and syncTokens is the bridge. That
holds on Unix too.

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

* feat(windows): export the shared runtime from liblogos_core.dll

Makes liblogos_core the single provider of TokenManager, LogosAPI,
LogosAPIClient and the LogosResult stream operators, so the Basecamp
process has ONE of each instead of nine.

The export set comes from a GENERATED .def rather than dllexport in the
headers, because the definitions live in liblogos_protocol.a /
liblogos_qt_sdk.a -- archives also linked by logos_host.exe, ui-host.exe,
every module plugin and every native platform. Annotating them for export
would mean a second, Windows-only, export-annotated build of both
archives kept in sync forever, to solve a Windows-only problem. Exporting
at this link instead leaves those archives compiled byte-identically.

Two details in gen-shared-exports.sh that look like they could be
simplified and cannot:

  * It exports the WHOLE archive, not a curated class list. ld picks
    archive members by object file for reasons unrelated to our symbols
    -- measured here, main_ui referenced std::string's move constructor
    and ld satisfied it from logos_api.cpp.obj, dragging LogosAPI,
    LogosAPIClient and TokenManager in behind it. Consumers link an empty
    archive and take everything from the DLL, which only works if the DLL
    really provides everything; a partial export set surfaces as an
    undefined reference in a downstream repo, far from the cause.
  * It filters COMDAT symbols out. Those come from inline functions and
    templates in headers, so every consumer TU emits its own copy
    regardless; exporting them makes the import library a strong
    definition that collides with that copy.

Measured after: liblogos_core.dll goes from 18 exports to 376, of which
132 are the shared runtime; LogosBasecamp.exe drops 14.3 MB -> 1.08 MB
and main_ui.dll 23.2 MB -> 10.1 MB as the duplicated statics stop being
linked in. Native aarch64-darwin is unchanged.

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

* Merge origin/master into feat/windows-cross, and re-pin the L1-L7 inputs

master landed 56aa8bb (bump logos package manager); merged clean, no conflicts.

Then the whole chain moves to its merged revs: logos-nix (L1); logos-protocol,
logos-module, logos-container, process-stats (L2); logos-cpp-sdk,
logos-module-loader, default-container=logos-container-subprocess,
logos-package-manager (L3); logos-qt-sdk (L4);
default-module-loader=logos-module-loader-qt (L5); logos-capability-module (L7).

Verified packages.x86_64-windows.default evaluates to a .drv against the merged
set. The native aarch64-darwin build was NOT verified locally: this machine is
currently failing unrelated derivations two different ways -- clang killed with
signal 9 during CMake's trivial compiler probe, and macOS refusing a Nix-store
gtest dylib with "library load denied by system policy" during
gtest_discover_tests. The first was proven environmental rather than
input-related by building the IDENTICAL derivation hash standalone, where it
passed. CI is the authority for the native side.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-12 10:54:58 -03:00

95 lines
3.5 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()
# 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
)