This repository has no description
0

Configure Feed

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

superconfig / .github / workflows / update-pkgs.yml
11 kB 272 lines
1name: 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 23on: 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 36permissions: 37 contents: write 38 pull-requests: write 39 actions: write # dispatch and watch farm runs 40 41concurrency: 42 group: update-pkgs 43 cancel-in-progress: false 44 45env: 46 NIX_CONFIG: | 47 extra-substituters = https://cache.lassul.us 48 extra-trusted-public-keys = cache.lassul.us-1:aOMEO5TU6eLvhyPi0jmgjR2sVlGjMq3rK0MeACjTcQ0= 49 50jobs: 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