ci: the Flatpak workflow, local deps for the local build, the lens tools
- .forgejo/workflows/ci.yaml is Crafter.Build's workflow for this app: the base installed from the Catcrafts repo, the version stamped into the crafter-review module, the suite inside the build, then a smoke test of the app as it ships - the version and home, every library resolved and no RUNPATH outside /app, the bundled tools, a freeze and a dry run on a scratch repository. Publishing holds /srv/flatpak/.publish.lock and writes only this app's flatpakref. Unverified until a crafter-flatpak runner serves this repository. - `run flatpak --deps=local` builds against the Crafter.Network and Crafter.Thread checkouts next to this one, as a native --local build does, while their commits are not on forgejo: a gitignored manifest copy with three dir sources side by side and --local. - LensTools: every program the lens allowlist names, the shell and the MCP server's node and npx resolve on PATH; strict in the Flatpak build, where the suite runs in the sandbox that ships, a skip natively when this machine lacks one. Built on the stable base (Crafter.Build 3.0.0) with --deps=local: the suite passes inside flatpak-builder (25 passed, 2 skipped), and the workflow's smoke test passes against that build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7eccfd3b45
commit
5dab4b49d5
3 changed files with 373 additions and 17 deletions
274
.forgejo/workflows/ci.yaml
Normal file
274
.forgejo/workflows/ci.yaml
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
name: Flatpak
|
||||
|
||||
# Crafter.Build's workflow, for this app. One job, three outcomes:
|
||||
# pull request -> build the Flatpak, run the suite inside it, smoke-test it
|
||||
# push master -> also publish it as the `beta` branch of the Catcrafts
|
||||
# repo and refresh the rolling `latest` release
|
||||
# tag X.Y.Z -> publish it as `stable` and create release X.Y.Z
|
||||
#
|
||||
# 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]
|
||||
tags: ['[0-9]*.[0-9]*.[0-9]*']
|
||||
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
|
||||
if [ "${GITHUB_REF_TYPE}" = tag ]; then
|
||||
version="$GITHUB_REF_NAME"
|
||||
branch=stable
|
||||
# The metainfo's newest release is the release notes and must be
|
||||
# the version being tagged.
|
||||
top=$(grep -m1 -oP '<release version="\K[^"]+' "$METAINFO")
|
||||
if [ "$top" != "$version" ]; then
|
||||
echo "metainfo lists $top as the newest release, but the tag is $version" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Named after the release it follows, as a checkout describes
|
||||
# itself: 1.0.0-3-g21746b1 is three commits past 1.0.0.
|
||||
described=$(git describe --tags --always --exclude=latest)
|
||||
case "$described" in
|
||||
[0-9]*.[0-9]*.[0-9]*) version="$described" ;;
|
||||
*) version="0.0.0-dev+$described" ;;
|
||||
esac
|
||||
branch=beta
|
||||
fi
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "branch=$branch" >> "$GITHUB_OUTPUT"
|
||||
echo "version $version, flatpak branch $branch"
|
||||
|
||||
- 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 "{\"lenses\": {\"repo-functional\": {\"run\": false}}}\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 and on tags.
|
||||
- name: Bundle
|
||||
if: github.event_name != 'pull_request'
|
||||
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.event_name != 'pull_request'
|
||||
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.event_name != 'pull_request'
|
||||
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: Rolling 'latest' release
|
||||
if: github.event_name != 'pull_request' && github.ref_type != 'tag'
|
||||
run: |
|
||||
git config user.email "ci@catcrafts.net"
|
||||
git config user.name "Crafter Review CI"
|
||||
git tag -f latest
|
||||
git push origin latest --force
|
||||
- name: Publish rolling 'latest' release
|
||||
if: github.event_name != 'pull_request' && github.ref_type != 'tag'
|
||||
uses: https://code.forgejo.org/actions/forgejo-release@v2
|
||||
with:
|
||||
direction: upload
|
||||
url: ${{ github.server_url }}
|
||||
repo: ${{ github.repository }}
|
||||
tag: latest
|
||||
title: Latest master build
|
||||
prerelease: true
|
||||
override: true
|
||||
release-dir: dist
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Release notes
|
||||
if: github.ref_type == 'tag'
|
||||
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_type == 'tag'
|
||||
uses: https://code.forgejo.org/actions/forgejo-release@v2
|
||||
with:
|
||||
direction: upload
|
||||
url: ${{ github.server_url }}
|
||||
repo: ${{ github.repository }}
|
||||
tag: ${{ github.ref_name }}
|
||||
title: crafter-review ${{ github.ref_name }}
|
||||
release-notes-file: release-notes.md
|
||||
release-dir: dist
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
Loading…
Reference in a new issue