mirror of
https://github.com/logos-co/logos-liblogos.git
synced 2026-08-27 12:51:10 +00:00
* 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>
85 lines
3.2 KiB
Nix
85 lines
3.2 KiB
Nix
# Bundles modules from external flake inputs into the logos_host_qt modules directory.
|
|
# logos_host_qt expects: modules/<name>/manifest.json + <name>_plugin.{so,dylib,dll}
|
|
# When portableBuild is false (default/dev), manifest keys get a "-dev" suffix
|
|
# to match the dev variant lookup in platformVariantsToTry().
|
|
{ pkgs, common, capabilityModule, portableBuild ? false }:
|
|
|
|
let
|
|
# The manifest keys must describe the TARGET, not the machine doing the
|
|
# build. `uname` used to supply them, which is correct natively and wrong
|
|
# under cross-compilation: a Windows build on the Linux builder emitted
|
|
# "linux-x86_64-dev" keys, so PackageManagerLib::platformVariantsToTry()
|
|
# (which returns "windows-x86_64-dev" on Windows) would find no entry and the
|
|
# capability module would silently not load.
|
|
hostPlatform = pkgs.stdenv.hostPlatform;
|
|
platform =
|
|
if hostPlatform.isDarwin then "darwin"
|
|
else if hostPlatform.isWindows then "windows"
|
|
else "linux";
|
|
arch =
|
|
if hostPlatform.isAarch64 then "aarch64" else "x86_64";
|
|
suffix = if portableBuild then "" else "-dev";
|
|
in
|
|
pkgs.runCommand "${common.pname}-modules-${common.version}"
|
|
{
|
|
inherit (common) meta;
|
|
}
|
|
''
|
|
mkdir -p $out/modules/capability_module
|
|
|
|
# Copy the plugin library. Every extension is listed explicitly -- an
|
|
# if/elif chain, or a Unix-only pair of globs, installs NOTHING on Windows
|
|
# and still succeeds.
|
|
shopt -s nullglob
|
|
plugins=(${capabilityModule}/lib/*.dylib ${capabilityModule}/lib/*.so ${capabilityModule}/lib/*.dll)
|
|
for lib in "''${plugins[@]}"; do
|
|
cp "$lib" $out/modules/capability_module/
|
|
done
|
|
|
|
# Determine the plugin filename that was copied
|
|
pluginFile=""
|
|
for f in $out/modules/capability_module/*; do
|
|
if [ -f "$f" ]; then
|
|
pluginFile="$(basename "$f")"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -z "$pluginFile" ]; then
|
|
echo "Error: No capability_module library found under ${capabilityModule}/lib" >&2
|
|
ls -la ${capabilityModule}/lib >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
platform="${platform}"
|
|
arch="${arch}"
|
|
suffix="${suffix}"
|
|
|
|
# Create manifest.json for plugin discovery
|
|
# "type" is REQUIRED, not decorative. PackageManagerLib::getInstalledModules
|
|
# enumerates manifests with `types = {"core"}` and skips -- silently, with
|
|
# no diagnostic -- every manifest whose type does not match. Without this
|
|
# field the directory scans clean, the module never enters the registry,
|
|
# and the only symptom is a later "Module not found in known modules:
|
|
# capability_module" from whoever tried to load it. It matches the "core"
|
|
# in logos-capability-module/metadata.json, which is what the lgx-bundled
|
|
# manifest carries.
|
|
cat > $out/modules/capability_module/manifest.json <<EOF
|
|
{
|
|
"name": "capability_module",
|
|
"version": "1.0.0",
|
|
"type": "core",
|
|
"main": {
|
|
"$platform-$arch$suffix": "$pluginFile",
|
|
"$platform-amd64$suffix": "$pluginFile",
|
|
"$platform-arm64$suffix": "$pluginFile",
|
|
"$platform-x86_64$suffix": "$pluginFile",
|
|
"$platform-aarch64$suffix": "$pluginFile"
|
|
}
|
|
}
|
|
EOF
|
|
|
|
echo "Modules directory contents:"
|
|
ls -laR $out/modules/
|
|
''
|