diff --git a/.github/workflows/flatpak-spike.yml b/.github/workflows/flatpak-spike.yml index 26a50976..52a41bf4 100644 --- a/.github/workflows/flatpak-spike.yml +++ b/.github/workflows/flatpak-spike.yml @@ -86,6 +86,20 @@ jobs: echo "- \`$id\` → **NOT PUBLISHED**" >> "$GITHUB_STEP_SUMMARY" fi done + # Enumerate rather than guess. Run 31602275749 died in bindgen with + # "Unable to find libclang", so an LLVM extension is required and its exact + # name is not something to discover one 8-minute run at a time. This prints + # every SDK extension published for this runtime version. + { + echo + echo "
All org.freedesktop.Sdk.Extension.* on 24.08" + echo + printf '%s\n' "$CATALOG" \ + | awk '$1 ~ /^org\.freedesktop\.Sdk\.Extension\./ && $2 == "24.08" { print "- `" $1 "`" }' \ + | sort + echo + echo "
" + } >> "$GITHUB_STEP_SUMMARY" - name: "Stage B — install runtime, SDK, base and extensions" id: install @@ -97,7 +111,8 @@ jobs: org.freedesktop.Sdk/x86_64/24.08 \ org.electronjs.Electron2.BaseApp/x86_64/24.08 \ org.freedesktop.Sdk.Extension.node22/x86_64/24.08 \ - org.freedesktop.Sdk.Extension.rust-stable/x86_64/24.08 + org.freedesktop.Sdk.Extension.rust-stable/x86_64/24.08 \ + org.freedesktop.Sdk.Extension.llvm18/x86_64/24.08 # --------------------------------------------------------------------- # Stage C is the single biggest unknown for any Electron app on Flathub: @@ -126,51 +141,95 @@ jobs: echo "generated $COUNT sources, $BYTES bytes" # --------------------------------------------------------------------- - # Stage D: the same problem for Rust. Two lockfiles, and the compositor tree - # pulls bindgen, which wants libclang at build time as well as crates. - - name: "Stage D — vendor both Cargo lockfiles" + # Stage D: the same problem for Rust, for both lockfiles. `cargo vendor` was + # enough to answer "do these resolve"; the manifest needs the generator's + # output, which is a source list flatpak-builder can fetch offline. + - name: "Stage D — cargo sources for both lockfiles" id: vendor continue-on-error: true run: | set -euo pipefail - for dir in crates electron/native/pipewire-capture; do - echo "--- $dir ---" - ( cd "$dir" && cargo vendor --versioned-dirs /tmp/vendor-$(basename "$dir") \ - > /tmp/vendor-$(basename "$dir").toml ) - echo "$dir: $(find /tmp/vendor-$(basename "$dir") -maxdepth 1 -type d | wc -l) crates" - done + /tmp/fbt-venv/bin/pip install --quiet tomlkit aiohttp + gen() { + /tmp/fbt-venv/bin/python /tmp/fbt/cargo/flatpak-cargo-generator.py "$1" -o "$2" + echo "$2: $(python3 -c "import json,sys;print(len(json.load(open('$2'))))") sources" + } + gen crates/Cargo.lock build/flatpak/cargo-sources-crates.json + gen electron/native/pipewire-capture/Cargo.lock build/flatpak/cargo-sources-pipewire.json + # The generator hardcodes $CARGO_HOME/vendor, so these two lists overlap on + # every crate both trees share. Report the collision count rather than + # discovering it inside flatpak-builder. + python3 - <<'PY' + import json + a = {s.get('dest') for s in json.load(open('build/flatpak/cargo-sources-crates.json'))} + b = {s.get('dest') for s in json.load(open('build/flatpak/cargo-sources-pipewire.json'))} + shared = {d for d in a & b if d} + print(f"shared destinations: {len(shared)}") + for d in sorted(shared)[:10]: + print(f" {d}") + PY - # --------------------------------------------------------------------- - # Stage E: the manifest ships a deliberately wrong ffmpeg sha256 so that a - # guessed digest can never reach a submission. Resolve the real one here. - - name: "Stage E — resolve the ffmpeg source digest" - id: ffmpeg - continue-on-error: true - run: | - set -euo pipefail - URL=https://ffmpeg.org/releases/ffmpeg-8.1.2.tar.xz - if curl -fsSL --retry 3 -o /tmp/ffmpeg.tar.xz "$URL"; then - SHA=$(sha256sum /tmp/ffmpeg.tar.xz | awk '{print $1}') - echo "sha=$SHA" >> "$GITHUB_OUTPUT" - echo "$URL → $SHA" - else - echo "::warning::$URL does not exist — the pinned build is BtbN's n8.1.2-34-g9b6c8969e0, which is a *snapshot*, not an upstream release. The manifest needs a real upstream version, and whichever one is chosen is not the tree the addon was tested against." - exit 1 - fi + # Stage E is gone. It resolved the ffmpeg digest, which now lives pinned in the + # manifest, so all it could do was re-download 8.1.2 to confirm a constant. + # It also demonstrated the failure mode this workflow was already corrected + # for once: it reported "does not exist" for `curl: (35) Connection reset by + # peer`, turning a network flake into a false claim about upstream. A stage + # that can only be redundant or wrong is worth deleting rather than fixing. # --------------------------------------------------------------------- - # Stage F: the actual build. Expected to fail — the interesting output is - # WHERE. Run it even when earlier stages failed, so the log exists. + # Stage F: the actual build. Run it even when earlier stages failed, so the + # log exists. - name: "Stage F — flatpak-builder" id: build continue-on-error: true run: | set -euo pipefail + # --repo exports an OSTree repo alongside the build tree, which is what + # stage G needs: the repo lint catches things the manifest lint cannot see, + # like an icon or metainfo that did not actually get exported. + # + # In the workspace and NOT in /tmp. A flatpak always gets a private /tmp, + # whatever filesystem permissions it holds, so the sandboxed linter in stage G + # cannot see a host /tmp path — it read the manifest by a workspace-relative + # path perfectly well, which is what makes this the fix. flatpak-builder --user --install-deps-from=flathub --force-clean \ --disable-rofiles-fuse \ + --repo="$GITHUB_WORKSPACE/flatpak-repo" \ /tmp/flatpak-build build/flatpak/com.getopenscreen.OpenScreen.yml \ 2>&1 | tee /tmp/flatpak-build.log + # --------------------------------------------------------------------- + # Stage G: the gate Flathub actually applies at submission time. Running it + # here is the difference between "it builds" and "they would accept it", and + # it costs one flatpak install rather than a review round-trip. + # + # org.flatpak.Builder only for the lint — the build above keeps apt's + # flatpak-builder, which is the one proven to work, so this stays additive. + - name: "Stage G — flatpak-builder-lint" + id: lint + continue-on-error: true + run: | + set -euo pipefail + flatpak install -y --user --noninteractive flathub org.flatpak.Builder + lint() { + local mode="$1" target="$2" + echo "### flatpak-builder-lint $mode" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + if flatpak run --command=flatpak-builder-lint org.flatpak.Builder \ + "$mode" "$target" 2>&1 | tee "/tmp/lint-$mode.json"; then + echo "clean" >> "$GITHUB_STEP_SUMMARY" + else + cat "/tmp/lint-$mode.json" >> "$GITHUB_STEP_SUMMARY" + fi + echo '```' >> "$GITHUB_STEP_SUMMARY" + } + # Both, and neither is allowed to abort the other: they report different + # classes of problem and a submission has to satisfy the two. + rc=0 + lint manifest build/flatpak/com.getopenscreen.OpenScreen.yml || rc=1 + lint repo "$GITHUB_WORKSPACE/flatpak-repo" || rc=1 + exit $rc + - name: Collect artifacts if: always() uses: actions/upload-artifact@v7 @@ -179,6 +238,8 @@ jobs: path: | /tmp/flatpak-build.log build/flatpak/generated-sources.json + build/flatpak/cargo-sources-crates.json + build/flatpak/cargo-sources-pipewire.json if-no-files-found: warn retention-days: 7 @@ -189,10 +250,9 @@ jobs: B: ${{ steps.install.outcome }} C: ${{ steps.nodegen.outcome }} D: ${{ steps.vendor.outcome }} - E: ${{ steps.ffmpeg.outcome }} F: ${{ steps.build.outcome }} + G: ${{ steps.lint.outcome }} NPM_COUNT: ${{ steps.nodegen.outputs.count }} - FFMPEG_SHA: ${{ steps.ffmpeg.outputs.sha }} run: | { echo "## Verdict" @@ -202,12 +262,12 @@ jobs: echo "| A — versions exist | $A |" echo "| B — runtime/SDK/base install | $B |" echo "| C — offline npm sources (${NPM_COUNT:-n/a} entries) | $C |" - echo "| D — cargo vendor, both lockfiles | $D |" - echo "| E — ffmpeg source digest (${FFMPEG_SHA:-unresolved}) | $E |" + echo "| D — cargo sources, both lockfiles | $D |" echo "| F — flatpak-builder | $F |" + echo "| G — flatpak-builder-lint | $G |" echo - echo "Stage F failing is the expected result, not the finding. The finding is" - echo "which of A–E failed, because those are the ones that decide whether this" - echo "port is a week or a quarter. Read the tail of the build log in the" - echo "\`flatpak-spike\` artifact before drawing any conclusion from F." + echo "F green means it builds offline from source. G green means Flathub would" + echo "not reject it on sight. **Neither means the app runs** — nothing here has" + echo "launched it, and a CI runner has no desktop session or ScreenCast portal to" + echo "launch it into. That verification needs a Linux desktop and a human." } >> "$GITHUB_STEP_SUMMARY" diff --git a/build/flatpak/com.getopenscreen.OpenScreen.desktop b/build/flatpak/com.getopenscreen.OpenScreen.desktop new file mode 100644 index 00000000..ab5ad4c6 --- /dev/null +++ b/build/flatpak/com.getopenscreen.OpenScreen.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Type=Application +Name=OpenScreen +GenericName=Screen Recorder +Comment=Screen recorder and video editor +Exec=openscreen %U +Icon=com.getopenscreen.OpenScreen +Terminal=false +Categories=AudioVideo;AudioVideoEditing;Recorder; +Keywords=screen;record;recorder;capture;screencast;video;editor; +# Electron derives the WM class from productName, which is "Openscreen" in +# electron-builder.json5 — not the app id. Without this the window does not +# associate with the launcher icon under Wayland or GNOME. +StartupWMClass=Openscreen +StartupNotify=true diff --git a/build/flatpak/com.getopenscreen.OpenScreen.yml b/build/flatpak/com.getopenscreen.OpenScreen.yml index e4d04af3..cef027cd 100644 --- a/build/flatpak/com.getopenscreen.OpenScreen.yml +++ b/build/flatpak/com.getopenscreen.OpenScreen.yml @@ -1,30 +1,35 @@ -# Flatpak manifest — SPIKE, not a submission. +# Flatpak manifest for Flathub. # -# The point of this file is to be built by .github/workflows/flatpak-spike.yml and -# FAIL INFORMATIVELY. Flathub forbids the shortcut every other channel in this repo -# takes: "All source available submissions must be built entirely from source code. -# This requirement applies to the main application component defined in the manifest, -# as well as any runtime dependencies included in the manifest." OpenScreen is MIT, -# so it is source-available, so no repackaged .deb and no extra-data escape hatch. +# Still driven by .github/workflows/flatpak-spike.yml rather than submitted: the +# offline npm cache, the vendored crates and ffmpeg-from-source are all wired now, +# but nobody has launched the resulting app. Do not open a flathub/flathub PR on +# this until someone has. # -# Every value marked SPIKE below is a guess the CI run is supposed to confirm or -# refute. Do not treat this as a reviewed manifest. +# Why it is built from source at all, rather than repackaging the .deb like every +# other channel in this repo: "All source available submissions must be built +# entirely from source code. This requirement applies to the main application +# component defined in the manifest, as well as any runtime dependencies included +# in the manifest." OpenScreen is MIT, so that rule applies, and extra-data is not +# an escape hatch — it exists for non-redistributable sources. app-id: com.getopenscreen.OpenScreen runtime: org.freedesktop.Platform -# SPIKE: 24.08 is the runtime the Electron2 BaseApp is known to publish against. -# If the spike reports the base is missing for this version, bump both together — -# they must match or flatpak-builder refuses the base outright. runtime-version: '24.08' sdk: org.freedesktop.Sdk base: org.electronjs.Electron2.BaseApp base-version: '24.08' +# Same as the upstream Electron quick-start manifest: the locale splitting confuses +# an Electron tree that ships its own locales under resources/. +separate-locales: false sdk-extensions: - # SPIKE: package.json pins node 22.22.1. The freedesktop SDK ships node as a - # versioned extension and node22 may simply not exist for 24.08 — the spike - # enumerates what is actually available before this build is attempted. - org.freedesktop.Sdk.Extension.node22 - # The two Rust crates (crates/, electron/native/pipewire-capture/) need cargo. - org.freedesktop.Sdk.Extension.rust-stable + # bindgen, reached through crates/compositor-view-napi and the pipewire helper, + # loads libclang at build time and the SDK does not ship it — run 31602275749 died + # exactly there. LLVM 18 is the freedesktop 24.08 base toolchain, so llvm18 is the + # extension that should match; stage A of the same workflow now enumerates every + # published Sdk.Extension for this runtime, so if this name is wrong the run says + # which one to use instead of costing another guess. + - org.freedesktop.Sdk.Extension.llvm18 command: openscreen finish-args: @@ -32,47 +37,76 @@ finish-args: - --socket=wayland - --socket=fallback-x11 - --socket=pulseaudio - # Screen capture goes through the portal, not through a raw socket. The helper in - # electron/native/pipewire-capture already speaks org.freedesktop.portal.ScreenCast, - # which is the sandboxed path, so this part of the port is unusually well placed. - - --talk-name=org.freedesktop.portal.ScreenCast - - --talk-name=org.freedesktop.portal.Desktop + # NO --talk-name for the portals, deliberately. Screen capture goes through + # org.freedesktop.portal.ScreenCast — the helper in + # electron/native/pipewire-capture speaks it, which is the one part of this port + # that was already sandbox-shaped — but portal bus names are reachable from every + # sandbox without being requested, so asking is both redundant and rejected: + # flatpak-builder-lint raises finish-args-portal-talk-name for anything starting + # with `org.freedesktop.portal.`, and it is an error, not a warning. # Webcam. - --device=all - # Vulkan: the compositor addon is a Vulkan renderer. + # The compositor addon is a Vulkan renderer. - --device=dri - --share=network - --filesystem=xdg-videos - --filesystem=xdg-documents -build-options: - append-path: /usr/lib/sdk/node22/bin:/usr/lib/sdk/rust-stable/bin - env: - # Keep npm and cargo inside the build dir; the sandbox has no HOME to speak of. - npm_config_cache: /run/build/openscreen/npm-cache - CARGO_HOME: /run/build/openscreen/cargo - modules: # --------------------------------------------------------------------------- - # 1. ffmpeg FROM SOURCE. + # 0. patchelf — a build tool, not a shipped dependency. + # + # build-linux-compositor-addon.mjs refuses to run without it, and for a reason + # central to this whole port: it is what rewrites the ffmpeg symbol names so the + # addon cannot bind to Chromium's bundled libffmpeg.so. The SDK does not carry it. + # + # `cleanup: ['*']` because later modules need it during the build and the finished + # app must not ship a copy of a build tool. + - name: patchelf + cleanup: + - '*' + sources: + - type: archive + url: https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0.tar.gz + sha256: "64de10e4c6b8b8379db7e87f58030f336ea747c0515f381132e810dbf84a86e7" + + # --------------------------------------------------------------------------- + # 0b. SPIRV-Headers — also build-only. + # + # ggml's Vulkan backend wants exactly two things (ggml/src/ggml-vulkan/ + # CMakeLists.txt): `find_package(Vulkan COMPONENTS glslc REQUIRED)`, which the + # runtime satisfies at 1.3.290 with glslc present, and + # `find_package(SPIRV-Headers CONFIG REQUIRED)`, which nothing provides. Only the + # second was missing in run 31634845684. + # + # Version matched to the runtime's Vulkan rather than "latest": the whole point of + # this package is agreeing with the loader and glslc that are already there. # - # This is the module that decides whether the port is a week or a quarter, and it - # is the reason the spike exists. scripts/fetch-ffmpeg.mjs pins a BtbN *prebuilt* - # tree (ffmpeg-n8.1.2-…-linux64-lgpl-shared-8.1.tar.xz, sha256 c882a80f…) and - # Flathub will reject it: prebuilt is prebuilt whether or not it is sha-pinned. + # The alternative was building STT CPU-only by forcing OSC_ENABLE_VULKAN=OFF, which + # would have made Flatpak transcription slower than the deb's for no reason beyond + # one missing headers package. + - name: spirv-headers + buildsystem: cmake-ninja + cleanup: + - '*' + sources: + - type: git + url: https://github.com/KhronosGroup/SPIRV-Headers.git + tag: vulkan-sdk-1.3.290.0 + commit: 2acb319af38d43be3ea76bfabf3998e5281d8d12 + + # --------------------------------------------------------------------------- + # 1. ffmpeg, from source. # - # Worse, the prebuilt tree is not merely convenient. crates/compositor-view-napi - # RENAMES every ffmpeg dynamic symbol in the libraries it ships, because Electron - # links Chromium's own stripped libffmpeg.so into the same address space and the - # addon would otherwise bind to that. See scripts/build-linux-compositor-addon.mjs - # — it runs `nm -D --defined-only` over the vendored .so files and stages renamed - # copies. That mechanism needs headers AND libraries it owns, which rules out the - # org.freedesktop.Platform.ffmpeg-full extension (libraries, no dev headers). + # Proven to build in run 31575509206 (~7 minutes). It cannot be the BtbN prebuilt + # tree that scripts/fetch-ffmpeg.mjs pins — Flathub rejects prebuilt dependencies + # — and it cannot be org.freedesktop.Platform.ffmpeg-full either: that extension + # ships libraries without dev headers, and crates/compositor-view-napi needs + # headers AND libraries it owns, because it renames every ffmpeg dynamic symbol so + # the addon cannot bind to Chromium's own stripped libffmpeg.so. # - # So: build ffmpeg from the upstream release tarball into /app, with the same LGPL - # shape as the pinned build, and let the existing script rename copies of it. - # SPIKE: the configure flags below are a first cut at matching what the addon and - # the pipewire helper actually link against. Expect this list to be wrong. + # KNOWN GAP: 8.1.2 upstream is the BtbN pin minus 34 commits. It compiles; whether + # those commits matter to the renaming has not been exercised by running the app. - name: ffmpeg config-opts: - --disable-static @@ -86,40 +120,180 @@ modules: sources: - type: archive url: https://ffmpeg.org/releases/ffmpeg-8.1.2.tar.xz - # SPIKE: placeholder. The workflow resolves and prints the real digest; do - # not commit a guessed sha256, the build must fail loudly instead. - sha256: 0000000000000000000000000000000000000000000000000000000000000000 + # Resolved by a spike run, not guessed. QUOTED: unquoted, a 64-digit value + # is a YAML integer, and flatpak-builder then reports "No checksum + # specified" and never downloads the tarball at all. + sha256: "464beb5e7bf0c311e68b45ae2f04e9cc2af88851abb4082231742a74d97b524c" # --------------------------------------------------------------------------- # 2. The application. - # - # generated-sources.json is NOT committed: flatpak-node-generator produces it from - # package-lock.json and it is large and entirely derived. The spike workflow writes - # it next to this file before building. If it is absent, that is the first thing to - # check — not a manifest bug. - name: openscreen buildsystem: simple build-options: + append-path: /usr/lib/sdk/node22/bin:/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm18/bin env: - # Point the native build at the ffmpeg this manifest just built rather than - # at crates/thirdparty/, which nothing provisions inside the sandbox. + # bindgen resolves libclang through this, and build-linux-compositor-addon.mjs + # reads it too rather than searching. Pointing at the extension's lib dir is + # what makes the ffmpeg headers parseable at all. + LIBCLANG_PATH: /usr/lib/sdk/llvm18/lib + # Named rather than left to PATH: the script checks this variable first and + # module 0 installs to a known location, so there is nothing to discover. + PATCHELF: /app/bin/patchelf + # electron/native/whisper-stt/CMakeLists.txt pulls three repositories with + # FetchContent, which wants git and a network. They are pinned as sources + # below and CMake is redirected at the checkouts. The names are FetchContent's + # own, uppercased: `whisper`, `httplib`, `json`. + WHISPER_EXTRA_CMAKE_FLAGS: >- + -DFETCHCONTENT_SOURCE_DIR_WHISPER=/run/build/openscreen/thirdparty/whisper.cpp + -DFETCHCONTENT_SOURCE_DIR_HTTPLIB=/run/build/openscreen/thirdparty/cpp-httplib + -DFETCHCONTENT_SOURCE_DIR_JSON=/run/build/openscreen/thirdparty/json + # THE PATHS MATTER, and getting them wrong is why the first two spike runs + # died in `npm ci`. flatpak-node-generator lays its output out relative to + # the module build dir as `flatpak-node/`, so both of these are + # /run/build//flatpak-node/… — the module is named openscreen. + # + # XDG_CACHE_HOME is not optional despite looking like a nicety: it is where + # the Electron binary download is cached, so electron's postinstall goes to + # the network without it and the sandbox has none. + XDG_CACHE_HOME: /run/build/openscreen/flatpak-node/cache + npm_config_cache: /run/build/openscreen/flatpak-node/npm-cache + # Same shape for cargo: flatpak-cargo-generator hardcodes its vendor + # directory as $CARGO_HOME/vendor and writes the replace-with config into + # $CARGO_HOME, so CARGO_HOME has to be this exact path. + CARGO_HOME: /run/build/openscreen/cargo + CARGO_NET_OFFLINE: 'true' + # scripts/before-pack.cjs refuses payloads needing a newer glibc than Ubuntu + # 22.04 provides, which is right for the deb/rpm/pacman/AppImage and a category + # error here: a Flatpak resolves against org.freedesktop.Platform's glibc, not + # the host's. Raising MAX_SYMBOL_VERSION instead would drop a distro from the + # packages that genuinely need the floor. Waives the ceiling comparison only. + OPENSCREEN_SYMBOL_FLOOR: runtime-provided + # crates/thirdparty/ffmpeg-linux64-lgpl-shared is what the native build + # scripts look for and nothing provisions it here. Module 1 installed its + # headers and libraries into /app, which is the shape they expect. FFMPEG_DIR: /app build-commands: - # SPIKE: build:linux is `fetch:ffmpeg:sdk && build:native:linux && - # build:native:compositor:linux && tsc && vite build && electron-builder …`. - # The fetch step is a network download and cannot run here, which is why the - # steps are spelled out instead of calling the npm script. + # `npm ci --offline`, not `npm run build:linux`: that script starts with + # `fetch:ffmpeg:sdk`, which is a network download, and module 1 already + # provides what it would have fetched. The remaining steps are spelled out. - npm ci --offline + # Present module 1's ffmpeg at the path the native scripts already look for. + # + # FFMPEG_DIR covers the compositor addon, but build-linux-pipewire-helper.mjs + # reads `crates/thirdparty/ffmpeg-linux64-lgpl-shared/lib` directly to populate + # the helper's own `helper-ffmpeg/` directory — and when that path is absent it + # WARNS rather than fails, so run 31636202957 sailed past it and was caught much + # later by the before-pack guard instead. The two copies are deliberately + # different: the helper needs the original sonames, while the addon gets every + # symbol renamed to `osff_*` so it cannot bind to Chromium's libffmpeg.so. + # + # Symlinks rather than a copy, so there is exactly one ffmpeg in the build. + - | + vendored=crates/thirdparty/ffmpeg-linux64-lgpl-shared + mkdir -p "$vendored" + ln -sfn /app/include "$vendored/include" + ln -sfn /app/lib "$vendored/lib" + ls "$vendored/lib"/libav*.so* >/dev/null - npm run build:native:linux - npm run build:native:compositor:linux + # Not optional, and the before-pack guard is right to refuse without it: run + # 31608092415 stopped here because a payload missing the STT helper ships an app + # where transcription fails in front of the user. + - npm run build:whisper-binaries - npm run build-vite - # SPIKE: electron-builder's own linux targets are wrong here — Flathub packages - # the app tree directly. `--dir` is the closest thing; whether it cooperates - # with the BaseApp's electron is unknown and is a question for the run. - - npx electron-builder --linux dir --config.npmRebuild=false - - cp -r dist/linux-unpacked /app/openscreen + # `dir`, because Flathub packages the app tree and has no use for a .deb or an + # AppImage. executableName is pinned so the wrapper below can name the binary + # instead of discovering it. + # electronDist rather than letting electron-builder resolve the runtime: run + # 31640186488 reached `packaging platform=linux arch=x64 electron=41.2.1` and then + # died on `getaddrinfo EAI_AGAIN github.com`. The generated sources DO cache + # electron-v41.2.1-linux-x64.zip under flatpak-node/cache/electron, but matching + # whatever cache layout this electron-builder expects is a guess, and npm ci has + # already extracted the runtime into node_modules. Point at that and no download + # can be attempted at all. + - | + test -d node_modules/electron/dist || { + echo "node_modules/electron/dist is missing — electron's postinstall did not extract the offline zip" >&2 + exit 1 + } + npx electron-builder --linux dir \ + --config.npmRebuild=false \ + --config.executableName=openscreen \ + --config.electronDist=node_modules/electron/dist + # NOT dist/: electron-builder.json5 sets `directories.output` to + # `release/${version}`, so the tree is release/1.9.2/linux-unpacked. Globbed + # rather than hardcoded so a version bump does not break this, and asserted so a + # miss fails here instead of installing an empty /app/openscreen. + - | + unpacked=$(echo release/*/linux-unpacked) + test -d "$unpacked" || { + echo "no linux-unpacked under release/ — looked for release/*/linux-unpacked" >&2 + exit 1 + } + cp -r "$unpacked" /app/openscreen + - install -Dm755 openscreen.sh /app/bin/openscreen + - install -Dm644 build/flatpak/com.getopenscreen.OpenScreen.desktop /app/share/applications/${FLATPAK_ID}.desktop + # The metainfo's launchable is `openscreen.desktop`, which is what the deb + # installs and what that file's own comment says a Flatpak build must rewrite. + # Rewriting it here rather than editing the source keeps the file honest for + # the deb, and Flatpak only exports desktop files named after the app id, so + # an unrewritten launchable makes the app unlaunchable from a software centre. + - | + sed "s|>openscreen\.desktop<|>${FLATPAK_ID}.desktop<|" \ + build/com.getopenscreen.OpenScreen.metainfo.xml > metainfo.rewritten.xml + grep -q ">${FLATPAK_ID}.desktop<" metainfo.rewritten.xml \ + || { echo "launchable rewrite matched nothing — did the metainfo change?" >&2; exit 1; } + install -Dm644 metainfo.rewritten.xml /app/share/metainfo/${FLATPAK_ID}.metainfo.xml + # Flatpak only exports icons named after the app ID, so the repo's bare + # `512x512.png` names have to be rewritten on the way in. + - | + for size in 16 24 32 48 64 128 256 512; do + install -Dm644 "icons/icons/png/${size}x${size}.png" \ + "/app/share/icons/hicolor/${size}x${size}/apps/${FLATPAK_ID}.png" + done sources: - - type: git - url: https://github.com/getopenscreen/openscreen.git - tag: v1.9.2 + # SPIKE ONLY, and it must change before any submission: Flathub wants a + # `type: git` pinned to a tag AND its commit sha. It cannot be that yet — + # v1.9.2 predates both build/com.getopenscreen.OpenScreen.metainfo.xml and the + # desktop file next to this manifest, so a tagged build would fail on files + # that do not exist in it. Building the working tree also means the spike tests + # the branch under review rather than a release from before the work started. + # Switch to tag + commit once a release ships containing both files. + - type: dir + path: ../.. + # Generated by the spike workflow, not committed: both are derived from + # lockfiles and large. generated-sources.json alone is ~900 KB. - generated-sources.json + # TWO cargo lockfiles, and the generator has no option to separate their + # vendor directories — both land in $CARGO_HOME/vendor and both write the same + # replace-with config. Crates common to the two (anyhow, bindgen…) therefore + # appear twice at the same destination. If flatpak-builder refuses that, the + # fix is to build each crate tree under its own CARGO_HOME rather than to + # merge the lockfiles. + - cargo-sources-crates.json + - cargo-sources-pipewire.json + # The three FetchContent dependencies of the STT helper, pinned to tag AND + # commit because a tag alone is not reproducible. whisper.cpp v1.9.1 carries + # ggml in-tree, so there is no fourth pin to keep in step. + - type: git + url: https://github.com/ggml-org/whisper.cpp.git + tag: v1.9.1 + commit: f049fff95a089aa9969deb009cdd4892b3e74916 + dest: thirdparty/whisper.cpp + - type: git + url: https://github.com/yhirose/cpp-httplib.git + tag: v0.18.1 + commit: 5c1a34e766817cf0865ca8bc5af5edeb0f9b2df4 + dest: thirdparty/cpp-httplib + - type: git + url: https://github.com/nlohmann/json.git + tag: v3.11.3 + commit: 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 + dest: thirdparty/json + # Zypak is mandatory, not a nicety: without it Electron's sandbox fails with + # "SUID sandbox helper binary" and the app does not start. + - type: script + dest-filename: openscreen.sh + commands: + - export TMPDIR="$XDG_RUNTIME_DIR/app/$FLATPAK_ID" + - exec zypak-wrapper /app/openscreen/openscreen "$@" diff --git a/scripts/before-pack.cjs b/scripts/before-pack.cjs index 227d2d48..807f838e 100644 --- a/scripts/before-pack.cjs +++ b/scripts/before-pack.cjs @@ -504,6 +504,23 @@ function checkLinuxNativePayload(context) { */ const MAX_SYMBOL_VERSION = { GLIBC: "2.35", GLIBCXX: "3.4.30", CXXABI: "1.3.13" }; +/** + * The one build where the ceiling above is a category error rather than a floor. + * + * A Flatpak does not link against the host's glibc: it runs inside its runtime, so + * `org.freedesktop.Platform` decides what the binaries resolve against, and that is + * newer than every distro the ceiling protects. Checking a Flatpak payload against + * Ubuntu 22.04's glibc refuses a build that cannot have the problem — and the only + * other way past it, raising MAX_SYMBOL_VERSION, would silently drop a distro from + * the deb/rpm/pacman/AppImage packages that DO need the floor. + * + * Set by build/flatpak/com.getopenscreen.OpenScreen.yml and nothing else. It waives + * ONLY the ceiling comparison: the parser assertion still runs, because "does the + * floor apply here" and "did the scan work at all" are unrelated questions, and the + * second is how this guard stays honest. + */ +const SYMBOL_FLOOR_WAIVED = process.env.OPENSCREEN_SYMBOL_FLOOR === "runtime-provided"; + /** Dotted numeric compare, so 3.4.9 < 3.4.30 and 2.4 < 2.38 rather than by string. */ function compareVersions(a, b) { const left = a.split(".").map(Number); @@ -618,6 +635,18 @@ function checkLinuxSymbolVersionFloor(dir) { ); } + // After the parser assertion on purpose — see SYMBOL_FLOOR_WAIVED. Loud, because a + // waived guard that says nothing is indistinguishable from a guard that passed. + if (SYMBOL_FLOOR_WAIVED) { + console.log( + `[before-pack] symbol-version floor WAIVED for ${scanned.length} ELF files in ` + + `${path.relative(ROOT, dir)}: OPENSCREEN_SYMBOL_FLOOR=runtime-provided.\n` + + " Valid only when the runtime ships its own glibc, which means the Flatpak build.\n" + + " Distro packages (deb/rpm/pacman/AppImage) must never set it.", + ); + return; + } + const offenders = scanned .map((entry) => ({ name: entry.name, diff --git a/scripts/build-whisper-stt.sh b/scripts/build-whisper-stt.sh index 3dd1f304..e8894e96 100644 --- a/scripts/build-whisper-stt.sh +++ b/scripts/build-whisper-stt.sh @@ -314,6 +314,23 @@ BUILD_FLAGS=() if [[ -n "${DEFAULT_FLAG}" ]]; then BUILD_FLAGS+=("${DEFAULT_FLAG}") fi +# An escape hatch for builds that cannot reach the network. The CMakeLists pulls +# whisper.cpp, cpp-httplib and nlohmann/json with FetchContent, which needs git +# and a network; a Flatpak sandbox has neither, so the Flathub manifest pre-fetches +# all three as pinned sources and redirects CMake at them with +# -DFETCHCONTENT_SOURCE_DIR_. Those flags cannot arrive as CLI arguments — +# the parser above rejects anything it does not recognise, deliberately — and CMake +# reads cache variables from the command line rather than the environment, so this +# is the seam. +# +# Word-split on purpose: the value is a list of cmake flags, not one argument. +if [[ -n "${WHISPER_EXTRA_CMAKE_FLAGS:-}" ]]; then + # `read -a` rather than bare expansion so `set -u` and shellcheck both stay + # happy, and bash 3.2 on macOS handles it identically. + read -r -a EXTRA_CMAKE_FLAGS <<< "${WHISPER_EXTRA_CMAKE_FLAGS}" + BUILD_FLAGS+=("${EXTRA_CMAKE_FLAGS[@]}") + echo "[whisper-stt] extra cmake flags from WHISPER_EXTRA_CMAKE_FLAGS: ${WHISPER_EXTRA_CMAKE_FLAGS}" +fi # See the comment in build_variant() re: bash 3.2 + `set -u` + empty arrays # (macOS x64/CPU has no DEFAULT_FLAG, so BUILD_FLAGS is genuinely empty here). build_variant "default" ${BUILD_FLAGS[@]+"${BUILD_FLAGS[@]}"}