diff --git a/.github/workflows/flatpak-spike.yml b/.github/workflows/flatpak-spike.yml new file mode 100644 index 00000000..26a50976 --- /dev/null +++ b/.github/workflows/flatpak-spike.yml @@ -0,0 +1,213 @@ +name: Flatpak spike + +# THROWAWAY. This workflow exists to answer one question — how far from a Flathub +# submission is this repository — and should be deleted once it has answered it. +# It is `workflow_dispatch` only and runs on no event, so it costs nothing until +# someone asks. +# +# Every stage is `continue-on-error` on purpose. A spike that stops at the first +# failure tells you one thing; this one tells you all of them in a single run, and +# writes the verdict to the run summary. Read the summary, not the logs. + +on: + workflow_dispatch: + inputs: + ref: + description: "Ref to spike (defaults to the branch this is dispatched on)" + required: false + type: string + +permissions: + contents: read + +jobs: + spike: + name: How far is Flathub + runs-on: ubuntu-latest + # The full attempt downloads a runtime, an SDK, two SDK extensions, a base app, + # every npm tarball in the lockfile and every crate in two Cargo.locks. If it + # has not concluded in an hour it has concluded something anyway. + timeout-minutes: 60 + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.ref || github.ref }} + persist-credentials: false + + - name: Install flatpak tooling + run: | + set -euo pipefail + sudo apt-get update -qq + sudo apt-get install -y -qq flatpak flatpak-builder python3-venv + flatpak remote-add --if-not-exists --user \ + flathub https://dl.flathub.org/repo/flathub.flatpakrepo + flatpak --version + flatpak-builder --version + + # --------------------------------------------------------------------- + # Stage A is the cheapest and the most likely to invalidate the manifest: + # it asks Flathub what actually exists before anything tries to install it. + # The manifest guesses runtime 24.08, node22 and an Electron2 BaseApp at the + # same version. Any of the three can simply not be published. + - name: "Stage A — what versions actually exist" + id: versions + continue-on-error: true + run: | + set -euo pipefail + echo "## Stage A — availability" >> "$GITHUB_STEP_SUMMARY" + # Query ONCE and keep the exit status. Per-id queries behind `|| true` + # turn a broken remote into six "NOT PUBLISHED" lines, which is the one + # wrong answer this stage must never give: it would read as "Flathub does + # not ship the runtime" when it means "the question never got asked". + if ! CATALOG=$(flatpak remote-ls flathub --user --columns=application,branch 2>&1); then + { + echo + echo "**QUERY FAILED** — \`flatpak remote-ls\` errored, so nothing below is a" + echo "statement about what Flathub publishes. Stage A draws no conclusion." + echo + echo '```' + echo "$CATALOG" | tail -5 + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + for id in \ + org.freedesktop.Platform \ + org.freedesktop.Sdk \ + org.electronjs.Electron2.BaseApp \ + org.freedesktop.Sdk.Extension.node22 \ + org.freedesktop.Sdk.Extension.node20 \ + org.freedesktop.Sdk.Extension.rust-stable + do + FOUND=$(printf '%s\n' "$CATALOG" | awk -v id="$id" '$1 == id { printf "%s ", $2 }') + if [[ -n "$FOUND" ]]; then + echo "- \`$id\` → branches: $FOUND" >> "$GITHUB_STEP_SUMMARY" + else + echo "- \`$id\` → **NOT PUBLISHED**" >> "$GITHUB_STEP_SUMMARY" + fi + done + + - name: "Stage B — install runtime, SDK, base and extensions" + id: install + continue-on-error: true + run: | + set -euo pipefail + flatpak install -y --user --noninteractive flathub \ + org.freedesktop.Platform/x86_64/24.08 \ + 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 + + # --------------------------------------------------------------------- + # Stage C is the single biggest unknown for any Electron app on Flathub: + # the sandbox has no network, so every npm tarball must be declared as a + # pinned source. flatpak-node-generator derives that from package-lock.json. + # If it cannot handle this lockfile, nothing downstream matters. + - name: "Stage C — offline npm sources from the lockfile" + id: nodegen + continue-on-error: true + run: | + set -euo pipefail + # `node/` is a poetry project exposing the `flatpak-node-generator` console + # script — not a loose .py to invoke by path. Pinned: this tool decides + # whether the whole port is viable, so it should not change under us + # between two runs of the same spike. + git clone --filter=blob:none https://github.com/flatpak/flatpak-builder-tools.git /tmp/fbt + git -C /tmp/fbt checkout 737c0085912f9f7dabf9341d4608e2a77a51a73a + python3 -m venv /tmp/fbt-venv + /tmp/fbt-venv/bin/pip install --quiet /tmp/fbt/node + /tmp/fbt-venv/bin/flatpak-node-generator npm package-lock.json \ + -o build/flatpak/generated-sources.json + BYTES=$(stat -c%s build/flatpak/generated-sources.json) + COUNT=$(python3 -c "import json;print(len(json.load(open('build/flatpak/generated-sources.json'))))") + echo "bytes=$BYTES" >> "$GITHUB_OUTPUT" + echo "count=$COUNT" >> "$GITHUB_OUTPUT" + 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" + 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 + + # --------------------------------------------------------------------- + # 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 F: the actual build. Expected to fail — the interesting output is + # WHERE. 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 + flatpak-builder --user --install-deps-from=flathub --force-clean \ + --disable-rofiles-fuse \ + /tmp/flatpak-build build/flatpak/com.getopenscreen.OpenScreen.yml \ + 2>&1 | tee /tmp/flatpak-build.log + + - name: Collect artifacts + if: always() + uses: actions/upload-artifact@v7 + with: + name: flatpak-spike + path: | + /tmp/flatpak-build.log + build/flatpak/generated-sources.json + if-no-files-found: warn + retention-days: 7 + + - name: Verdict + if: always() + env: + A: ${{ steps.versions.outcome }} + B: ${{ steps.install.outcome }} + C: ${{ steps.nodegen.outcome }} + D: ${{ steps.vendor.outcome }} + E: ${{ steps.ffmpeg.outcome }} + F: ${{ steps.build.outcome }} + NPM_COUNT: ${{ steps.nodegen.outputs.count }} + FFMPEG_SHA: ${{ steps.ffmpeg.outputs.sha }} + run: | + { + echo "## Verdict" + echo + echo "| Stage | Outcome |" + echo "|---|---|" + 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 "| F — flatpak-builder | $F |" + 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." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/build/flatpak/com.getopenscreen.OpenScreen.yml b/build/flatpak/com.getopenscreen.OpenScreen.yml new file mode 100644 index 00000000..e4d04af3 --- /dev/null +++ b/build/flatpak/com.getopenscreen.OpenScreen.yml @@ -0,0 +1,125 @@ +# Flatpak manifest — SPIKE, not a submission. +# +# 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. +# +# 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. +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' +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 +command: openscreen + +finish-args: + - --share=ipc + - --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 + # Webcam. + - --device=all + # Vulkan: 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. + # + # 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. + # + # 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). + # + # 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. + - name: ffmpeg + config-opts: + - --disable-static + - --enable-shared + - --disable-programs + - --disable-doc + - --enable-gpl + - --enable-version3 + - --enable-libvpx + - --enable-libopus + 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 + + # --------------------------------------------------------------------------- + # 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: + env: + # Point the native build at the ffmpeg this manifest just built rather than + # at crates/thirdparty/, which nothing provisions inside the sandbox. + 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 + - npm run build:native:linux + - npm run build:native:compositor:linux + - 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 + sources: + - type: git + url: https://github.com/getopenscreen/openscreen.git + tag: v1.9.2 + - generated-sources.json