mirror of
https://github.com/logos-co/logos-basecamp.git
synced 2026-08-27 14:51:07 +00:00
* feat(windows): wire Basecamp for the x86_64-windows target
First slice of Stage 4. Basecamp now evaluates for the Windows
pseudo-system up to its first genuinely unported dependency
(logos-design-system); native aarch64-darwin and x86_64-linux
evaluation is unchanged.
Four blockers cleared, found by evaluating and fixing in turn:
1. flake.nix used a hardcoded `systems` list, so packages.x86_64-windows
did not exist. Now routes through logos-nix.lib.forAllTargets, which
keys the cross target as a pseudo-system — that is what lets the 34
`dep.packages.${system}.x` interpolations stay untouched. Bundlers
(nix-bundle-logos-module-install, nix-bundle-dir) are keyed by
buildSystem instead: they RUN on the builder, so taking them from the
target set would hand it a PE it cannot execute.
2. meta.platforms was platforms.unix.
3. krb5 does not cross-evaluate: it carries a host-platform bash, so the
splice fails with "Refusing to evaluate package 'bash-5.3p9'" — an
error naming bash, several levels from the cause. Guarded in both
common.buildInputs and app.nix's qtLibPath.
4. qtwebview propagates qtwebengine — a full Chromium — which fails to
cross-evaluate on cups, three levels from the cause. It is also
entirely unused: no C++ include, no `import QtWebView` in any QML,
and app/CMakeLists.txt's find_package COMPONENTS list omits WebView.
The only surviving mention is a stale line in docs/project.md.
krb5 and qtwebview are GUARDED rather than deleted, so this stays a
Windows-only change. Both look like dead weight on Unix as well, and
dropping them there would shrink every bundle — worth doing separately,
with its own testing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): drop the QML inspector on Windows
logos-qt-mcp is the QML inspector behind the UI test harness. It has no
Windows target and is not needed to run the app -- nix/app.nix already
accepts `logosQtMcp ? null` and gates the inspector on it -- so Windows
builds go without it, and the inspector-dependent outputs are simply
absent from the Windows package set.
With this, logos-basecamp EVALUATES for x86_64-windows end to end; native
aarch64-darwin and x86_64-linux are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): cross-build LogosBasecamp.exe
Basecamp now produces a real Windows executable. Five blockers, each
found by building and each of a kind already seen in this port.
1. abseil-cpp does not cross-compile (absl/base/internal/
thread_identity.cc includes <pthread.h>, absent on mingw+mcfgthread).
It is dead weight -- "absl" appears nowhere in the sources or CMake,
only in nix files and the README. Guarded here; also removed from
logos-package-manager-ui's metadata, which pulled it in separately.
2. installedModules cut to [] on Windows. The install path runs each
module through nix-bundle-logos-module-install -> nix-bundle-lgx ->
nix-bundle-dir, and nix-bundle-dir is ELF/Mach-O only. This keeps the
bundler chain, lgpm and four module repos off the critical path.
3. The hand-rolled configurePhase never passed $cmakeFlags, so
-DCMAKE_SYSTEM_NAME=Windows was dropped. The symptom was nowhere near
the cause: FindThreads probed for pthreads instead of Win32 threads and
Qt6Config reported "Qt6 could not be found because dependency Threads
could not be found".
4. Qt's host-TOOL packages were unreachable. app.nix builds its own cmake
line and never inherited common.cmakeFlags, so adding the flags there
was not enough -- they had to go on the invocation too. Symptom again
misleading: 'Failed to find required Qt component "RemoteObjects"',
when the TARGET Qt6RemoteObjects is present and it is
Qt6RemoteObjectsTools, the host package, that is missing.
5. Two unsuffixed-name defects, the class that has now appeared eleven
times. app/CMakeLists.txt copied `logos_host` without
CMAKE_EXECUTABLE_SUFFIX, and because it is a POST_BUILD step the
failure read "FAILED: LogosBasecamp.exe" with no undefined references
-- the real message was the "Error copying file" line above it. Then
the installPhase's `if [ -f build/LogosBasecamp ]` had no else-branch,
so the mingw build installed NOTHING, exited 0, and produced an output
whose bin/, lib/, modules/ and plugins/ were all empty. It now probes
both spellings and hard-fails with a directory listing.
Also: ENABLE_QML_INSPECTOR is now gated on logosQtMcp != null rather than
on enableInspector alone -- the inspector cannot be on without it -- and
Windows installs the binary directly instead of behind a /bin/sh wrapper
that could not run there.
NOT YET RUN on Windows; that needs the Qt plugin/QML staging proven in
the design-system milestone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): main_ui configures and compiles under cross
Two fixes, both of kinds already seen in this port.
The code generator must RUN on the builder, so it now comes from the
BUILD system via a new logosSdkBuild binding. This is a SPLIT, not a
swap: the same cpp-sdk output carries both the generator binary AND the
target headers/CMake package, so logosSdk stays for
-DLOGOS_CPP_SDK_ROOT. Getting it backwards succeeds natively and, under
cross, puts a PE on the builder's PATH -- the symptom was
"logos-cpp-generator: command not found".
main-ui.nix has its own hand-rolled configurePhase which, like app.nix's,
never passed $cmakeFlags -- so -DCMAKE_SYSTEM_NAME=Windows was dropped
and FindThreads probed for pthreads, surfacing three levels away as
"Qt6 could not be found because dependency Threads could not be found".
It now passes $cmakeFlags and Qt's host-TOOL package paths.
main_ui now configures and compiles. It does NOT yet link: the qt-sdk
static library is pulled in twice, giving "multiple definition of
LogosAPI::LogosAPI". That is a link-graph question, not a Windows
spelling issue, and is the next thing to solve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(windows): name the main_ui plugin .dll, and link it
src/CMakeLists.txt set the plugin SUFFIX with `if(APPLE) ".dylib"
else() ".so"` and no Windows branch, so the cross build emitted
main_ui.so -- a genuine PE carrying a Unix extension. app/window.cpp
looks for ".dll" under Q_OS_WIN, so it would never have been found and
Basecamp would have gone on reporting "No main UI" with nothing
obviously wrong. Windows is now tested before the else-branch, and
nix/main-ui.nix accepts the .dll spelling too.
With the liblogos_core export fix, main_ui links and installs as
main_ui.dll.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: don't block startup on modules that are not loaded
PackageCoordinator guarded its startup IPC with
`if (!client || !client->isConnected()) return;`, which never fired --
see the logos-protocol fix. Past that dead guard it made ~19 blocking
calls (4 directory setters, 8 package_manager subscriptions, 1
package_downloader) against modules that were absent, each waiting 20 s
twice over. All of it runs on the GUI thread inside the Window
constructor (window.cpp invokes createWidget with Qt::DirectConnection),
and main.cpp calls show() only afterwards -- so the window simply did not
appear for ~7 minutes.
Ask the question in-process instead: logos_core_get_loaded_modules, via
the CoreModuleManager this class already holds. No IPC, no timeout. The
same guard already ships in logos-logoscore-cli's daemon, which skips the
identical setEmbeddedModulesDirectory block and reports that package
commands are unavailable. Basecamp already logged "Failed to load
package_manager module by default" roughly 40 s before the first block --
it knew, and ignored it.
The subscriptions are re-armed on CoreModuleManager::coreModulesChanged,
so a module installed later in the session still gets wired up; without
that this would trade a 7-minute stall for a silently non-functional
Modules view, which is the worse bug. Both subscribe paths are
idempotent, and the "not loaded" warning is emitted once rather than on
every stats-timer tick.
Verified on Windows: the guard now fires, the transport warns before the
doomed wait, and Basecamp's real UI renders -- sidebar, Logos mark and
Welcome page -- instead of a blank window.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: guard the remaining package_manager startup calls
fetchUiPluginMetadata() and refreshDependencyInfo() checked only
`!m_logosAPI`, so they still blocked ~20 s acquiring a token for an
absent package_manager after the subscribe paths were guarded. That was
the last blocking wait at startup, and the transport's new
"will block up to 20000 ms" warning is what pointed at it.
fetchUiPluginMetadata reuses its existing metadata-unavailable branch
rather than inventing a new one -- it clears m_appsLoading and emits
uiModulesChanged, so the UI settles instead of sitting in a loading state
for the timeout. refreshRepositories needed no change: it guards on
isConnected(), which now tells the truth.
Measured on Windows with no modules installed: "will block up to" 0,
"Timeout waiting for replica" 0, "Requesting object" 0. The UI renders
identically -- sidebar, Logos mark, Welcome page.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(windows): resolve UI plugins' private DLLs from their own directory
The logos-module chokepoint fix covers LogosModule::loadFromPath, which
is every process-isolated module. Basecamp constructs its own
QPluginLoaders and was not covered: PluginLoader's background pre-load
and finishCppPluginLoad for UI plugins, and window.cpp for main_ui.
Each lives in its own directory (plugins/<name>/), so anything vendored
beside it is invisible to Windows' loader, which searches the
EXECUTABLE's directory rather than the importing DLL's. This works today
only because every DLL happens to be staged next to the exe; a plugin
with a private dependency would fail with "The specified module could not
be found", naming the plugin rather than the dependency.
The references are deliberately not released: these plugins stay resident
for the process lifetime (QPluginLoader's destructor does not unload).
No-op off Windows, so no platform guards at the call sites.
Cross-builds and native build both green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(windows): key the install bundlers by target
nix-bundle-logos-module-install now does its own host/target split
internally, so keying the whole bundler by buildSystem is no longer
right -- that is what made it derive the .lgx variant name and library
extension from the build platform.
installedModules stays [] on Windows for now: with the labelling fixed,
lgpm correctly refuses to install a windows-x86_64 package on a Linux
builder, and cross-installation needs a platform override that lgpm does
not have yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): ship the pre-installed modules
installedModules is no longer cut on Windows. The cross-install path now
works end to end: capability_module, package_manager and
package_downloader are laid out under modules/, main_ui and
package_manager_ui under plugins/, each a real PE with its own DLL
closure and a variant file reading windows-x86_64-dev.
It took a host/target split at every layer, and each failed somewhere
unrelated to itself: the .lgx bundler keyed by target (it decides the
variant name and library extension), lgpm from the build system (it runs
there), a --platform override for cross-installation, and a Nix-double
to lgpm-variant translation.
Verified on real Windows: capability_module and package_manager load,
spawn their hosts, and answer IPC over named pipes -- the log shows
ModuleProxy rejecting unauthorized calls, which is the token flow
talking, not a Windows failure.
KNOWN GAP, and it fails loudly: package_downloader does not load --
"Cannot load library ... package_downloader_plugin.dll: The specified
module could not be found". Its external library
libpackage_downloader_lib.dll needs libcurl-4.dll, which is in neither
the module directory nor bin/. LogosModule.cmake copies an external
library's DLL but not that DLL's own dependencies, so the closure must be
walked to a fixpoint there too -- the same lesson as the Qt plugin and
bundle closures. Strictly better than shipping no modules at all, and the
failure names the file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): import the shared runtime instead of linking it
LogosBasecamp.exe and main_ui.dll now compile with LOGOS_SHARED_USE_DLL
and link an emptied logos-qt-sdk archive, so TokenManager, LogosAPI and
LogosAPIClient come from liblogos_core.dll. Before this each image linked
its own copy and therefore its own function-local statics: the host saved
a capability token into its TokenManager, main_ui read its own empty one,
and every cross-module call was refused.
Measured on real Windows across three runs: "rejecting unauthorized call"
29 -> 0, and "No token found" 29 -> 0 replaced by nine "Found token for
module". All modules still load; no missing-symbol or duplicate-symbol
diagnostics.
NOT fixed by this, and not caused by it: the package_manager_ui icon
still does not appear in the sidebar. The pre-change run had the same
zero ui-host spawns. Sidebar entries come from
PackageCoordinator::getInstalledUiPluginsAsync, which reads
package_manager's installed-package registry -- empty in this payload, so
the list is empty regardless of what is on disk in plugins/. That is a
separate unmet precondition.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: never open a window larger than the screen
setupUi asked for a fixed 1600x900 and nothing anywhere in this repo consulted
the screen -- the <QScreen> include at the top of window.cpp had been unused.
On a display smaller than that the window opens with its right and bottom edges
off-screen, and on Windows it cannot be dragged back into view, so whatever
hangs off is permanently unreachable: in practice the sidebar's bottom-anchored
system buttons and the version label.
Two halves, because one is not enough:
* setupUi clamps the requested size to availableGeometry, so an oversized
window is never mapped in the first place.
* showEvent re-checks once. resize() sets the CLIENT size while
availableGeometry() bounds the FRAME, and the decoration margins are not
knowable until the platform window exists -- measured, the clamped client
filled the work area exactly and the frame still hung 72px below it.
Both are skipped under the offscreen platform (QOffscreenScreen hardcodes its
geometry) so headless UI doctests keep their full-size window and qt-mcp's
scene-position clicks still land.
Three things that look like they could be simpler and cannot, each measured:
* The showEvent check compares SIZES, not rectangles. QRect::contains() is the
obvious formulation and misfires on both platforms: on Windows
frameGeometry() comes from GetWindowRect, which includes DWM's invisible
resize border (~13px/side at 192dpi), so a correctly placed window never
reports as contained and this would run on every launch; on macOS Qt reports
a frame whose title bar sits above availableGeometry's origin, and acting on
that moved the window 66px down at launch on a platform where nothing is
broken.
* The shrink is bounded by the current size. Without boundedTo, a window that
is merely too TALL is also stretched to the full available WIDTH -- observed
on this path: 1600 logical wide silently became 1715.
* The clamp is to availableGeometry exactly, not to a fraction of it. Anything
less is not a no-op on displays that fit the design size, and would undo the
widening documented at the resize() call (1600 was chosen so the Package
Manager's Action and Description columns are not clipped).
Measured on Windows (real hardware, DPI-aware capture, visible frame via
DWMWA_EXTENDED_FRAME_BOUNDS against a 3456x1826 work area):
normal session bottom overflow 45px -> 2px (rounding), width still 1600
small desktop ~550px of width off-screen -> right 14px, bottom 2px
below app minimum bottom 126px -> 33px (Basecamp's own 800x600 floor)
Verified a no-op elsewhere by running the identical logic against Qt 6.11.1 on
macOS: constructor clamp no-op, showEvent reports FITS, size and position
unchanged; and under QT_QPA_PLATFORM=offscreen both paths are skipped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: re-fit the window when the screen shrinks under it
The launch clamp (76e0f6d) only runs once. An RDP session with
/dynamic-resolution, or a taskbar appearing, shrinks the desktop under a running
window and nothing reacted -- the window was left overflowing, with the
sidebar's bottom-anchored system buttons under the taskbar and unreachable.
Watches availableGeometryChanged on the screen the window is on, coalesced
through a 150ms settle timer, and re-fits. Measured on real Windows, same
binary pair and run conditions, work area shrinking 1922 -> 1826 physical px:
shipped overflow (3,3) -> (4,111) window did NOT shrink NO-REFIT
re-fit overflow (3,3) -> (3,2) window shrank REFIT-WORKS
An 8-agent design pass was REFUTED 3/3 by its own reviewers and this is the
corrected version, not that design. Three of its defects were real:
* THE RATCHET. It fitted to min(previous, available) on every event -- a
monotone non-increasing map, so the window converges on the smallest work area
seen all session and never grows back. A taskbar that auto-hides and
reappears, or an RDP window dragged smaller and back, would have shrunk
Basecamp permanently. Fixed by bounding against m_desiredSize: the launch
design size, replaced by any resize the USER performs, so the window grows
back but never past what was asked for.
* NO isVisible() GATE. closeEvent hides to the tray on EVERY platform and hide()
does not set Qt::WindowMinimized, so a window-state gate does not cover it.
The fit would have run and latched on a hidden window whose frame geometry is
meaningless.
* A QRect LATCH that could not distinguish "the screen shrank under the window"
from "the window moved to another monitor". Dropped entirely: the
`target == size()` early return is both the idempotence and the
non-recursion, so there is no latch to fall out of sync -- and no need for the
design's re-entrancy flag, which a reviewer showed could never be true on
entry.
Three things kept from that design because they beat the obvious choice:
screenChanged re-points the watch but never fits (Qt migrates windows onto a
1024x768 lock screen on RDP disconnect, and fitting there would shrink with no
way back); the settle timer, because the fit shrinks and chasing a drag's
intermediates would latch its smallest frame; and connections held by handle
with a QPointer<QScreen>, because a session lock adds and then DELETES a screen.
Every guard lives inside fitFrameToAvailableGeometry() rather than in the
callers, so all three entry points are covered by construction -- a reviewer
caught that the design left the scheduler unguarded while changeEvent called it
on every state change.
Two bugs found in this implementation while writing it: QPointer<QScreen> needs
QScreen COMPLETE (QPointer static_asserts QObject derivation), caught by the
cross build; and resizeEvent recorded the MAXIMIZED size as the user's intent,
so a later un-maximize would try to grow the window to full-screen size. Also
replaced `if (!m_screenChangedConnection)` with an explicit QPointer<QWindow>
comparison: Qt documents Connection::operator bool only as "the connect() call
succeeded" and says nothing about sender lifetime, so a stale-but-truthy
connection would have silently stopped re-pointing the watch.
Verified a no-op elsewhere by running the shipped function body against Qt
6.11.1 on macOS: constructor clamp no-op, fit reports FITS, size and position
unchanged; and under QT_QPA_PLATFORM=offscreen both paths are skipped, so
headless UI doctests keep their full-size window.
NOT verified: whether an RDP resolution change arrives as
availableGeometryChanged on the same screen rather than a screen remove/add,
which is deliberately not fitted on. The work-area path is proven; that one is
inferred.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(windows): ship the portable Basecamp with its modules pre-installed
The distributed build cut installedModules to [] on Windows, and skipped
nix-bundle-dir was never the reason -- the comment claimed bundle.sh
would emit an empty payload, which is true, but the conclusion should
have been "don't run bundle.sh on Windows", not "ship no modules".
A PE needs no relocation: its import table carries DLL base names only
and Windows searches the executable's own directory first, so the build
output is already position-independent, and win-dll-link.sh has staged
the closure into bin/. nix-bundle-lgx reached the same conclusion for
module payloads in d539a3f; this applies it to the app bundle.
Without this there is no portable Windows Basecamp with a working
package manager, and therefore no way to install anything from a
catalog: platformVariantsToTry() appends "-dev" to every variant unless
LGPM_PORTABLE_BUILD is set, so the dev build asks for
`windows-x86_64-dev` while a published package is `windows-x86_64` and
every catalog row reports Not Available.
Native builds are unchanged by construction -- bundleFor is dirBundler
off Windows.
* fix(windows): app.nix copied no shared libraries at all
The liblogos shared-library loop globbed *.dylib and *.so only. On Windows
it matched nothing, and because each candidate is guarded by `[ -f "$f" ]`
with a trailing `|| true`, it copied zero files and exited 0.
Thirteen DLLs sit in that same lib/ -- liblogos_core, libpackage_manager_lib,
liblgx, icuuc76, icudt76, libsodium-26 and the mingw runtime -- and
LogosBasecamp.exe imports liblogos_core.dll DIRECTLY. The result is
0xC0000135 (STATUS_DLL_NOT_FOUND) before main(): no Qt error, no stderr, no
output whatsoever. It has only ever run on Windows because those DLLs were
hand-staged into the payload.
Adds *.dll to the glob, sends DLLs to bin/ rather than lib/ (Windows searches
the executable's own directory, has no rpath, and win-dll-link.sh only
processes $out/bin), and makes copying zero libraries a hard error -- this
loop exiting 0 having done nothing is the whole defect.
Also corrects the comment on bundleFor. It claimed skipping nix-bundle-dir on
Windows was right because a PE needs no relocation. That conflates the
bundler's two jobs: relocation (genuinely unnecessary on PE) and Qt plugin /
QML / qt.conf staging (required on every platform, since those DLLs are
LoadLibrary'd and appear in no import table). Skipping it yields a bundle with
no qt.conf and no QPA plugin. The comment now says so and names the real fix.
* fix(windows): stage liblogos' own dependency DLLs, and prove the closure
461ddcd copied ${logosLiblogos}/lib into bin/, which was necessary but not
sufficient. liblogos_core.dll and logos_host.exe each import libspdlog.dll and
libfmt.dll, and those two live in liblogos' BIN, not its lib/ -- nixpkgs'
win-dll-link.sh walked logos_host.exe's imports and staged the closure there.
spdlog is a buildInput of liblogos, not of basecamp, so basecamp's own
win-dll-link pass could not resolve them either.
Measured on the built bundle with a PE-capable objdump, fixpoint over 52 seed
PEs: with lib/ copied and bin/ not, libspdlog.dll and libfmt.dll were the only
non-OS names left unresolved for LogosBasecamp.exe. That is still 0xC0000135
before main() -- the exact symptom copying lib/ was meant to cure. Mirrors what
logos-package-manager/nix/lib.nix already does with liblgx's closure.
This runs in postFixup, not installPhase, and the ordering is load-bearing:
win-dll-link.sh registers _linkDLLs in fixupOutputHooks, which run BEFORE
postFixup. Doing it in installPhase makes the already-present skip vacuous, so
Qt6Core/Qt6Network/Qt6RemoteObjects get copied out of liblogos as real files
while Qt6Gui/Qt6Widgets stay symlinked into basecamp's own qtbase -- a bin/
with a MIXED Qt in it the moment those pins diverge. Verified: in postFixup the
loop stages exactly 2 files and every Qt DLL stays a symlink.
The check asserts the invariant rather than the mechanism: every DLL that
liblogos_core.dll imports AND that liblogos itself ships must resolve next to
the executable. Names liblogos does not ship are OS DLLs, so no hand-maintained
system-DLL list can rot. A zero import count is treated as a measurement
failure (objdump with no PE target), never as a pass. Both failure paths were
observed firing, not assumed.
Native Linux is unchanged: same file set, byte-identical staged .so files.
* fix(windows): drive the bundle's DLL import closure to a fixpoint
$out/modules/<m>/ and $out/plugins/<p>/ were never walked at all.
nixpkgs' win-dll-link.sh registers _linkDLLs in fixupOutputHooks, and
that entry point only ever processes $prefix/bin. modules/ and plugins/
are filled in installPhase from .lgx payloads and UI-plugin outputs -- in
directories the hook does not look at -- so nothing ever read their
import tables. Measured on this bundle before this commit: main_ui.dll
imports Qt6Qml.dll, Qt6QuickControls2.dll and Qt6QuickWidgets.dll and not
one of them was in plugins/main_ui/ or in bin/.
One pass would not have been enough either: you cannot read the imports
of a DLL that is not there yet. The sweep needs five rounds here, and
round 4 is not decoration -- Qt6QmlWorkerScript.dll is reachable only
through Qt6QmlMeta.dll, which round 3 staged.
So: index every .dll in the build closure (closureInfo over the same
roots as buildInputs -- a PE embeds no store paths, so this derivation's
own references are useless for the purpose), then walk bin/, lib/,
modules/* and plugins/* to a fixpoint, staging what is missing into bin/
and FAILING on anything the closure cannot provide. Result: 9 DLLs staged
over 5 rounds, and every non-system import in the bundle now resolves.
This is a hard error and not a warning because the failure it prevents is
totally silent: a missing DLL is 0xC0000135 before main() runs, or
ERROR_MOD_NOT_FOUND blamed on the plugin, with no Qt message and no
stderr. It exits 0 at build time every time.
It is also the only place the windowsHostLibs list in nix-bundle-lgx can
be falsified. That list DELETES DLLs from every .lgx payload on the claim
that the host ships them; nothing there can check the claim, and one
wrong entry (libiconv-2.dll, reached via libcurl-4 -> libidn2-0) shipped
a package_downloader that could not load on real Windows. A/B: with that
entry restored, this build now fails and names the DLL and its three
importers instead of producing a broken bundle.
Two anti-vacuity guards, since a wrong answer here always looks like
success: an empty DLL index is fatal, and reading zero imports across the
whole bundle is treated as a measurement failure (an objdump without a PE
target prints nothing, and then every import "resolves").
Also fixes a silent no-op next door: `if ls "$sdk/lib/"liblogos_sdk.*`
runs with nullglob set, so an unmatched glob left `ls` with NO arguments,
which listed the working directory and succeeded -- after which
`cp -L "$out/lib/"` ran with a single argument and printed
"cp: missing destination file operand", guarded by `|| true`. Test each
candidate instead. This is the only change outside the isWindows guard;
the native output tree is byte-identical apart from self-referential
store paths.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(windows): the import gate could not see 12 of its own roots
Three holes, all silent, all in the check that exists to catch silent holes.
`_pe_roots` used `find ... -type f`, which does not match a SYMLINK -- and 12
of the 36 PE entries in $out/bin are relative symlinks created by nixpkgs'
win-dll-link.sh (Qt6Core, Qt6Gui, Qt6Widgets, Qt6Network, Qt6RemoteObjects,
libcrypto, libssl, libzstd, libb2, libpng16, libpcre2, libdouble-conversion).
Their import tables were never read. Four non-system names in this tree are
imported only by those roots, three of them entries on nix-bundle-lgx's
windowsHostLibs -- the list this gate is the only thing capable of falsifying.
`find -L` makes -type f test the target. Measured on the real bundle: root set
105 -> 117, exactly the 12 that were invisible.
Every branch also carried `-maxdepth 1`, so a PE below the first level of lib/,
modules/<m>/ or plugins/<p>/ was skipped with no message. Nothing in the current
bundle sits there, but it was demonstrated by putting one there: the gate stayed
at rc=0 while a full-depth sweep found 8 unresolved imports. Depth limit dropped.
A dangling symlink is invisible to `find -L -type f` and unopenable at runtime,
so it is now a hard error rather than a quiet omission from the root set.
And the success line claimed "Every non-system PE import in bin/, lib/, modules/
and plugins/ resolves" while checking neither the symlinks nor anything deeper
than depth 1. Since it was the only output on the pass path it read as proof of
full coverage. It now states the size of what it checked:
PE import closure verified: 117 root(s), 7141 import(s) read, 0 unresolved.
* fix: give installing the same IPC budget as downloading
Downloading had a five-minute deadline (kDownloadIpcDeadlineMs) while
installing was left on the 20 s default from logos_mode.h — backwards.
Downloading is network-bound and retryable; installing is disk-bound over a
payload package_manager reads, gunzips and tar-parses three times and
Merkle-hashes twice, then extracts and copies. Measured at ~1 s on a warm dev
box and unbounded on a slow disk, a large package, or a machine whose
antivirus scans each DLL as it lands.
Blowing the deadline cancels none of that work: the files still install and
only the reply is abandoned, so the user is told the install failed when it
did not — which is exactly what a Windows install looked like while a separate
read-only-payload bug was making the reply never arrive at all.
Also names the empty-reply case instead of reporting the generic
"Installation failed". A dropped or timed-out call arrives here as a
default-constructed QVariantMap because installPluginAsync's callback has no
error slot; every real reply carries "path", so an empty map is never a
genuine answer. That inference is INTERIM: logos-cpp-sdk master f3369fa (#132)
already provides installPluginAsyncResult with logos::AsyncResult<T> carrying
the real CallError, and both call sites should switch to it once
feat/windows-cross picks that up — it is 12 commits behind master today. The
comments say so at both sites. f3369fa did not change the async default
deadline, so this Timeout is required either way.
* fix: tell a dropped install reply apart from a failed install
Both call sites used installPluginAsync, whose callback receives a bare
QVariantMap — so a transport failure and a provider that legitimately returned
an empty map are the same thing to the caller. A timed-out call therefore
surfaced as the generic "Installation failed", pointing the user at a package
that was very likely fine, and on Windows that is exactly what a separate
read-only-payload bug produced: every file installed, no reply came back, and
the row went red.
Switches to installPluginAsyncResult, whose callback takes
logos::AsyncResult<QVariantMap> and carries the value and the error together.
Transport failure is now checked FIRST, before `value` is read at all — on a
timeout `value` is default-constructed, and interpreting it as an install
verdict is the precise mistake this channel exists to remove.
The message says the deadline expired and that the package may in fact be
installed, because blowing the deadline cancels nothing already underway.
This replaces an interim heuristic that inferred a dropped reply from an empty
map. The inference was sound — a real reply always carries "path" — but it was
an inference. The real channel arrived on this branch with logos-cpp-sdk
f3369fa (#132, "async callers can see the error, sync callers can set a
deadline"), picked up by the master merge; the comments left at both sites
named it as the thing to switch to.
Builds for x86_64-linux and x86_64-windows in both repos.
* chore(deps): re-pin the chain to its merged revs
Levels 1-8 of the Windows chain are merged. This branch was locked to
pre-merge revs of every one of them, so it could only evaluate against the
unmerged branches.
Level 9; the workspace re-pin follows once this lands.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
59 lines
2.8 KiB
CMake
59 lines
2.8 KiB
CMake
# Windows: take the shared C++ runtime from liblogos_core.dll, not from the
|
|
# static archives.
|
|
#
|
|
# LogosBasecamp.exe and main_ui.dll run in ONE process, and both link
|
|
# liblogos_qt_sdk.a / liblogos_protocol.a. On ELF and Mach-O that is harmless:
|
|
# liblogos_core exports the symbols and the loader interposes them, so there is
|
|
# one LogosAPI, one TokenManager, one of everything. PE has no interposition, so
|
|
# each image binds its own static copy and gets its own function-local statics.
|
|
# The visible symptom was 29 "ModuleProxy: rejecting unauthorized call" lines and
|
|
# no package_manager_ui in the sidebar: the host wrote the capability token into
|
|
# its TokenManager and the plugin read a different, empty one.
|
|
#
|
|
# liblogos_core.dll now exports those symbols explicitly (see
|
|
# logos-liblogos/src/CMakeLists.txt) and the consumers declare them
|
|
# __declspec(dllimport) via LOGOS_SHARED_USE_DLL. That alone is NOT enough,
|
|
# because ld picks archive members by object file for reasons that have nothing
|
|
# to do with our symbols: measured here, main_ui's AppsModel.cpp.obj referenced
|
|
# std::string's move constructor, ld satisfied it out of liblogos_qt_sdk.a's
|
|
# logos_api.cpp.obj, and that one object dragged in LogosAPI, LogosAPIClient and
|
|
# TokenManager behind it -- each then colliding with the DLL's export
|
|
# ("multiple definition of `LogosAPI::LogosAPI'"). No export list can prevent
|
|
# that; the archive simply must not be a candidate.
|
|
#
|
|
# So the archives are replaced by an EMPTY one. Everything else the imported
|
|
# targets carry -- include directories, Qt/OpenSSL/Boost/nlohmann link
|
|
# interfaces, compile features -- is untouched, which is why this is done by
|
|
# repointing IMPORTED_LOCATION rather than by dropping the
|
|
# target_link_libraries() call and re-listing the usage requirements by hand.
|
|
#
|
|
# Off Windows this file is never included: ELF/Mach-O already resolve to the one
|
|
# provider, and there is nothing to fix.
|
|
|
|
function(logos_use_shared_runtime_from_dll)
|
|
if(NOT WIN32)
|
|
return()
|
|
endif()
|
|
|
|
# One empty archive per build tree, shared by every target that needs it.
|
|
set(_stub "${CMAKE_BINARY_DIR}/logos_shared_from_dll_stub.a")
|
|
if(NOT EXISTS "${_stub}")
|
|
execute_process(
|
|
COMMAND "${CMAKE_AR}" crs "${_stub}"
|
|
RESULT_VARIABLE _ar_rc
|
|
OUTPUT_QUIET ERROR_VARIABLE _ar_err)
|
|
if(NOT _ar_rc EQUAL 0 OR NOT EXISTS "${_stub}")
|
|
message(FATAL_ERROR
|
|
"Could not create the empty archive that stands in for the "
|
|
"logos static libraries on Windows: ${_ar_err}")
|
|
endif()
|
|
endif()
|
|
|
|
foreach(_tgt IN LISTS ARGN)
|
|
if(TARGET ${_tgt})
|
|
set_target_properties(${_tgt} PROPERTIES IMPORTED_LOCATION "${_stub}")
|
|
message(STATUS "Windows: ${_tgt} provided by liblogos_core.dll, static archive suppressed")
|
|
endif()
|
|
endforeach()
|
|
endfunction()
|