Session 07: promote package-proof to stable - #527
Conversation
…to cdx/session-07-package-proof
vcpkg's gperf port requires autoconf, autoconf-archive, automake, and libtool (libtoolize) from the system package manager. Without them the Linux_AppImage workflow fails at 'VCPKG: Install Project Dependencies' when building fontconfig's gperf dependency. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
The version and capabilities probes run before $pdfTool was assigned, causing Windows_MSI to fail at Validate staged install tree under Set-StrictMode. Move the Join-Path assignment above the first use. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
…to cdx/session-07-package-proof
…age-proof # Conflicts: # .github/workflows/LinuxInstall.yml
…to cdx/session-07-package-proof
Combine Linux AppImage workflow deps: keep autoconf/automake/libtool for gperf/vcpkg (#523) and libfontconfig1-dev from stable. Retain stable's VCPKG_BINARY_SOURCES cache wiring. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
…dio-berry/loop into cdx/session-07-package-proof Co-authored-by: michael berry <mberrys@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 59d0daa2f8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Remove continue-on-error from the Sentry debug-file upload steps in WindowsInstall.yml and reusable-windows.yml. Project targeting already resolves the numeric id from LOOP_SENTRY_DSN; a 404 must fail the job instead of producing a green MSI with a skipped upload. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Install autoconf, autoconf-archive, automake, and libtool on reusable-linux and fuzz apt steps so vcpkg can build gperf via fontconfig, matching LinuxInstall.yml (#523). Fixes Release Gate linux/build and fuzz_regression failures on PR #527. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Stage libqoffscreen.so (and optional libqminimal.so) under install/usr/lib/platforms and write install/usr/bin/qt.conf before linuxdeployqt so PdfTool preflight works when the smoke harness sets QT_QPA_PLATFORM=offscreen and strips developer Qt paths. Matches the Windows MSI pattern of a self-describing staged tree and the prior Session 07 offscreen staging work that did not land on this branch. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Copy the built Loop.Canvas QML module into the staged install tree, bundle qoffscreen.dll for QT_QPA_PLATFORM=offscreen, and set Qml2Imports=qml;. so LoopEditor --quick-smoke resolves packaged QML without developer Qt paths. Also stage Loop.Canvas in Linux_AppImage for coherent cross-platform packaging. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Replace Linux heredoc and Windows here-string qt.conf writers with printf and a PowerShell string array so [Paths] starts at column 0. Indented workflow bodies were emitting invalid qt.conf and nullifying offscreen/plugin path staging (#508). Co-authored-by: michael berry <mberrys@users.noreply.github.com>
…to cdx/session-07-package-proof
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24ad77d102
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Stage libqsqlite.so into install/usr/lib/sqldrivers before linuxdeployqt strips optional SQL drivers from the ephemeral Qt tree. PdfTool preflight needs QSQLITE for operation-history writes during AppImage smoke. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Stage qsqlite.dll from the Qt install into install/usr/bin/sqldrivers before packaged smoke runs with developer Qt env vars stripped. Mirrors the Linux libqsqlite.so staging added in 88ce56b and fails the job if the driver is missing. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
LoopEditor now prepends the executable directory to Qt library paths before QGuiApplication loads QPA plugins, matching PdfTool's packaged- smoke fix. WindowsInstall stages LoopEditorQuickplugin and LoopLibQuickplugin beside the copied Loop.Quick and Loop.Canvas modules, extends windeployqt QML scan paths to the built modules, and writes qt.conf with a literal Qml2Imports=qml;. line (hosted PowerShell was dropping the ';.' suffix from the prior array writer per run 33669439530 evidence). Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Linux packaging uses download_verified.sh --gh-asset for appimagetool, type2-runtime, and linuxdeployqt. Hosted runners can return HTTP 403 for unauthenticated Releases API asset fetches (run 33687722897). Mirror the existing PowerShell helper: send Authorization: Bearer GITHUB_TOKEN when the job token is available and retry transient curl failures. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
…ML plugin DLL staging Linux AppImage smoke failed because linuxdeployqt never scanned LoopEditor QML imports, so qtquickcontrols2plugin was absent from the packaged tree. Pass -qmldir for the editor and built Loop modules and fail deploy if the Controls plugin is still missing. Windows MSI deploy failed because Copy-BuiltQmlPlugin required LoopEditorQuickplugin.dll and LoopLibQuickplugin.dll artifacts that the static LoopEditorQuick build never produces. Remove that staging step; windeployqt and the existing LoopLibQuick.dll install path already cover the runtime closure. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
…outputs LoopEditorQuick is a static qt_add_qml_module linked into LoopEditor, so there is no LoopEditorQuickplugin.dll to copy. LoopLibQuick is SHARED and emits LoopLibQuickplugin next to its qmldir under build/LoopLibQuick/Loop/Canvas. Windows deploy now asserts the built Loop.Quick and Loop.Canvas module trees include qmldir and qmltypes, then copies LoopLibQuickplugin.dll from the CMake output directory into qml/Loop/Canvas and the install bin. Linux staging verifies libLoopLibQuickplugin.so and the module metadata before deploy. Keeps Linux -qmldir scanning for qtquickcontrols2plugin. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
LoopLibQuickplugin is a separate Qt QML MODULE target that CMake emits at build/LoopLibQuick/Loop/Canvas/libLoopLibQuickplugin.so, but it is not in the LoopEditor link closure. Package CI only built LoopEditor and friends, so the 6c7a16e pre-deploy assert failed even though the module metadata existed. Add LoopLibQuickplugin to the Linux and Windows package build targets and discover the plugin with find before staging so versioned .so names still resolve. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
linuxdeployqt -qmldir does not bundle libqtquickcontrols2plugin.so for qrc-resident LoopEditor QML imports. After deploy, copy the QtQuick/Controls module tree from QT_ROOT_DIR into install/usr/lib/qml and fail if the plugin is still missing, matching the manual QSQLITE staging pattern. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Both packaging jobs failed before reaching packaging, from one change: moving the Qt closure from an explicit install(DIRECTORY ... REGEX) block to qt_generate_deploy_qml_app_script left three gates asserting on the old manifest-tracked layout. Windows died in cmake --install, before WiX. MSVC_REDISTRIBUTABLES_PATH is exported as a native Windows path and was embedded verbatim into the generated cmake_install.cmake, where "C:\Program Files\..." raises "Invalid character escape '\P'". Normalise it with file(TO_CMAKE_PATH), matching the get_filename_component() branch that already normalised. Linux died in verify-loop-surface, before AppImage. Two causes: the scan root had been narrowed to install/usr/bin, but on Linux the product libraries install to usr/lib -- only Windows collapses lib onto bin -- so LoopLibCore and LoopLibQuick could not be found; and the install-manifest comparison rejected qt.conf, which the deploy script writes outside install() and which therefore can never appear in install_manifest.txt. Hold only first-party payload to install_manifest.txt and let the deployer own the Qt closure. The missing-file direction stays strict, so a first-party artifact that skips install() still fails closed. The widgets-free contract no longer has a source regex to inspect. Enforce it against the real deployed tree instead, via the scan_install_tree path the verifier already implements, wired into both packaging jobs. Restore LOOP_INSTALL_QT_DEPENDENCIES as the switch for shipping a Qt closure: it had become orphaned, so the Flatpak, Docker, fuzz and reusable Linux builds that configure it OFF were getting a bundled Qt anyway. Make it explicit in the two packaging workflows now that it is load-bearing. Also correct the changelog Breaking-Change field to the required yes/no spelling, and the target count that this change's retirement of LoopEditorQuick moved from 70 to 69. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UwdQdPY7TZ1fQ1cvpNrpaq
Six test targets still linked LoopEditorQuick, which this change folded into the LoopEditor executable. CMake treats an unknown target name in target_link_libraries as a raw -l flag, so configure kept succeeding and the breakage stayed invisible until the widgets-free gate stopped failing first and the build actually reached these targets: tst_quickdocumentmodeltest.cpp:2:10: fatal error: quickdocumentmodel.h: No such file or directory LoopEditorQuick had carried its source directory as a PUBLIC include, so retiring it took the LoopEditor headers off every dependent's include path. Compile the LoopEditor sources each test needs directly into it and add the LoopEditor include root, matching how ProductQuickAccessibilitySmoke already reaches the same sources now that no library exports them. Each target gets only what its test includes, so the deliberately minimal link lines are preserved: UnitTestsDocumentViewSession keeps Core/Gui/Test with no QML engine, per architecture invariant I24. UnitTestsQuickAccessibility also drops LoopEditorQuickplugin and LoopEditorQuickplugin_init, retired with the library. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UwdQdPY7TZ1fQ1cvpNrpaq
Packaging only ran on workflow_dispatch with a hand-supplied source_sha, so no pull request ever proved the MSI or the AppImage. A one-character CMake path bug reached this branch and survived three days of green push CI because nothing automatic ever ran cmake --install on Windows. Add a pull_request trigger to both packaging workflows, resolving the checkout to the dispatch input or the PR head. The exact-SHA verification is unchanged and still runs on both paths: a PR head SHA is a full 40-character SHA, so the integrity check holds. Narrowing this with a paths: filter is a one-line edit if the runner cost bites. Also defuse three assertions that report ordinary work as a contract breach: - The packaging build command was pinned as an exact string, -j6 and target order included. Pin the target list instead, which is the actual contract. - counts["targets"] was pinned to a literal. It is the size of an inventory that grows with ordinary development, and validate_contract() already checks what the evidence means, so drop it. The plugin ledger and the UI form ledger are finite, so keep those but assert the set and the ledger length rather than a hand-maintained number: a thirteenth plugin now names itself instead of arriving as "13 != 12". - The supply-chain policy required a linuxdeployqt pin while the workflow contract forbade the workflows from mentioning the tool. It is no longer used, so drop the pin and its required-tools entry. ubuntu-22.04 stays pinned deliberately: it sets the AppImage glibc floor. It now carries a comment saying so, so retiring it reads as the decision it is rather than an assertion to satisfy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UwdQdPY7TZ1fQ1cvpNrpaq
The AppImage job now reaches packaging and installs cleanly, and fails on
the next thing: forty-odd Qt plugins reported as Loop artifacts.
product-surface: unmanifested first-party artifact:
usr/lib/qml/QtQuick/Controls/libqtquickcontrols2plugin.so
Qt names its plugins lib<something>plugin.so, and the first-party glob
lib*Plugin* is matched case-insensitively, so it claims every one of them.
What used to stop that is ignored_artifact_paths, but its patterns describe
a flat layout (qml/*, platforms/*) while the deploy script stages modules at
usr/lib/qml/<Module>/... and plugins/<type>/..., which those patterns never
match. Under linuxdeployqt none of this was in the tree at verification time,
so the mismatch could not show.
Name the deployed layout: usr/lib/qml/* and plugins/*. Loop's own plugins
install to usr/lib/loop and usr/lib/pdfplugins, so neither pattern reaches
them, and a regression test pins that in both directions.
This narrows only the first-party classification. Detection of forbidden
installed artifacts runs against every file in the tree and is not gated by
it, so the guarantee that a retired plugin cannot ship is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UwdQdPY7TZ1fQ1cvpNrpaq
Five of the six retargeted tests build. UnitTestsShellKeyboard does not: LoopEditor/editorhost.cpp:30: LoopLibQuick/sources/loopcanvasitem.h:38:10: fatal error: QQuickItem: No such file or directory editorhost.cpp includes loopcanvasitem.h, which includes <QQuickItem>, and this target's link line names Qt6::Qml but not Qt6::Quick. It compiled before because the retired LoopEditorQuick linked Qt6::Quick PUBLIC and passed it down; LoopLibQuick does not. Naming it restores exactly the environment the target already had, and matches UnitTestsEditorHost, which compiles the same sources and builds. Qt6::Widgets stays absent, which is the invariant this file is protecting: I21 for the interaction tests and I25 for UnitTestsQuickCanvas both turn on the missing Widgets link, not on Quick. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UwdQdPY7TZ1fQ1cvpNrpaq
The AppImage job now builds, installs and clears both verification gates, and fails one step later: scripts/smoke-test-install.ps1:80 (Join-Path $env:ProgramFiles "share\loop\profiles") Cannot bind argument to parameter 'Path' because it is null. This step is new here; stable never ran smoke-test-install.ps1 on Linux. The script is Windows-shaped throughout -- $env:ProgramFiles, backslash path literals that become literal filenames on Linux, .exe and .dll suffixes at ten call sites, and an OCR sidecar block that invokes a .cmd mock with no Linux equivalent. Making it portable is real work with judgement calls in it, not a null guard, and it is not what this change is for. Remove the step and leave Linux where stable had it. Windows keeps its own relocated-install smoke unchanged, and Linux keeps the AppImage smoke test that runs against the packaged artifact. Cross-platform smoke coverage for the relocated Linux tree stays worth having and is filed as follow-up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UwdQdPY7TZ1fQ1cvpNrpaq
…yout Three CI failures, two root causes, verified against run 33808874249/33808874196: 1. Windows MSB3191 + Linux QML No-such-file (one root): qt_add_qml_module QML_FILES used ../LoopEditor/qml/*.qml. The '..' makes Qt derive qmlcache dir ProductQuickAccessibilitySmoke_.._LoopEditor, which MSBuild rejects (MSB3191), and misplaces the embedded resource as qrc:/qt/qml/Loop/LoopEditor/qml/Main.qml so loadFromModule Loop.Quick/Main exits 2 on Linux. Fix: mirror the six QML files under ProductQuickAccessibilitySmoke/qml/ (byte-identical, LoopEditor/qml stays the single source of truth) and list them without '..'. Proven locally with Qt 6.11.1/MinGW: fixed layout configures, builds with a clean ProofSmoke_qml qmlcache dir, and loadFromModule resolves LOAD_OK; the '..' layout fails Qt file staging on Windows by construction. 2. Windows relocated/staged/MSI smoke Could not locate loop-default.json: Resolve-ProfilesDir assumed one depth and its comment named a deleted plugin owner. Fix: bounded upward walk (InstallDir + 3 ancestors x share/usr-share) covering staged bindir, relocated bindir and MSI install-root layouts, keeping the legacy ProgramFiles fallback; comment now names PdfTool as the install owner. The smoke also accepts an install root for binaries (MSI .../LOOP -> .../LOOP/usr/bin) via a resolved binDir used by every binary-relative path. 3. Relocated-tree InstallDir pointed at a phantom level: Copy-Item to a nonexistent destination copies contents directly, so the tree lands at relocated/usr/bin, not relocated/install/usr/bin. The workflow now passes the real path. Out of scope, intentionally untouched: heat already harvests the whole build/install tree (profiles reach the MSI); MSIX/ZIP pack only usr/bin because AppxManifest.xml must sit at the package root and MSIX is not Session 07 gate evidence per docs/PLATFORM_SUPPORT.md.
Summary
Fixes Linux AppImage run 33702376042 (
cb2a0a6c): Linux Deploy Qt passed LoopLibQuickplugin staging but failed post-check becauselibqtquickcontrols2plugin.sowas absent underinstall/usr/lib/qml.-qmldir(from5102a008) does not bundle QtQuick.Controls for qrc-resident editor QML. This change explicitly copies the module from the runner Qt install, same pattern as QSQLITE.Fix
After
linuxdeployqt, copy$QT_ROOT_DIR/qml/QtQuick/Controls→install/usr/lib/qml/QtQuick/Controls(includeslibqtquickcontrols2plugin.soand style submodules). Fail if source module or plugin is missing, or if staged tree still lacks the plugin after copy.Preserved
LoopLibQuickpluginbuild target, Linux QSQLITE, authenticated downloads, Windows packaging, identity contract (noloupe-pdf).-qmldirflags remain but are not relied on for Controls.Verification
python3 -m unittest scripts.ci.test_workflow_contracts -v— passNext step
Re-dispatch
Linux_AppImagewith full tip SHA when ready. Merge on hold.