This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

.github: add daily update-pkgs workflow for packages and flake.lock, merged after a farm build

+272
+272
.github/workflows/update-pkgs.yml
··· 1 + name: update-pkgs 2 + 3 + # Daily package and flake.lock updates. Nothing reaches master without a full 4 + # farm build: 5 + # 6 + # 1. update (one job per package): run its passthru.updateScript through 7 + # tools/update-pkgs, build the package on its platform (darwin-only 8 + # packages on macOS), push update/<pkg> and open or refresh its PR. 9 + # lock: `nix flake update`, pushed as update/flake-lock with its own PR. 10 + # 2. verify: for every open update/* PR, one after another (flake.lock 11 + # first), rebase it onto the current master, build all machines with the 12 + # farm (farm.yml) on that commit and squash-merge the PR only if the run 13 + # succeeded. A commit that already has a finished farm run is not built 14 + # again, so a failing PR stays open until its branch or master changes. 15 + # 16 + # One PR at a time because the farm has a fixed endpoint and runs in a single 17 + # concurrency group, where GitHub keeps only one pending run. 18 + # 19 + # Needs "Allow GitHub Actions to create and approve pull requests" in the repo 20 + # settings. Merges made with GITHUB_TOKEN don't trigger push workflows, so the 21 + # farm build of master after merging is dispatched here as well. 22 + 23 + on: 24 + schedule: 25 + - cron: "0 3 * * *" # daily 03:00 UTC 26 + workflow_dispatch: 27 + inputs: 28 + packages: 29 + description: Packages to update, space separated (empty for all) 30 + default: "" 31 + automerge: 32 + description: Merge PRs whose farm build succeeded 33 + type: boolean 34 + default: true 35 + 36 + permissions: 37 + contents: write 38 + pull-requests: write 39 + actions: write # dispatch and watch farm runs 40 + 41 + concurrency: 42 + group: update-pkgs 43 + cancel-in-progress: false 44 + 45 + env: 46 + NIX_CONFIG: | 47 + extra-substituters = https://cache.lassul.us 48 + extra-trusted-public-keys = cache.lassul.us-1:aOMEO5TU6eLvhyPi0jmgjR2sVlGjMq3rK0MeACjTcQ0= 49 + 50 + jobs: 51 + list: 52 + runs-on: ubuntu-latest 53 + outputs: 54 + matrix: ${{ steps.list.outputs.matrix }} 55 + steps: 56 + - uses: actions/checkout@v4 57 + - uses: cachix/install-nix-action@v31 58 + - id: list 59 + env: 60 + PACKAGES: ${{ inputs.packages }} 61 + run: | 62 + names() { 63 + nix eval --json --impure --apply builtins.attrNames --expr \ 64 + "import ./tools/update-pkgs/scripts.nix { flake = toString ./.; host = \"$1\"; }" 65 + } 66 + # Linux packages update and build on ubuntu; the ones that only 67 + # exist for darwin on macOS. 68 + matrix=$(jq -cn --argjson linux "$(names x86_64-linux)" \ 69 + --argjson all "$(names aarch64-darwin)" --arg only "$PACKAGES" ' 70 + ($only | split(" ") | map(select(. != ""))) as $want 71 + | [ $all[] 72 + | select(($want | length) == 0 or (. as $p | $want | index($p))) 73 + | if (. as $p | $linux | index($p)) 74 + then { pkg: ., os: "ubuntu-latest", system: "x86_64-linux" } 75 + else { pkg: ., os: "macos-latest", system: "aarch64-darwin" } 76 + end ]') 77 + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" 78 + echo "$matrix" | jq . 79 + 80 + update: 81 + needs: list 82 + if: needs.list.outputs.matrix != '[]' 83 + name: ${{ matrix.pkg }} 84 + runs-on: ${{ matrix.os }} 85 + strategy: 86 + fail-fast: false 87 + matrix: 88 + include: ${{ fromJSON(needs.list.outputs.matrix) }} 89 + steps: 90 + - uses: actions/checkout@v4 91 + with: 92 + fetch-depth: 0 93 + - uses: cachix/install-nix-action@v31 94 + - env: 95 + PKG: ${{ matrix.pkg }} 96 + SYSTEM: ${{ matrix.system }} 97 + GH_TOKEN: ${{ github.token }} 98 + # nix-update and the update scripts query the GitHub API 99 + GITHUB_TOKEN: ${{ github.token }} 100 + run: | 101 + version() { nix eval --raw ".#packages.$SYSTEM.$PKG.version" 2>/dev/null || echo unknown; } 102 + old=$(version) 103 + 104 + nix run .#update-pkgs -- "$PKG" 105 + if [ -z "$(git status --porcelain)" ]; then 106 + echo "$PKG: no update" 107 + exit 0 108 + fi 109 + 110 + nix build -L --no-link ".#packages.$SYSTEM.$PKG" 111 + 112 + new=$(version) 113 + prefix=pkgs 114 + [ -d "pkgs/$PKG" ] || prefix=tools 115 + if [ "$old" != "$new" ]; then 116 + msg="$prefix.$PKG: $old -> $new" 117 + else 118 + msg="$prefix.$PKG: update" 119 + fi 120 + 121 + branch="update/$PKG" 122 + git config user.name "github-actions[bot]" 123 + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 124 + git switch -c "$branch" 125 + git add -A 126 + git commit -m "$msg" 127 + 128 + # Same change as the branch already carries (e.g. a PR whose farm 129 + # build failed and upstream hasn't moved): keep its commit, so 130 + # verify doesn't build it again. 131 + if git fetch -q origin "$branch" 2>/dev/null && 132 + git diff --quiet FETCH_HEAD HEAD -- "$prefix/$PKG"; then 133 + echo "$PKG: $branch already has this update" 134 + else 135 + git push --force origin "$branch" 136 + fi 137 + 138 + if [ -z "$(gh pr list --head "$branch" --state open --json number --jq '.[].number')" ]; then 139 + gh pr create --base master --head "$branch" --title "$msg" \ 140 + --body "Automated update by the update-pkgs workflow (\`nix run .#update-pkgs -- $PKG\`), package built on $SYSTEM. Merged automatically once the farm has built all machines from this branch." 141 + fi 142 + 143 + lock: 144 + # whole runs only, not manual runs for selected packages 145 + if: inputs.packages == '' 146 + runs-on: ubuntu-latest 147 + steps: 148 + - uses: actions/checkout@v4 149 + with: 150 + fetch-depth: 0 151 + - uses: cachix/install-nix-action@v31 152 + - env: 153 + GH_TOKEN: ${{ github.token }} 154 + GITHUB_TOKEN: ${{ github.token }} 155 + run: | 156 + export NIX_CONFIG="$NIX_CONFIG"$'\n'"access-tokens = github.com=$GITHUB_TOKEN" 157 + nix flake update 2>&1 | tee "$RUNNER_TEMP/update.log" 158 + if git diff --quiet -- flake.lock; then 159 + echo "flake.lock: no update" 160 + exit 0 161 + fi 162 + 163 + msg="flake.lock: update" 164 + branch="update/flake-lock" 165 + git config user.name "github-actions[bot]" 166 + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 167 + git switch -c "$branch" 168 + git commit -m "$msg" -- flake.lock 169 + 170 + # Same lock as the branch already carries: keep its commit, so 171 + # verify doesn't build it again. 172 + if git fetch -q origin "$branch" 2>/dev/null && 173 + git diff --quiet FETCH_HEAD HEAD -- flake.lock; then 174 + echo "flake.lock: $branch already has this update" 175 + else 176 + git push --force origin "$branch" 177 + fi 178 + 179 + { 180 + echo "Automated \`nix flake update\` by the update-pkgs workflow. Merged automatically once the farm has built all machines from this branch." 181 + echo 182 + echo '```' 183 + sed -n '/^• /,$p' "$RUNNER_TEMP/update.log" 184 + echo '```' 185 + } >"$RUNNER_TEMP/body.md" 186 + pr=$(gh pr list --head "$branch" --state open --json number --jq '.[].number') 187 + if [ -z "$pr" ]; then 188 + gh pr create --base master --head "$branch" --title "$msg" --body-file "$RUNNER_TEMP/body.md" 189 + else 190 + gh pr edit "$pr" --body-file "$RUNNER_TEMP/body.md" 191 + fi 192 + 193 + verify: 194 + needs: [update, lock] 195 + if: always() && needs.update.result != 'cancelled' && needs.lock.result != 'cancelled' 196 + runs-on: ubuntu-latest 197 + timeout-minutes: 350 198 + steps: 199 + - uses: actions/checkout@v4 200 + with: 201 + fetch-depth: 0 202 + - env: 203 + GH_TOKEN: ${{ github.token }} 204 + AUTOMERGE: ${{ github.event_name == 'schedule' || inputs.automerge }} 205 + run: | 206 + repo=$GITHUB_REPOSITORY 207 + merged=0 208 + git config user.name "github-actions[bot]" 209 + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" 210 + 211 + # farm run for a head commit: "<id> <status> <conclusion>" or empty 212 + farm_run() { 213 + gh run list -R "$repo" -w farm.yml -b "$1" -e workflow_dispatch -L 20 \ 214 + --json databaseId,headSha,status,conclusion \ 215 + --jq "[.[] | select(.headSha == \"$2\")][0] // empty | \"\(.databaseId) \(.status) \(.conclusion)\"" 216 + } 217 + 218 + # flake.lock first: every package PR is rebased onto the master it 219 + # gets merged into right before its build, so after the lock merge 220 + # the packages are built once, against the new lock, and the final 221 + # farm run on master only hits the cache. 222 + prs=$(gh pr list -R "$repo" --state open --base master --limit 100 \ 223 + --json number,headRefName \ 224 + --jq '[.[] | select(.headRefName | startswith("update/"))] 225 + | sort_by(.headRefName != "update/flake-lock") | .[] 226 + | "\(.number) \(.headRefName)"') 227 + 228 + while read -r pr branch; do 229 + [ -n "$pr" ] || continue 230 + echo "::group::#$pr $branch" 231 + git fetch -q origin master "$branch" 232 + git switch -q -C "$branch" "origin/$branch" 233 + if ! git rebase -q origin/master; then 234 + git rebase --abort 235 + echo "#$pr: conflicts with master, left for the next run" 236 + echo "::endgroup::" 237 + continue 238 + fi 239 + sha=$(git rev-parse HEAD) 240 + if [ "$sha" != "$(git rev-parse "origin/$branch")" ]; then 241 + git push -q --force-with-lease="$branch:origin/$branch" origin "$branch" 242 + fi 243 + 244 + run=$(farm_run "$branch" "$sha") 245 + if [ -z "$run" ]; then 246 + gh workflow run farm.yml -R "$repo" --ref "$branch" 247 + for _ in $(seq 60); do 248 + sleep 10 249 + run=$(farm_run "$branch" "$sha") 250 + [ -n "$run" ] && break 251 + done 252 + fi 253 + id=${run%% *} 254 + if [ -z "$id" ]; then 255 + echo "#$pr: farm run did not start" 256 + echo "::endgroup::" 257 + continue 258 + fi 259 + gh run watch "$id" -R "$repo" --interval 60 >/dev/null || true 260 + conclusion=$(gh run view "$id" -R "$repo" --json conclusion --jq .conclusion) 261 + echo "#$pr: farm run $id on $sha: $conclusion" 262 + if [ "$conclusion" = success ] && [ "$AUTOMERGE" = true ]; then 263 + gh pr merge "$pr" -R "$repo" --squash --delete-branch --match-head-commit "$sha" 264 + merged=$((merged + 1)) 265 + fi 266 + echo "::endgroup::" 267 + done <<<"$prs" 268 + 269 + echo "merged update PRs: $merged" 270 + if [ "$merged" -gt 0 ]; then 271 + gh workflow run farm.yml -R "$repo" --ref master 272 + fi