Some checks failed
Flatpak / flatpak (push) Has been cancelled
A push to master published the build as the rolling `beta` branch and refreshed a `latest` prerelease; a tag cut the stable release. Now every push to master is the release: CI takes the version from the metainfo's newest <release>, publishes `stable`, tags the pushed commit and creates the release with that entry's notes. A version that is released already fails the job before anything is built or published, and so does a lookup that cannot tell, so a push to master adds a new <release>. Publishing is master's alone: a manual run on another branch builds and tests and cuts nothing. Tags are no longer a trigger, and the rolling `beta` and `latest` steps are gone. The README and AGENTS say so, and the first release's entry, 1.0.0, takes the date of its push and names what it ships.
256 lines
12 KiB
YAML
256 lines
12 KiB
YAML
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
name: Flatpak
|
|
|
|
# Crafter.Build's workflow, for this app, with one change: every push to
|
|
# master is a release (2026-11-02: no rolling `beta` or `latest` builds).
|
|
# One job, two outcomes:
|
|
# pull request -> build the Flatpak, run the suite inside it, smoke-test it
|
|
# push master -> also publish it as `stable` and cut release X.Y.Z, the
|
|
# metainfo's newest <release>: its tag at the pushed commit
|
|
# and its notes. A version that is released already fails
|
|
# the job before anything is built or published.
|
|
#
|
|
# It builds on the Crafter.Build Flatpak (`base:` in the manifest), whose
|
|
# crafter-build and LLVM compile this project, so the runner installs that
|
|
# base from the same repo it publishes into.
|
|
#
|
|
# The `crafter-flatpak` label is the privileged runner on the Hetzner box
|
|
# (bubblewrap needs it) with the Sdk install, the flatpak-builder state, the
|
|
# repo's web root and the signing key mounted. It is registered to
|
|
# Crafter.Build alone today: until this repository has such a runner, this
|
|
# workflow does not run.
|
|
#
|
|
# Both apps publish into one repo: the publish step holds
|
|
# /srv/flatpak/.publish.lock, which protects anything once Crafter.Build's
|
|
# workflow takes it too. This one writes its own flatpakref and never the
|
|
# shared catcrafts.flatpakrepo, which Crafter.Build's workflow owns.
|
|
on:
|
|
pull_request:
|
|
branches: [master]
|
|
push:
|
|
branches: [master]
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
APP_ID: net.catcrafts.CrafterReview
|
|
MANIFEST: packaging/flatpak/net.catcrafts.CrafterReview.yaml
|
|
CI_MANIFEST: packaging/flatpak/ci-manifest.yaml
|
|
METAINFO: packaging/flatpak/net.catcrafts.CrafterReview.metainfo.xml
|
|
RUNTIME: org.freedesktop.Sdk//26.08
|
|
BASE: net.catcrafts.CrafterBuild//stable
|
|
REPO_DIR: /srv/flatpak/repo
|
|
WEB_DIR: /srv/flatpak
|
|
|
|
jobs:
|
|
flatpak:
|
|
runs-on: crafter-flatpak
|
|
container:
|
|
image: archlinux:latest
|
|
options: >-
|
|
-v crafter-flatpak-system:/var/lib/flatpak
|
|
-v crafter-flatpak-builder:/root/.cache/flatpak-builder
|
|
-v /var/www/flatpak:/srv/flatpak
|
|
-v /etc/crafter-flatpak:/run/crafter-flatpak:ro
|
|
steps:
|
|
- name: Install tools
|
|
run: |
|
|
pacman-key --init
|
|
pacman-key --populate archlinux
|
|
pacman -Sy --noconfirm --needed archlinux-keyring
|
|
pacman -Syu --noconfirm --needed flatpak flatpak-builder ccache git jq gnupg go-yq ostree python util-linux
|
|
git config --global --add safe.directory '*'
|
|
|
|
- name: Sdk and base app
|
|
# A no-op once the volume is warm; `update` follows point releases
|
|
# and a new stable base.
|
|
run: |
|
|
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
|
flatpak remote-add --if-not-exists catcrafts https://flatpak.catcrafts.net/catcrafts.flatpakrepo
|
|
flatpak install -y --noninteractive flathub "$RUNTIME"
|
|
flatpak install -y --noninteractive catcrafts "$BASE"
|
|
flatpak update -y --noninteractive
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: true
|
|
|
|
- name: Version and channel
|
|
id: meta
|
|
run: |
|
|
set -eu
|
|
# The metainfo's newest release is the version and its notes. On
|
|
# master it must be new: its tag must not exist yet, and a lookup
|
|
# that cannot tell stops the job as well.
|
|
version=$(grep -m1 -oP '<release version="\K[^"]+' "$METAINFO")
|
|
if [ "${GITHUB_REF}" = refs/heads/master ]; then
|
|
code=0
|
|
git ls-remote --exit-code --tags origin "refs/tags/$version" > /dev/null || code=$?
|
|
case "$code" in
|
|
0) echo "the metainfo's newest release, $version, is released already: a push to master names a new <release> in $METAINFO" >&2; exit 1 ;;
|
|
2) ;;
|
|
*) echo "git ls-remote exited $code: cannot tell whether $version is released" >&2; exit 1 ;;
|
|
esac
|
|
fi
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
echo "branch=stable" >> "$GITHUB_OUTPUT"
|
|
echo "version $version, flatpak branch stable"
|
|
|
|
- name: Build
|
|
# The manifest is rewritten beside the original so its relative
|
|
# source paths still resolve. The suite runs inside this build.
|
|
env:
|
|
VERSION: ${{ steps.meta.outputs.version }}
|
|
run: |
|
|
set -eu
|
|
yq '(.modules[] | select(.name == "crafter-review") | ."build-options".env.CRAFTER_REVIEW_VERSION) = strenv(VERSION)' "$MANIFEST" > "$CI_MANIFEST"
|
|
grep -n 'CRAFTER_REVIEW_VERSION' "$CI_MANIFEST"
|
|
flatpak-builder --force-clean --ccache --disable-rofiles-fuse \
|
|
--state-dir=/root/.cache/flatpak-builder \
|
|
--repo=repo --default-branch="${{ steps.meta.outputs.branch }}" \
|
|
build-dir "$CI_MANIFEST"
|
|
|
|
- name: Smoke test
|
|
# In the app as it ships: the version and the home, every library
|
|
# resolved and none through a RUNPATH outside /app, the bundled
|
|
# tools, and a freeze and a dry run on a scratch repository (a dry run
|
|
# joins no board and takes no lifetime lock, so it runs inside
|
|
# `flatpak build`; it exits 2 by design).
|
|
env:
|
|
VERSION: ${{ steps.meta.outputs.version }}
|
|
run: |
|
|
set -eu
|
|
shown=$(flatpak build build-dir crafter-review --version)
|
|
printf '%s\n' "$shown"
|
|
printf '%s\n' "$shown" | grep -qF "crafter-review $VERSION (prompt v"
|
|
flatpak build build-dir sh -euc '
|
|
for f in /app/bin/crafter-review /app/lib/node/bin/node; do
|
|
if ldd "$f" | grep "not found"; then echo "$f: a library is missing" >&2; exit 1; fi
|
|
bad=$(readelf -d "$f" | sed -n "s/.*(R[UN]*PATH).*\[\(.*\)\]/\1/p" | tr ":" "\n" | grep -v -e "^\$" -e "^/app" -e "^\\\$ORIGIN" || true)
|
|
if [ -n "$bad" ]; then echo "$f: a RUNPATH outside /app: $bad" >&2; exit 1; fi
|
|
done
|
|
rg --version | head -1
|
|
node --version
|
|
npx --version
|
|
git --version'
|
|
flatpak build build-dir sh -euc '
|
|
export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
|
|
export GIT_AUTHOR_NAME=CI GIT_AUTHOR_EMAIL=ci@catcrafts.net GIT_COMMITTER_NAME=CI GIT_COMMITTER_EMAIL=ci@catcrafts.net
|
|
cd "$(mktemp -d)"
|
|
git init -q -b main .
|
|
git config user.email ci@catcrafts.net
|
|
printf ".review-gates/push/\n.review-gates/trees/\n" > .gitignore
|
|
mkdir .review-gates
|
|
printf "{\"modes\": {\"commits\": {\"lenses\": [\"repo-better-solution\", \"repo-bug-prover\", \"repo-security\", \"repo-facts\"]}}}\n" > .review-gates/config.json
|
|
git add -A
|
|
git commit -q -m base
|
|
base=$(git rev-parse HEAD)
|
|
printf "def hello(name):\n return name\n" > hello.py
|
|
git add hello.py
|
|
git commit -q -m "add hello"
|
|
crafter-review freeze --slug ci --base "$base"
|
|
hash=$(jq -r .hash .review-gates/push/ci/freeze.json)
|
|
code=0
|
|
crafter-review run --artifact .review-gates/push/ci --hash "$hash" --base "$base" --trees . \
|
|
--dispositions .review-gates/push/ci/gate-state/dispositions.json --scratch "$PWD/scratch" --dry-run || code=$?
|
|
if [ "$code" != 2 ]; then echo "the dry run exited $code, not 2" >&2; exit 1; fi
|
|
ls scratch/*-dry/prompts | head -3'
|
|
|
|
# Everything below publishes and only runs on master.
|
|
- name: Bundle
|
|
if: github.ref == 'refs/heads/master'
|
|
run: |
|
|
mkdir -p dist
|
|
flatpak build-bundle repo "dist/crafter-review-${{ steps.meta.outputs.version }}.flatpak" "$APP_ID" "${{ steps.meta.outputs.branch }}"
|
|
cp packaging/flatpak/net.catcrafts.CrafterReview.flatpakref dist/
|
|
(cd dist && sha256sum * > SHA256SUMS)
|
|
ls -la dist
|
|
|
|
- name: Upload bundle
|
|
if: github.ref == 'refs/heads/master'
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: crafter-review-${{ steps.meta.outputs.version }}
|
|
path: dist/*.flatpak
|
|
if-no-files-found: error
|
|
|
|
- name: Publish to the Catcrafts repo
|
|
if: github.ref == 'refs/heads/master'
|
|
run: |
|
|
set -eu
|
|
if [ ! -s /run/crafter-flatpak/signing-key.asc ] || [ ! -s /run/crafter-flatpak/key-id ]; then
|
|
echo "no signing key under /etc/crafter-flatpak on the runner host (Crafter.Build's AGENTS.md, CI and publishing)" >&2
|
|
exit 1
|
|
fi
|
|
export GNUPGHOME=$(mktemp -d)
|
|
gpg --batch --quiet --import /run/crafter-flatpak/signing-key.asc
|
|
FLATPAK_GPG_KEY_ID=$(cat /run/crafter-flatpak/key-id)
|
|
# One publish at a time into the repo both apps share: a commit and
|
|
# the summary update of one workflow never interleave with the
|
|
# other's.
|
|
exec 9> "$WEB_DIR/.publish.lock"
|
|
flock 9
|
|
[ -f "$REPO_DIR/config" ] || ostree init --repo="$REPO_DIR" --mode=archive
|
|
flatpak build-commit-from --src-repo=repo \
|
|
--gpg-sign="$FLATPAK_GPG_KEY_ID" --gpg-homedir="$GNUPGHOME" \
|
|
"$REPO_DIR" "app/$APP_ID/x86_64/${{ steps.meta.outputs.branch }}"
|
|
flatpak build-update-repo --gpg-sign="$FLATPAK_GPG_KEY_ID" --gpg-homedir="$GNUPGHOME" \
|
|
--generate-static-deltas --prune "$REPO_DIR"
|
|
# This app's ref file only: catcrafts.flatpakrepo is Crafter.Build's.
|
|
key=$(gpg --export "$FLATPAK_GPG_KEY_ID" | base64 --wrap=0)
|
|
sed "s|GPGKey=.*|GPGKey=$key|" packaging/flatpak/net.catcrafts.CrafterReview.flatpakref > "$WEB_DIR/net.catcrafts.CrafterReview.flatpakref"
|
|
flock -u 9
|
|
|
|
- name: Tag the release
|
|
if: github.ref == 'refs/heads/master'
|
|
run: |
|
|
# The release's tag at the pushed commit. Never forced: a tag that
|
|
# appeared since the version check fails the push.
|
|
git tag "${{ steps.meta.outputs.version }}" "$GITHUB_SHA"
|
|
git push origin "refs/tags/${{ steps.meta.outputs.version }}"
|
|
|
|
- name: Release notes
|
|
if: github.ref == 'refs/heads/master'
|
|
run: |
|
|
# The newest <release> block's description as Markdown, the way
|
|
# Crafter.Build's workflow writes its notes.
|
|
python3 - > release-notes.md <<'EOF'
|
|
import xml.etree.ElementTree as ET
|
|
|
|
desc = ET.parse("packaging/flatpak/net.catcrafts.CrafterReview.metainfo.xml").getroot().find("releases/release/description")
|
|
|
|
def text(el):
|
|
parts = [el.text or ""]
|
|
for child in el:
|
|
inner = text(child)
|
|
parts.append({"em": f"*{inner}*", "code": f"`{inner}`"}.get(child.tag, inner))
|
|
parts.append(child.tail or "")
|
|
return " ".join("".join(parts).split())
|
|
|
|
blocks = list(desc)
|
|
out = []
|
|
for i, el in enumerate(blocks):
|
|
heads_list = i + 1 < len(blocks) and blocks[i + 1].tag in ("ul", "ol")
|
|
if el.tag == "p":
|
|
out.append(f"## {text(el)}" if heads_list else text(el))
|
|
elif el.tag == "ul":
|
|
out.append("\n".join(f"- {text(li)}" for li in el))
|
|
elif el.tag == "ol":
|
|
out.append("\n".join(f"{n}. {text(li)}" for n, li in enumerate(el, 1)))
|
|
print("\n\n".join(out))
|
|
print("\nInstall:\n\n flatpak remote-add --if-not-exists catcrafts https://flatpak.catcrafts.net/catcrafts.flatpakrepo\n flatpak install catcrafts net.catcrafts.CrafterReview\n")
|
|
EOF
|
|
- name: Publish release
|
|
if: github.ref == 'refs/heads/master'
|
|
uses: https://code.forgejo.org/actions/forgejo-release@v2
|
|
with:
|
|
direction: upload
|
|
url: ${{ github.server_url }}
|
|
repo: ${{ github.repository }}
|
|
tag: ${{ steps.meta.outputs.version }}
|
|
title: crafter-review ${{ steps.meta.outputs.version }}
|
|
release-notes-file: release-notes.md
|
|
release-dir: dist
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|