From a9939eb703aeae02fa887973b562fdc0d9a954db Mon Sep 17 00:00:00 2001 From: Scott Jones Date: Wed, 19 Aug 2026 17:45:34 -0400 Subject: [PATCH 01/11] Build aarch64 packages on native ARM64 runners The build system already supports aarch64 end to end -- bin/build, bin/sign, bin/update-repo and bin/sync-repo all take --arch, the Dockerfile bootstraps an Arch Linux ARM rootfs, and omarchy-keyring is published for aarch64 so that image can bootstrap -- but nothing runs it, so pkgs.omarchy.org serves no aarch64 tree. GitHub's ARM64 runners are free for public repositories, so the build needs no QEMU. The workflow is dispatch-only and takes an optional package list, and it stops at uploading artifacts: signing and syncing need credentials only a maintainer has. --- .github/workflows/build-aarch64.yml | 128 ++++++++++++++++++++++++++++ README.md | 3 + 2 files changed, 131 insertions(+) create mode 100644 .github/workflows/build-aarch64.yml diff --git a/.github/workflows/build-aarch64.yml b/.github/workflows/build-aarch64.yml new file mode 100644 index 000000000..d267d4838 --- /dev/null +++ b/.github/workflows/build-aarch64.yml @@ -0,0 +1,128 @@ +name: Build aarch64 Packages + +# Builds the aarch64 half of the repository on GitHub's native ARM64 runners, +# which are free for public repositories. The build system already supports +# aarch64 end to end -- bin/build, bin/sign, bin/update-repo and bin/sync-repo +# all take --arch -- but it has only ever been driven by hand, and the README +# tells you to emulate ARM64 on an x86_64 host with QEMU. Running it on a +# native runner removes the emulation entirely. +# +# This builds and uploads packages as artifacts; it deliberately does not +# publish. Publishing needs the repository signing key and the pkgs.omarchy.org +# credentials, so promoting these artifacts stays a maintainer step: +# +# bin/repo sign --arch aarch64 +# bin/repo promote --arch aarch64 +# bin/repo sync --arch aarch64 +# +# Packages whose PKGBUILD arch=() excludes aarch64 are skipped by +# should_build_for_arch() in build/build.sh, so hardware-specific x86 packages +# (nvidia, asusctl, intel-*) cost nothing here. + +on: + workflow_dispatch: + inputs: + packages: + description: 'Specific packages to build (space-separated, leave empty for all)' + required: false + default: '' + mirror: + description: 'Mirror to build against' + required: false + default: edge + type: choice + options: + - edge + - stable + +jobs: + build: + runs-on: ubuntu-24.04-arm + # A full unscoped build compiles every aarch64-capable package from source. + # GitHub caps a job at 6 hours; if the full set ever outgrows that, drive it + # in batches with the packages input rather than raising this. + timeout-minutes: 360 + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Confirm the runner is natively aarch64 + # bin/build falls back to QEMU when it finds an x86_64 host. That still + # produces correct packages, but takes many times longer -- long enough + # to hit the job timeout. If this ever runs somewhere else, fail loudly + # rather than silently emulating. + run: | + set -euo pipefail + arch=$(uname -m) + echo "Runner architecture: $arch" + if [[ $arch != "aarch64" ]]; then + echo "::error::Expected a native aarch64 runner, got $arch." \ + "Building aarch64 under emulation here would exceed the job timeout." + exit 1 + fi + + - name: Build packages + env: + PACKAGES: ${{ inputs.packages }} + MIRROR: ${{ inputs.mirror }} + run: | + set -euo pipefail + if [[ -n $PACKAGES ]]; then + bin/build --arch aarch64 --mirror "$MIRROR" --package "$PACKAGES" + else + bin/build --arch aarch64 --mirror "$MIRROR" + fi + + - name: Summarize what was built + if: always() + env: + MIRROR: ${{ inputs.mirror }} + run: | + set -euo pipefail + output="build-output/$MIRROR/aarch64" + { + echo "## aarch64 packages ($MIRROR)" + echo + if compgen -G "$output/*.pkg.tar.*" >/dev/null; then + echo '```' + (cd "$output" && ls -1 ./*.pkg.tar.*) + echo '```' + else + echo "No packages were produced." + fi + } >>"$GITHUB_STEP_SUMMARY" + + - name: Upload packages + if: always() + uses: actions/upload-artifact@v4 + with: + name: omarchy-aarch64-${{ inputs.mirror }}-${{ github.run_id }} + path: build-output/${{ inputs.mirror }}/aarch64 + if-no-files-found: warn + retention-days: 14 + + - name: Upload build logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: build-logs-${{ github.run_id }} + path: logs/ + if-no-files-found: ignore + retention-days: 14 + + - name: Notify Basecamp on failure + if: failure() && env.BASECAMP_CHATBOT_URL != '' + env: + BASECAMP_CHATBOT_URL: ${{ secrets.BASECAMP_CHATBOT_URL }} + run: | + curl -s -o /dev/null \ + -H "Content-Type: application/json" \ + -d "$(jq -n --arg content \ + "🔴 aarch64 build failed
View run" \ + '{content: $content}')" \ + "$BASECAMP_CHATBOT_URL" diff --git a/README.md b/README.md index d57fcb1a1..19885fe29 100644 --- a/README.md +++ b/README.md @@ -839,6 +839,9 @@ bin/repo release --package my-package - Same workflow, just add `--arch aarch64`; the scheduled pipeline runs it automatically once `aarch64` is in `PUBLISHED_ARCHES` - Packages whose `arch=()` lacks `aarch64` are skipped, not failed +- Or build natively in CI: the **Build aarch64 Packages** workflow runs on GitHub's + ARM64 runners, which need no emulation. Trigger it from the Actions tab; it uploads + the packages as an artifact and leaves signing and publishing to a maintainer. ### Building for Both Architectures From 8e52262e7cf6c474774a80f385eb47ed134a8e7a Mon Sep 17 00:00:00 2001 From: Scott Jones Date: Wed, 19 Aug 2026 17:53:10 -0400 Subject: [PATCH 02/11] Make the bind-mounted build directories writable by the container The builder container works as its own uid 1000 user, while a GitHub runner is uid 1001, and make_dir_writable() chowns the mounted output directories to the host user. The container then cannot write its incremental omarchy-build database, pacman -Sy fails to open it, and no makedepends resolve -- the build dies on the first package. This is invisible on a workstation, where the developer is uid 1000 too. --- .github/workflows/build-aarch64.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/build-aarch64.yml b/.github/workflows/build-aarch64.yml index d267d4838..3c0cb733f 100644 --- a/.github/workflows/build-aarch64.yml +++ b/.github/workflows/build-aarch64.yml @@ -66,6 +66,26 @@ jobs: exit 1 fi + - name: Prepare the bind-mounted build directories + # bin/build bind-mounts build-output/ and pkgs.omarchy.org/ into the + # builder container, which works as the image's "builder" user (uid + # 1000, from the useradd in build/Dockerfile). A GitHub runner is uid + # 1001, and make_dir_writable() chowns these directories to the host + # user -- so the container cannot write its incremental + # omarchy-build.db, and pacman then fails to resolve any makedepends. + # This is invisible on a workstation where the developer is also uid + # 1000. chown preserves the mode, so opening the mode first leaves both + # users able to write. + env: + MIRROR: ${{ inputs.mirror }} + run: | + set -euo pipefail + echo "Runner uid: $(id -u)" + for dir in "build-output/$MIRROR/aarch64" "pkgs.omarchy.org/$MIRROR/aarch64"; do + mkdir -p "$dir" + chmod -R 777 "$dir" + done + - name: Build packages env: PACKAGES: ${{ inputs.packages }} From 631ebe19904ab0c894e53ec1ab74c42b3c38c652 Mon Sep 17 00:00:00 2001 From: Scott Jones Date: Mon, 31 Aug 2026 13:22:33 -0400 Subject: [PATCH 03/11] Correct the publish recipe and drop the unreachable log upload The maintainer recipe in the header comment published packages without a database. bin/promote-build skips omarchy-build.db* when it moves packages into the published tree, and bin/repo update is the only thing that runs the repo-add that produces omarchy.db, so sign -> promote -> sync uploaded package files that no pacman client could resolve. Add the update step, and thread --mirror through all four: helpers/paths.sh defaults MIRROR to edge, so the recipe as written would have read the wrong tree for a stable artifact. The batching suggestion on timeout-minutes was not dependency-complete either. build/build.sh builds only the named packages and counts a dependency only when it is also in the selected set, and it configures the production [omarchy] repo only when a database already exists, which is never true on a clean runner. A batch containing omarchy without omarchy-settings fails at makepkg -s on its pinned omarchy-settings=${pkgver}. Say what the input is actually for rather than offering it as a way to split a full build. The log upload could never match. LOG_DIR is logs/, written only by bin/repo, and this workflow calls bin/build directly; build/build.sh writes no log files at all. With if-no-files-found: ignore the step was silent about it, implying a diagnostic artifact that never existed. The job log is the diagnostic. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-aarch64.yml | 39 ++++++++++++++++++----------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-aarch64.yml b/.github/workflows/build-aarch64.yml index 3c0cb733f..3eec3edc0 100644 --- a/.github/workflows/build-aarch64.yml +++ b/.github/workflows/build-aarch64.yml @@ -9,11 +9,22 @@ name: Build aarch64 Packages # # This builds and uploads packages as artifacts; it deliberately does not # publish. Publishing needs the repository signing key and the pkgs.omarchy.org -# credentials, so promoting these artifacts stays a maintainer step: +# credentials, so promoting these artifacts stays a maintainer step. Download +# the artifact into build-output//aarch64, then, with matching +# the mirror the artifact was built against: # -# bin/repo sign --arch aarch64 -# bin/repo promote --arch aarch64 -# bin/repo sync --arch aarch64 +# bin/repo sign --arch aarch64 --mirror +# bin/repo promote --arch aarch64 --mirror +# bin/repo update --arch aarch64 --mirror +# bin/repo sync --arch aarch64 --mirror +# +# The update step is not optional. bin/promote-build moves package files into +# the published tree but deliberately skips omarchy-build.db*, and bin/repo +# update is the only thing that runs the repo-add that builds omarchy.db. +# Without it bin/sync-repo uploads packages and no database, and pacman clients +# pointed at the aarch64 tree resolve nothing. --mirror matters for the same +# reason: helpers/paths.sh defaults MIRROR to edge, so omitting it while +# promoting a stable artifact reads and writes the wrong tree. # # Packages whose PKGBUILD arch=() excludes aarch64 are skipped by # should_build_for_arch() in build/build.sh, so hardware-specific x86 packages @@ -39,8 +50,15 @@ jobs: build: runs-on: ubuntu-24.04-arm # A full unscoped build compiles every aarch64-capable package from source. - # GitHub caps a job at 6 hours; if the full set ever outgrows that, drive it - # in batches with the packages input rather than raising this. + # GitHub caps a job at 6 hours. If the full set ever outgrows that, note + # that the packages input is not a general answer: build/build.sh builds + # only the names given and resolves inter-package dependencies solely + # within that set, and it configures the production [omarchy] repo only + # when a database already exists -- which on a clean runner it does not. So + # a batch has to be dependency-closed to succeed. pkgbuilds/omarchy, for + # instance, pins omarchy-settings=${pkgver} and fails at makepkg -s without + # it. The input is for targeted rebuilds; splitting a full build needs the + # batches chosen with that in mind. timeout-minutes: 360 permissions: contents: read @@ -126,15 +144,6 @@ jobs: if-no-files-found: warn retention-days: 14 - - name: Upload build logs - if: failure() - uses: actions/upload-artifact@v4 - with: - name: build-logs-${{ github.run_id }} - path: logs/ - if-no-files-found: ignore - retention-days: 14 - - name: Notify Basecamp on failure if: failure() && env.BASECAMP_CHATBOT_URL != '' env: From 2a76ee17d36ab4ab5021e7b099f7ce876d0530ce Mon Sep 17 00:00:00 2001 From: Jimmy Van Veen Date: Mon, 24 Aug 2026 23:17:23 -0400 Subject: [PATCH 04/11] Publish a reusable aarch64 package repository artifact Extend the native ARM package build so an ISO workflow can call it, receive a complete omarchy.db repository artifact, and verify the requested package set. --- .github/workflows/build-aarch64.yml | 272 +++++++++++++++------------- 1 file changed, 147 insertions(+), 125 deletions(-) diff --git a/.github/workflows/build-aarch64.yml b/.github/workflows/build-aarch64.yml index 3eec3edc0..8591314b7 100644 --- a/.github/workflows/build-aarch64.yml +++ b/.github/workflows/build-aarch64.yml @@ -1,157 +1,179 @@ -name: Build aarch64 Packages +name: aarch64 package repo -# Builds the aarch64 half of the repository on GitHub's native ARM64 runners, -# which are free for public repositories. The build system already supports -# aarch64 end to end -- bin/build, bin/sign, bin/update-repo and bin/sync-repo -# all take --arch -- but it has only ever been driven by hand, and the README -# tells you to emulate ARM64 on an x86_64 host with QEMU. Running it on a -# native runner removes the emulation entirely. +# Builds the aarch64 [omarchy] repository natively on a GitHub-hosted arm64 +# runner and publishes it as a workflow artifact. Nothing here touches the +# x86_64 path: the sync-* workflows and bin/repo's defaults are unchanged. # -# This builds and uploads packages as artifacts; it deliberately does not -# publish. Publishing needs the repository signing key and the pkgs.omarchy.org -# credentials, so promoting these artifacts stays a maintainer step. Download -# the artifact into build-output//aarch64, then, with matching -# the mirror the artifact was built against: -# -# bin/repo sign --arch aarch64 --mirror -# bin/repo promote --arch aarch64 --mirror -# bin/repo update --arch aarch64 --mirror -# bin/repo sync --arch aarch64 --mirror -# -# The update step is not optional. bin/promote-build moves package files into -# the published tree but deliberately skips omarchy-build.db*, and bin/repo -# update is the only thing that runs the repo-add that builds omarchy.db. -# Without it bin/sync-repo uploads packages and no database, and pacman clients -# pointed at the aarch64 tree resolve nothing. --mirror matters for the same -# reason: helpers/paths.sh defaults MIRROR to edge, so omitting it while -# promoting a stable artifact reads and writes the wrong tree. -# -# Packages whose PKGBUILD arch=() excludes aarch64 are skipped by -# should_build_for_arch() in build/build.sh, so hardware-specific x86 packages -# (nvidia, asusctl, intel-*) cost nothing here. +# Three entry points: +# push -- every change to the package set on the snapdragon +# branch rebuilds what moved (the tree is cached). +# workflow_dispatch -- build on demand from a branch of this repository. +# workflow_call -- called by omarchy-iso's aarch64 ISO workflow so the +# ISO build can consume the repo from the same run. on: + push: + branches: [snapdragon] + paths: + - "pkgbuilds/**" + - "bin/**" + - "build/**" + - "helpers/**" + - ".github/workflows/build-aarch64.yml" workflow_dispatch: inputs: packages: - description: 'Specific packages to build (space-separated, leave empty for all)' - required: false - default: '' + description: "Space-separated pkgbases to build (empty = the ISO set)" + type: string + default: "" + mirror: + description: "edge or stable" + type: string + default: edge + workflow_call: + inputs: + pkgs_repository: + description: "owner/name of the omarchy-pkgs checkout to build" + type: string + required: true + pkgs_ref: + description: "branch, tag or SHA of that repository" + type: string + required: true + packages: + type: string + default: "" mirror: - description: 'Mirror to build against' - required: false + type: string default: edge - type: choice - options: - - edge - - stable + outputs: + artifact: + description: "Name of the uploaded repository artifact" + value: ${{ jobs.build.outputs.artifact }} jobs: build: runs-on: ubuntu-24.04-arm - # A full unscoped build compiles every aarch64-capable package from source. - # GitHub caps a job at 6 hours. If the full set ever outgrows that, note - # that the packages input is not a general answer: build/build.sh builds - # only the names given and resolves inter-package dependencies solely - # within that set, and it configures the production [omarchy] repo only - # when a database already exists -- which on a clean runner it does not. So - # a batch has to be dependency-closed to succeed. pkgbuilds/omarchy, for - # instance, pins omarchy-settings=${pkgver} and fails at makepkg -s without - # it. The input is for targeted rebuilds; splitting a full build needs the - # batches chosen with that in mind. timeout-minutes: 360 permissions: contents: read - + outputs: + artifact: ${{ steps.meta.outputs.artifact }} + env: + ARCH: aarch64 + INPUT_MIRROR: ${{ inputs.mirror }} + INPUT_PACKAGES: ${{ inputs.packages }} + # Everything the ISO installs from [omarchy] that has an aarch64 build + # (JimmayVV/omarchy-iso#6). quickshell-git is deliberately absent: the + # ISO substitutes Arch Linux ARM's quickshell for it. + ISO_PACKAGES: >- + omarchy-keyring ttf-jetbrains-mono-nerd-basic limine-mkinitcpio-hook limine-snapper-sync + linux-aarch64-pkgbase-shim qcom-firmware-extract + omarchy-settings omarchy omarchy-nvim + aether cliamp herdr localsend-bin mise-bin omacalc omacut omawrite omarchy-chromium-bin + ttfx yay tobi-try ttf-ia-writer ufw-docker xdg-terminal-exec yaru-icon-theme + tzupdate tensaku hyprland-preview-share-picker steps: - - name: Checkout repository + - name: Checkout omarchy-pkgs uses: actions/checkout@v4 with: - persist-credentials: false + repository: ${{ inputs.pkgs_repository || github.repository }} + ref: ${{ inputs.pkgs_ref || github.ref }} - - name: Confirm the runner is natively aarch64 - # bin/build falls back to QEMU when it finds an x86_64 host. That still - # produces correct packages, but takes many times longer -- long enough - # to hit the job timeout. If this ever runs somewhere else, fail loudly - # rather than silently emulating. + - name: Resolve inputs + id: meta run: | - set -euo pipefail - arch=$(uname -m) - echo "Runner architecture: $arch" - if [[ $arch != "aarch64" ]]; then - echo "::error::Expected a native aarch64 runner, got $arch." \ - "Building aarch64 under emulation here would exceed the job timeout." - exit 1 - fi + MIRROR="${INPUT_MIRROR:-edge}" + PACKAGES="${INPUT_PACKAGES:-$ISO_PACKAGES}" + case "$MIRROR" in edge|stable) ;; *) echo "invalid mirror: $MIRROR" >&2; exit 1 ;; esac + re='^[A-Za-z0-9._+ -]+$' + if ! [[ $PACKAGES =~ $re ]]; then echo "invalid package list" >&2; exit 1; fi + { + echo "MIRROR=$MIRROR" + echo "PACKAGES=$PACKAGES" + echo "REPO_DIR=pkgs.omarchy.org/$MIRROR/$ARCH" + echo "OUTPUT_DIR=build-output/$MIRROR/$ARCH" + } >> "$GITHUB_ENV" + echo "artifact=omarchy-repo-$ARCH-$MIRROR" >> "$GITHUB_OUTPUT" - - name: Prepare the bind-mounted build directories - # bin/build bind-mounts build-output/ and pkgs.omarchy.org/ into the - # builder container, which works as the image's "builder" user (uid - # 1000, from the useradd in build/Dockerfile). A GitHub runner is uid - # 1001, and make_dir_writable() chowns these directories to the host - # user -- so the container cannot write its incremental - # omarchy-build.db, and pacman then fails to resolve any makedepends. - # This is invisible on a workstation where the developer is also uid - # 1000. chown preserves the mode, so opening the mode first leaves both - # users able to write. - env: - MIRROR: ${{ inputs.mirror }} + - name: Runner facts run: | - set -euo pipefail - echo "Runner uid: $(id -u)" - for dir in "build-output/$MIRROR/aarch64" "pkgs.omarchy.org/$MIRROR/aarch64"; do - mkdir -p "$dir" - chmod -R 777 "$dir" - done + set -x + uname -a + nproc + free -h + df -h / /mnt 2>/dev/null || df -h / + docker version + docker buildx version + docker info --format '{{.Driver}} {{.DockerRootDir}}' + id + + # bin/repo build only rebuilds packages whose version moved relative to + # the omarchy.db already in the repository tree, so carry the tree across + # runs. The key is the PKGBUILD set; a partial hit still saves everything + # whose PKGBUILD did not change. + - name: Restore repository tree + uses: actions/cache@v4 + with: + path: ${{ env.REPO_DIR }} + key: omarchy-repo-${{ env.ARCH }}-${{ env.MIRROR }}-${{ hashFiles('pkgbuilds/**/PKGBUILD', 'pkgbuilds/**/.omarchy/**') }} + restore-keys: | + omarchy-repo-${{ env.ARCH }}-${{ env.MIRROR }}- + + # The builder image runs makepkg as uid 1000; the runner is uid 1001. + # make_dir_writable chowns to the runner, so the directories must be + # world-writable before it runs or every package write fails. + - name: Prepare output directories + run: | + mkdir -p "$OUTPUT_DIR" "$REPO_DIR" src logs + chmod -R a+rwX build-output pkgs.omarchy.org src + + - name: Build plan + run: bin/repo build --arch "$ARCH" --mirror "$MIRROR" --dry-run --package $PACKAGES - name: Build packages - env: - PACKAGES: ${{ inputs.packages }} - MIRROR: ${{ inputs.mirror }} run: | - set -euo pipefail - if [[ -n $PACKAGES ]]; then - bin/build --arch aarch64 --mirror "$MIRROR" --package "$PACKAGES" - else - bin/build --arch aarch64 --mirror "$MIRROR" - fi + start=$(date +%s) + bin/repo build --arch "$ARCH" --mirror "$MIRROR" --package $PACKAGES + echo "build wall-clock: $(( $(date +%s) - start ))s" - - name: Summarize what was built - if: always() - env: - MIRROR: ${{ inputs.mirror }} + # Replaces bin/repo sign + promote, which need the repository host's key. + - name: Publish into the repository tree and write omarchy.db run: | - set -euo pipefail - output="build-output/$MIRROR/aarch64" - { - echo "## aarch64 packages ($MIRROR)" - echo - if compgen -G "$output/*.pkg.tar.*" >/dev/null; then - echo '```' - (cd "$output" && ls -1 ./*.pkg.tar.*) - echo '```' - else - echo "No packages were produced." - fi - } >>"$GITHUB_STEP_SUMMARY" + shopt -s nullglob + files=("$OUTPUT_DIR"/*.pkg.tar.*) + echo "new package files: ${#files[@]}" + [[ ${#files[@]} -gt 0 ]] && cp -v "${files[@]}" "$REPO_DIR/" + chmod -R a+rwX pkgs.omarchy.org + bin/repo update --arch "$ARCH" --mirror "$MIRROR" - - name: Upload packages - if: always() + - name: Verify repository + run: | + cd "$REPO_DIR" + ls -la + echo "== packages in omarchy.db ==" + tar --use-compress-program=unzstd -tf omarchy.db.tar.zst | grep '/$' | sed 's|/$||' | sort + echo "== missing from the requested set ==" + missing=0 + for p in $PACKAGES; do + tar --use-compress-program=unzstd -tf omarchy.db.tar.zst | grep -q "^$p-[^-]*-[^-]*/$" || { echo " $p"; missing=1; } + done + du -sh . + exit $missing + + - name: Upload repository artifact uses: actions/upload-artifact@v4 with: - name: omarchy-aarch64-${{ inputs.mirror }}-${{ github.run_id }} - path: build-output/${{ inputs.mirror }}/aarch64 - if-no-files-found: warn - retention-days: 14 + name: ${{ steps.meta.outputs.artifact }} + path: ${{ env.REPO_DIR }} + if-no-files-found: error + retention-days: 7 - - name: Notify Basecamp on failure - if: failure() && env.BASECAMP_CHATBOT_URL != '' - env: - BASECAMP_CHATBOT_URL: ${{ secrets.BASECAMP_CHATBOT_URL }} - run: | - curl -s -o /dev/null \ - -H "Content-Type: application/json" \ - -d "$(jq -n --arg content \ - "🔴 aarch64 build failed
View run" \ - '{content: $content}')" \ - "$BASECAMP_CHATBOT_URL" + - name: Upload build logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: build-logs-${{ env.ARCH }}-${{ env.MIRROR }} + path: logs/ + if-no-files-found: ignore + retention-days: 7 From fdc08af66e145a6d043c0db7b8d68619ea92042b Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Thu, 27 Aug 2026 23:24:16 +0200 Subject: [PATCH 05/11] Make the reusable ARM repository build branch-neutral --- .github/workflows/build-aarch64.yml | 30 ++++------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build-aarch64.yml b/.github/workflows/build-aarch64.yml index 8591314b7..d1447a507 100644 --- a/.github/workflows/build-aarch64.yml +++ b/.github/workflows/build-aarch64.yml @@ -4,26 +4,14 @@ name: aarch64 package repo # runner and publishes it as a workflow artifact. Nothing here touches the # x86_64 path: the sync-* workflows and bin/repo's defaults are unchanged. # -# Three entry points: -# push -- every change to the package set on the snapdragon -# branch rebuilds what moved (the tree is cached). -# workflow_dispatch -- build on demand from a branch of this repository. -# workflow_call -- called by omarchy-iso's aarch64 ISO workflow so the -# ISO build can consume the repo from the same run. +# Two entry points: build on demand, or let an ISO workflow call this one and +# consume the complete repository artifact in the same run. on: - push: - branches: [snapdragon] - paths: - - "pkgbuilds/**" - - "bin/**" - - "build/**" - - "helpers/**" - - ".github/workflows/build-aarch64.yml" workflow_dispatch: inputs: packages: - description: "Space-separated pkgbases to build (empty = the ISO set)" + description: "Space-separated pkgbases to build (empty = every eligible package)" type: string default: "" mirror: @@ -63,16 +51,6 @@ jobs: ARCH: aarch64 INPUT_MIRROR: ${{ inputs.mirror }} INPUT_PACKAGES: ${{ inputs.packages }} - # Everything the ISO installs from [omarchy] that has an aarch64 build - # (JimmayVV/omarchy-iso#6). quickshell-git is deliberately absent: the - # ISO substitutes Arch Linux ARM's quickshell for it. - ISO_PACKAGES: >- - omarchy-keyring ttf-jetbrains-mono-nerd-basic limine-mkinitcpio-hook limine-snapper-sync - linux-aarch64-pkgbase-shim qcom-firmware-extract - omarchy-settings omarchy omarchy-nvim - aether cliamp herdr localsend-bin mise-bin omacalc omacut omawrite omarchy-chromium-bin - ttfx yay tobi-try ttf-ia-writer ufw-docker xdg-terminal-exec yaru-icon-theme - tzupdate tensaku hyprland-preview-share-picker steps: - name: Checkout omarchy-pkgs uses: actions/checkout@v4 @@ -84,7 +62,7 @@ jobs: id: meta run: | MIRROR="${INPUT_MIRROR:-edge}" - PACKAGES="${INPUT_PACKAGES:-$ISO_PACKAGES}" + PACKAGES="$INPUT_PACKAGES" case "$MIRROR" in edge|stable) ;; *) echo "invalid mirror: $MIRROR" >&2; exit 1 ;; esac re='^[A-Za-z0-9._+ -]+$' if ! [[ $PACKAGES =~ $re ]]; then echo "invalid package list" >&2; exit 1; fi From 67e3e70ac5e2a46a61dd7e1ad6e061c359a71dda Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Fri, 28 Aug 2026 13:05:39 +0200 Subject: [PATCH 06/11] Trim aarch64 repository comments --- .github/workflows/build-aarch64.yml | 16 +++------------- README.md | 4 +--- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-aarch64.yml b/.github/workflows/build-aarch64.yml index d1447a507..86e004cef 100644 --- a/.github/workflows/build-aarch64.yml +++ b/.github/workflows/build-aarch64.yml @@ -1,11 +1,6 @@ name: aarch64 package repo -# Builds the aarch64 [omarchy] repository natively on a GitHub-hosted arm64 -# runner and publishes it as a workflow artifact. Nothing here touches the -# x86_64 path: the sync-* workflows and bin/repo's defaults are unchanged. -# -# Two entry points: build on demand, or let an ISO workflow call this one and -# consume the complete repository artifact in the same run. +# Build an aarch64 Omarchy repository for manual or reusable workflows. on: workflow_dispatch: @@ -86,10 +81,7 @@ jobs: docker info --format '{{.Driver}} {{.DockerRootDir}}' id - # bin/repo build only rebuilds packages whose version moved relative to - # the omarchy.db already in the repository tree, so carry the tree across - # runs. The key is the PKGBUILD set; a partial hit still saves everything - # whose PKGBUILD did not change. + # Reuse packages whose build inputs have not changed. - name: Restore repository tree uses: actions/cache@v4 with: @@ -98,9 +90,7 @@ jobs: restore-keys: | omarchy-repo-${{ env.ARCH }}-${{ env.MIRROR }}- - # The builder image runs makepkg as uid 1000; the runner is uid 1001. - # make_dir_writable chowns to the runner, so the directories must be - # world-writable before it runs or every package write fails. + # The builder and runner use different user IDs. - name: Prepare output directories run: | mkdir -p "$OUTPUT_DIR" "$REPO_DIR" src logs diff --git a/README.md b/README.md index 19885fe29..04f56c5e9 100644 --- a/README.md +++ b/README.md @@ -839,9 +839,7 @@ bin/repo release --package my-package - Same workflow, just add `--arch aarch64`; the scheduled pipeline runs it automatically once `aarch64` is in `PUBLISHED_ARCHES` - Packages whose `arch=()` lacks `aarch64` are skipped, not failed -- Or build natively in CI: the **Build aarch64 Packages** workflow runs on GitHub's - ARM64 runners, which need no emulation. Trigger it from the Actions tab; it uploads - the packages as an artifact and leaves signing and publishing to a maintainer. +- For native CI builds, run the **aarch64 package repo** workflow. ### Building for Both Architectures From eb272760bf1ba932ec5977ad11bb70bd8776975c Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Sun, 6 Sep 2026 01:20:40 +0200 Subject: [PATCH 07/11] Validate reusable ARM builds and invalidate stale repository caches --- .github/workflows/build-aarch64.yml | 48 +++++++++---- test/test_build_aarch64.py | 105 ++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 14 deletions(-) create mode 100644 test/test_build_aarch64.py diff --git a/.github/workflows/build-aarch64.yml b/.github/workflows/build-aarch64.yml index 86e004cef..d342efeae 100644 --- a/.github/workflows/build-aarch64.yml +++ b/.github/workflows/build-aarch64.yml @@ -10,7 +10,7 @@ on: type: string default: "" mirror: - description: "edge or stable" + description: "edge, rc or stable" type: string default: edge workflow_call: @@ -58,9 +58,17 @@ jobs: run: | MIRROR="${INPUT_MIRROR:-edge}" PACKAGES="$INPUT_PACKAGES" - case "$MIRROR" in edge|stable) ;; *) echo "invalid mirror: $MIRROR" >&2; exit 1 ;; esac - re='^[A-Za-z0-9._+ -]+$' + case "$MIRROR" in edge|rc|stable) ;; *) echo "invalid mirror: $MIRROR" >&2; exit 1 ;; esac + re='^[A-Za-z0-9._+ -]*$' if ! [[ $PACKAGES =~ $re ]]; then echo "invalid package list" >&2; exit 1; fi + read -r -a pkgbases <<< "$PACKAGES" + for package in "${pkgbases[@]}"; do + if ! [[ $package =~ ^[A-Za-z0-9][A-Za-z0-9._+-]*$ && -f pkgbuilds/$package/PKGBUILD ]]; then + echo "unknown pkgbase: $package" >&2 + exit 1 + fi + done + PACKAGES="${pkgbases[*]}" { echo "MIRROR=$MIRROR" echo "PACKAGES=$PACKAGES" @@ -68,6 +76,7 @@ jobs: echo "OUTPUT_DIR=build-output/$MIRROR/$ARCH" } >> "$GITHUB_ENV" echo "artifact=omarchy-repo-$ARCH-$MIRROR" >> "$GITHUB_OUTPUT" + echo "packages_key=$(printf '%s\0' "${pkgbases[@]}" | sha256sum | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" - name: Runner facts run: | @@ -86,9 +95,7 @@ jobs: uses: actions/cache@v4 with: path: ${{ env.REPO_DIR }} - key: omarchy-repo-${{ env.ARCH }}-${{ env.MIRROR }}-${{ hashFiles('pkgbuilds/**/PKGBUILD', 'pkgbuilds/**/.omarchy/**') }} - restore-keys: | - omarchy-repo-${{ env.ARCH }}-${{ env.MIRROR }}- + key: omarchy-repo-v2-${{ env.ARCH }}-${{ env.MIRROR }}-${{ steps.meta.outputs.packages_key }}-${{ hashFiles('pkgbuilds/**', 'build/**', 'helpers/**', 'bin/**') }} # The builder and runner use different user IDs. - name: Prepare output directories @@ -97,19 +104,22 @@ jobs: chmod -R a+rwX build-output pkgs.omarchy.org src - name: Build plan - run: bin/repo build --arch "$ARCH" --mirror "$MIRROR" --dry-run --package $PACKAGES + run: | + read -r -a pkgbases <<< "$PACKAGES" + bin/repo build --arch "$ARCH" --mirror "$MIRROR" --dry-run --package "${pkgbases[@]}" - name: Build packages run: | start=$(date +%s) - bin/repo build --arch "$ARCH" --mirror "$MIRROR" --package $PACKAGES + read -r -a pkgbases <<< "$PACKAGES" + bin/repo build --arch "$ARCH" --mirror "$MIRROR" --package "${pkgbases[@]}" echo "build wall-clock: $(( $(date +%s) - start ))s" # Replaces bin/repo sign + promote, which need the repository host's key. - name: Publish into the repository tree and write omarchy.db run: | shopt -s nullglob - files=("$OUTPUT_DIR"/*.pkg.tar.*) + files=("$OUTPUT_DIR"/*.pkg.tar.zst "$OUTPUT_DIR"/*.pkg.tar.xz) echo "new package files: ${#files[@]}" [[ ${#files[@]} -gt 0 ]] && cp -v "${files[@]}" "$REPO_DIR/" chmod -R a+rwX pkgs.omarchy.org @@ -119,12 +129,22 @@ jobs: run: | cd "$REPO_DIR" ls -la - echo "== packages in omarchy.db ==" - tar --use-compress-program=unzstd -tf omarchy.db.tar.zst | grep '/$' | sed 's|/$||' | sort - echo "== missing from the requested set ==" + dbdir=$(mktemp -d) + trap 'rm -rf "$dbdir"' EXIT + tar --use-compress-program=unzstd -xf omarchy.db.tar.zst -C "$dbdir" + for desc in "$dbdir"/*/desc; do + awk ' + /^%BASE%$/ { getline; base=$0 } + /^%NAME%$/ { getline; name=$0 } + END { print (base != "" ? base : name) } + ' "$desc" + done > "$dbdir/pkgbases" + echo "== pkgbases in omarchy.db ==" + sort -u "$dbdir/pkgbases" missing=0 - for p in $PACKAGES; do - tar --use-compress-program=unzstd -tf omarchy.db.tar.zst | grep -q "^$p-[^-]*-[^-]*/$" || { echo " $p"; missing=1; } + read -r -a pkgbases <<< "$PACKAGES" + for p in "${pkgbases[@]}"; do + grep -Fxq "$p" "$dbdir/pkgbases" || { echo "missing pkgbase: $p" >&2; missing=1; } done du -sh . exit $missing diff --git a/test/test_build_aarch64.py b/test/test_build_aarch64.py new file mode 100644 index 000000000..1160feaf6 --- /dev/null +++ b/test/test_build_aarch64.py @@ -0,0 +1,105 @@ +"""Exercise workflow shell steps. Requires Bash, GNU tar, zstd and PyYAML.""" + +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + +import yaml + + +WORKFLOW = yaml.safe_load( + (Path(__file__).resolve().parents[1] / ".github/workflows/build-aarch64.yml").read_text() +) +STEPS = {step["name"]: step for step in WORKFLOW["jobs"]["build"]["steps"]} + + +class BuildAarch64Test(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) + package = self.root / "pkgbuilds/example/PKGBUILD" + package.parent.mkdir(parents=True) + package.touch() + + def run_step(self, name, **env): + return subprocess.run( + ["bash", "-euo", "pipefail", "-c", STEPS[name]["run"]], + cwd=self.root, + env={ + **os.environ, + "ARCH": "aarch64", + "INPUT_MIRROR": "edge", + "INPUT_PACKAGES": "", + "GITHUB_ENV": str(self.root / "env"), + "GITHUB_OUTPUT": str(self.root / "output"), + **env, + }, + capture_output=True, + text=True, + ) + + def test_empty_package_set_is_valid(self): + result = self.run_step("Resolve inputs") + self.assertEqual(result.returncode, 0, result.stderr) + + def test_known_package_and_rc_mirror(self): + result = self.run_step("Resolve inputs", INPUT_PACKAGES=" example ", INPUT_MIRROR="rc") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("PACKAGES=example\n", (self.root / "env").read_text()) + + def test_options_paths_unknown_packages_and_newlines_are_rejected(self): + for value in ("--dry-run", "..", "../example", "missing", "example\ninjected=value"): + with self.subTest(value=value): + result = self.run_step("Resolve inputs", INPUT_PACKAGES=value) + self.assertNotEqual(result.returncode, 0) + + def test_split_package_is_verified_by_pkgbase(self): + repo = self.root / "repo" + desc = repo / "example-libs-1-1/desc" + desc.parent.mkdir(parents=True) + desc.write_text("%NAME%\nexample-libs\n\n%BASE%\nexample\n") + subprocess.run( + ["tar", "--zstd", "-cf", "omarchy.db.tar.zst", desc.parent.name], + cwd=repo, check=True, + ) + result = self.run_step("Verify repository", REPO_DIR=str(repo), PACKAGES="example") + self.assertEqual(result.returncode, 0, result.stderr) + result = self.run_step("Verify repository", REPO_DIR=str(repo), PACKAGES="exampl.") + self.assertNotEqual(result.returncode, 0) + + def test_cache_includes_sources_and_package_selection(self): + cache = STEPS["Restore repository tree"]["with"] + self.assertNotIn("restore-keys", cache) + self.assertIn("packages_key", cache["key"]) + for path in ("pkgbuilds/**", "build/**", "helpers/**", "bin/**"): + self.assertIn(path, cache["key"]) + + @unittest.skipUnless(shutil.which("makepkg") and shutil.which("repo-add"), "requires Arch packaging tools") + def test_real_repository_records_pkgbase(self): + recipe = self.root / "pkgbuilds/example" + (recipe / "PKGBUILD").write_text('''pkgbase=example +pkgname=(example-libs) +pkgver=1 +pkgrel=1 +arch=(any) +license=(MIT) +package() { + install -Dm644 "$startdir/PKGBUILD" "$pkgdir/usr/share/example/PKGBUILD" +} +''') + subprocess.run(["makepkg", "--nodeps", "--noconfirm"], cwd=recipe, check=True, + stdout=subprocess.DEVNULL) + packages = list(recipe.glob("*.pkg.tar.zst")) + self.assertEqual(len(packages), 1) + subprocess.run(["repo-add", "omarchy.db.tar.zst", packages[0].name], cwd=recipe, + check=True, stdout=subprocess.DEVNULL) + result = self.run_step("Verify repository", REPO_DIR=str(recipe), PACKAGES="example") + self.assertEqual(result.returncode, 0, result.stderr) + + +if __name__ == "__main__": + unittest.main() From c4a26810802cd30df2d438534340d8015eaedef9 Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Sun, 6 Sep 2026 03:22:15 +0200 Subject: [PATCH 08/11] Run package regression suites in existing CI --- .github/workflows/test.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9fc270f3b..106fe6aac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,9 +25,16 @@ jobs: -w /workspace \ archlinux:base-devel bash -lc ' set -euo pipefail - pacman -Syu --noconfirm git jq + pacman -Syu --noconfirm git jq python-yaml xz zstd ./bin/sync-upstream self-test ./bin/sync-rebuilds --self-test ./bin/omarchy-pkgs self-test ./bin/omarchy-release self-test + shopt -s nullglob + for test in pkgbuilds/*/test.sh; do + bash "$test" + done + if [[ -d test ]]; then + runuser -u nobody -- python -m unittest discover -s test -v + fi ' From 35fdd994ffd4b631fc3c105eeafc7511325d62bc Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Sun, 6 Sep 2026 03:22:52 +0200 Subject: [PATCH 09/11] Trim ARM build diagnostics and use stable test step IDs --- .github/workflows/build-aarch64.yml | 17 ++--------------- test/test_build_aarch64.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build-aarch64.yml b/.github/workflows/build-aarch64.yml index d342efeae..e4bb8bf6c 100644 --- a/.github/workflows/build-aarch64.yml +++ b/.github/workflows/build-aarch64.yml @@ -78,20 +78,9 @@ jobs: echo "artifact=omarchy-repo-$ARCH-$MIRROR" >> "$GITHUB_OUTPUT" echo "packages_key=$(printf '%s\0' "${pkgbases[@]}" | sha256sum | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" - - name: Runner facts - run: | - set -x - uname -a - nproc - free -h - df -h / /mnt 2>/dev/null || df -h / - docker version - docker buildx version - docker info --format '{{.Driver}} {{.DockerRootDir}}' - id - # Reuse packages whose build inputs have not changed. - name: Restore repository tree + id: cache uses: actions/cache@v4 with: path: ${{ env.REPO_DIR }} @@ -110,10 +99,8 @@ jobs: - name: Build packages run: | - start=$(date +%s) read -r -a pkgbases <<< "$PACKAGES" bin/repo build --arch "$ARCH" --mirror "$MIRROR" --package "${pkgbases[@]}" - echo "build wall-clock: $(( $(date +%s) - start ))s" # Replaces bin/repo sign + promote, which need the repository host's key. - name: Publish into the repository tree and write omarchy.db @@ -126,9 +113,9 @@ jobs: bin/repo update --arch "$ARCH" --mirror "$MIRROR" - name: Verify repository + id: verify run: | cd "$REPO_DIR" - ls -la dbdir=$(mktemp -d) trap 'rm -rf "$dbdir"' EXIT tar --use-compress-program=unzstd -xf omarchy.db.tar.zst -C "$dbdir" diff --git a/test/test_build_aarch64.py b/test/test_build_aarch64.py index 1160feaf6..ee32197d5 100644 --- a/test/test_build_aarch64.py +++ b/test/test_build_aarch64.py @@ -13,7 +13,7 @@ WORKFLOW = yaml.safe_load( (Path(__file__).resolve().parents[1] / ".github/workflows/build-aarch64.yml").read_text() ) -STEPS = {step["name"]: step for step in WORKFLOW["jobs"]["build"]["steps"]} +STEPS = {step["id"]: step for step in WORKFLOW["jobs"]["build"]["steps"] if "id" in step} class BuildAarch64Test(unittest.TestCase): @@ -43,18 +43,18 @@ def run_step(self, name, **env): ) def test_empty_package_set_is_valid(self): - result = self.run_step("Resolve inputs") + result = self.run_step("meta") self.assertEqual(result.returncode, 0, result.stderr) def test_known_package_and_rc_mirror(self): - result = self.run_step("Resolve inputs", INPUT_PACKAGES=" example ", INPUT_MIRROR="rc") + result = self.run_step("meta", INPUT_PACKAGES=" example ", INPUT_MIRROR="rc") self.assertEqual(result.returncode, 0, result.stderr) self.assertIn("PACKAGES=example\n", (self.root / "env").read_text()) def test_options_paths_unknown_packages_and_newlines_are_rejected(self): for value in ("--dry-run", "..", "../example", "missing", "example\ninjected=value"): with self.subTest(value=value): - result = self.run_step("Resolve inputs", INPUT_PACKAGES=value) + result = self.run_step("meta", INPUT_PACKAGES=value) self.assertNotEqual(result.returncode, 0) def test_split_package_is_verified_by_pkgbase(self): @@ -66,13 +66,13 @@ def test_split_package_is_verified_by_pkgbase(self): ["tar", "--zstd", "-cf", "omarchy.db.tar.zst", desc.parent.name], cwd=repo, check=True, ) - result = self.run_step("Verify repository", REPO_DIR=str(repo), PACKAGES="example") + result = self.run_step("verify", REPO_DIR=str(repo), PACKAGES="example") self.assertEqual(result.returncode, 0, result.stderr) - result = self.run_step("Verify repository", REPO_DIR=str(repo), PACKAGES="exampl.") + result = self.run_step("verify", REPO_DIR=str(repo), PACKAGES="exampl.") self.assertNotEqual(result.returncode, 0) def test_cache_includes_sources_and_package_selection(self): - cache = STEPS["Restore repository tree"]["with"] + cache = STEPS["cache"]["with"] self.assertNotIn("restore-keys", cache) self.assertIn("packages_key", cache["key"]) for path in ("pkgbuilds/**", "build/**", "helpers/**", "bin/**"): @@ -97,7 +97,7 @@ def test_real_repository_records_pkgbase(self): self.assertEqual(len(packages), 1) subprocess.run(["repo-add", "omarchy.db.tar.zst", packages[0].name], cwd=recipe, check=True, stdout=subprocess.DEVNULL) - result = self.run_step("Verify repository", REPO_DIR=str(recipe), PACKAGES="example") + result = self.run_step("verify", REPO_DIR=str(recipe), PACKAGES="example") self.assertEqual(result.returncode, 0, result.stderr) From 7e4926dd2bbc3356d084b1dae2dea5368ceec032 Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Sun, 6 Sep 2026 20:03:35 +0200 Subject: [PATCH 10/11] Resolve ARM repository package bases from their recipes Use the existing architecture-aware metadata helper instead of treating recipe directories as pkgbases. Clarify input names and extend the existing regression cases for differing names, pkgname fallback, and target-architecture metadata. --- .github/workflows/build-aarch64.yml | 30 ++++++++++++++++------------- test/test_build_aarch64.py | 19 ++++++++++++++---- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-aarch64.yml b/.github/workflows/build-aarch64.yml index e4bb8bf6c..228dc6739 100644 --- a/.github/workflows/build-aarch64.yml +++ b/.github/workflows/build-aarch64.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: inputs: packages: - description: "Space-separated pkgbases to build (empty = every eligible package)" + description: "Space-separated recipe directory names (empty = every eligible package)" type: string default: "" mirror: @@ -61,14 +61,14 @@ jobs: case "$MIRROR" in edge|rc|stable) ;; *) echo "invalid mirror: $MIRROR" >&2; exit 1 ;; esac re='^[A-Za-z0-9._+ -]*$' if ! [[ $PACKAGES =~ $re ]]; then echo "invalid package list" >&2; exit 1; fi - read -r -a pkgbases <<< "$PACKAGES" - for package in "${pkgbases[@]}"; do + read -r -a packages <<< "$PACKAGES" + for package in "${packages[@]}"; do if ! [[ $package =~ ^[A-Za-z0-9][A-Za-z0-9._+-]*$ && -f pkgbuilds/$package/PKGBUILD ]]; then - echo "unknown pkgbase: $package" >&2 + echo "unknown recipe: $package" >&2 exit 1 fi done - PACKAGES="${pkgbases[*]}" + PACKAGES="${packages[*]}" { echo "MIRROR=$MIRROR" echo "PACKAGES=$PACKAGES" @@ -76,7 +76,7 @@ jobs: echo "OUTPUT_DIR=build-output/$MIRROR/$ARCH" } >> "$GITHUB_ENV" echo "artifact=omarchy-repo-$ARCH-$MIRROR" >> "$GITHUB_OUTPUT" - echo "packages_key=$(printf '%s\0' "${pkgbases[@]}" | sha256sum | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" + echo "packages_key=$(printf '%s\0' "${packages[@]}" | sha256sum | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" # Reuse packages whose build inputs have not changed. - name: Restore repository tree @@ -94,13 +94,13 @@ jobs: - name: Build plan run: | - read -r -a pkgbases <<< "$PACKAGES" - bin/repo build --arch "$ARCH" --mirror "$MIRROR" --dry-run --package "${pkgbases[@]}" + read -r -a packages <<< "$PACKAGES" + bin/repo build --arch "$ARCH" --mirror "$MIRROR" --dry-run --package "${packages[@]}" - name: Build packages run: | - read -r -a pkgbases <<< "$PACKAGES" - bin/repo build --arch "$ARCH" --mirror "$MIRROR" --package "${pkgbases[@]}" + read -r -a packages <<< "$PACKAGES" + bin/repo build --arch "$ARCH" --mirror "$MIRROR" --package "${packages[@]}" # Replaces bin/repo sign + promote, which need the repository host's key. - name: Publish into the repository tree and write omarchy.db @@ -115,6 +115,8 @@ jobs: - name: Verify repository id: verify run: | + source helpers/package-metadata.sh + pkgbuilds="$PWD/pkgbuilds" cd "$REPO_DIR" dbdir=$(mktemp -d) trap 'rm -rf "$dbdir"' EXIT @@ -129,9 +131,11 @@ jobs: echo "== pkgbases in omarchy.db ==" sort -u "$dbdir/pkgbases" missing=0 - read -r -a pkgbases <<< "$PACKAGES" - for p in "${pkgbases[@]}"; do - grep -Fxq "$p" "$dbdir/pkgbases" || { echo "missing pkgbase: $p" >&2; missing=1; } + read -r -a packages <<< "$PACKAGES" + for package in "${packages[@]}"; do + pkgbase=$(package_pkgbuild_var "$pkgbuilds/$package" pkgbase) + [[ -n $pkgbase ]] || pkgbase=$(package_pkgbuild_var "$pkgbuilds/$package" pkgname) + grep -Fxq "$pkgbase" "$dbdir/pkgbases" || { echo "missing pkgbase: $pkgbase ($package)" >&2; missing=1; } done du -sh . exit $missing diff --git a/test/test_build_aarch64.py b/test/test_build_aarch64.py index ee32197d5..2890076a5 100644 --- a/test/test_build_aarch64.py +++ b/test/test_build_aarch64.py @@ -23,7 +23,10 @@ def setUp(self): self.root = Path(self.tmp.name) package = self.root / "pkgbuilds/example/PKGBUILD" package.parent.mkdir(parents=True) - package.touch() + package.write_text("pkgname=example\n") + helpers = self.root / "helpers" + helpers.mkdir() + shutil.copy(Path(__file__).resolve().parents[1] / "helpers/package-metadata.sh", helpers) def run_step(self, name, **env): return subprocess.run( @@ -58,17 +61,25 @@ def test_options_paths_unknown_packages_and_newlines_are_rejected(self): self.assertNotEqual(result.returncode, 0) def test_split_package_is_verified_by_pkgbase(self): + (self.root / "pkgbuilds/example/PKGBUILD").write_text("pkgbase=example-source\npkgname=(example-libs)\n") repo = self.root / "repo" desc = repo / "example-libs-1-1/desc" desc.parent.mkdir(parents=True) - desc.write_text("%NAME%\nexample-libs\n\n%BASE%\nexample\n") + desc.write_text("%NAME%\nexample-libs\n\n%BASE%\nexample-source\n") subprocess.run( ["tar", "--zstd", "-cf", "omarchy.db.tar.zst", desc.parent.name], cwd=repo, check=True, ) result = self.run_step("verify", REPO_DIR=str(repo), PACKAGES="example") self.assertEqual(result.returncode, 0, result.stderr) - result = self.run_step("verify", REPO_DIR=str(repo), PACKAGES="exampl.") + # A recipe without pkgbase uses pkgname, read for the target architecture. + (self.root / "pkgbuilds/example/PKGBUILD").write_text( + '[[ $CARCH == aarch64 ]] || return 1\npkgname=example-source\n' + ) + result = self.run_step("verify", REPO_DIR=str(repo), PACKAGES="example") + self.assertEqual(result.returncode, 0, result.stderr) + (self.root / "pkgbuilds/example/PKGBUILD").write_text("pkgbase=exampl.-source\npkgname=(example-libs)\n") + result = self.run_step("verify", REPO_DIR=str(repo), PACKAGES="example") self.assertNotEqual(result.returncode, 0) def test_cache_includes_sources_and_package_selection(self): @@ -81,7 +92,7 @@ def test_cache_includes_sources_and_package_selection(self): @unittest.skipUnless(shutil.which("makepkg") and shutil.which("repo-add"), "requires Arch packaging tools") def test_real_repository_records_pkgbase(self): recipe = self.root / "pkgbuilds/example" - (recipe / "PKGBUILD").write_text('''pkgbase=example + (recipe / "PKGBUILD").write_text('''pkgbase=example-source pkgname=(example-libs) pkgver=1 pkgrel=1 From 3062872d9543350f847e94392cf9ba6ca13aa4ea Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Mon, 7 Sep 2026 14:27:24 +0200 Subject: [PATCH 11/11] Keep ARM build credentials ephemeral and clarify package selection --- .github/workflows/build-aarch64.yml | 8 +++++++- test/test_build_aarch64.py | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-aarch64.yml b/.github/workflows/build-aarch64.yml index 228dc6739..3321c2793 100644 --- a/.github/workflows/build-aarch64.yml +++ b/.github/workflows/build-aarch64.yml @@ -6,7 +6,9 @@ on: workflow_dispatch: inputs: packages: - description: "Space-separated recipe directory names (empty = every eligible package)" + description: >- + Space-separated recipe directory names (empty = every eligible package). + Include required recipes from this repository; dependencies are not added automatically. type: string default: "" mirror: @@ -24,6 +26,9 @@ on: type: string required: true packages: + description: >- + Space-separated recipe directory names (empty = every eligible package). + Include required recipes from this repository; dependencies are not added automatically. type: string default: "" mirror: @@ -52,6 +57,7 @@ jobs: with: repository: ${{ inputs.pkgs_repository || github.repository }} ref: ${{ inputs.pkgs_ref || github.ref }} + persist-credentials: false - name: Resolve inputs id: meta diff --git a/test/test_build_aarch64.py b/test/test_build_aarch64.py index 2890076a5..3db307446 100644 --- a/test/test_build_aarch64.py +++ b/test/test_build_aarch64.py @@ -49,6 +49,11 @@ def test_empty_package_set_is_valid(self): result = self.run_step("meta") self.assertEqual(result.returncode, 0, result.stderr) + def test_checkout_does_not_persist_credentials(self): + checkout = next(step for step in WORKFLOW["jobs"]["build"]["steps"] + if step.get("uses", "").startswith("actions/checkout@")) + self.assertIs(checkout["with"].get("persist-credentials"), False) + def test_known_package_and_rc_mirror(self): result = self.run_step("meta", INPUT_PACKAGES=" example ", INPUT_MIRROR="rc") self.assertEqual(result.returncode, 0, result.stderr)