dependency_bump #37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Dependency Automation | |
| # Prepares a submodule bump when mlx-swift or mlx-swift-lm cuts a release, and stops | |
| # at a pushed branch rather than opening a pull request. | |
| # | |
| # Opening the PR from a workflow needs a personal access token, because GitHub does | |
| # not start workflow runs for events raised by GITHUB_TOKEN. A bot-opened PR would | |
| # therefore arrive with no checks at all — permanently pending, never green — and this | |
| # repository gates releases on CI concluding successfully (see release.yml). A branch | |
| # is the honest stopping point: opening the PR yourself takes one click, and CI then | |
| # runs normally because the event is yours. | |
| # | |
| # That a human sees the bump before it merges is a feature. Bumps here have needed a | |
| # pointer check, an umbrella build and a smoke test to be trustworthy; the automation | |
| # does the mechanical part and leaves the judgement. | |
| on: | |
| repository_dispatch: | |
| types: [dependency_bump] | |
| permissions: | |
| contents: write | |
| jobs: | |
| bump-dependencies: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 0 | |
| # client_payload is attacker-controlled in principle — anything able to dispatch | |
| # to this repository chooses these strings — and they end up in shell and in a | |
| # ref name. Validate them here and pass them onward through the environment | |
| # rather than interpolating ${{ }} into a run block, where a crafted tag would | |
| # be executed rather than compared. | |
| - name: Validate dispatch payload | |
| env: | |
| PAYLOAD_SOURCE_REPO: ${{ github.event.client_payload.source_repo }} | |
| PAYLOAD_NEW_TAG: ${{ github.event.client_payload.new_tag }} | |
| run: | | |
| set -euo pipefail | |
| case "$PAYLOAD_SOURCE_REPO" in | |
| mlx-swift|mlx-swift-lm) ;; | |
| *) | |
| echo "::error::unexpected source_repo '$PAYLOAD_SOURCE_REPO' — expected mlx-swift or mlx-swift-lm" | |
| exit 1 | |
| ;; | |
| esac | |
| if ! printf '%s' "$PAYLOAD_NEW_TAG" | grep -Eq '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'; then | |
| echo "::error::refusing tag '$PAYLOAD_NEW_TAG' — not a plain tag name" | |
| exit 1 | |
| fi | |
| { | |
| echo "SOURCE_REPO=$PAYLOAD_SOURCE_REPO" | |
| echo "NEW_TAG=$PAYLOAD_NEW_TAG" | |
| } >> "$GITHUB_ENV" | |
| # Both dependencies are `.package(path: "./…")` in Package.swift, backed by git | |
| # submodules, so bumping either one is a pointer move. `swift package update` | |
| # does nothing for a path dependency — SwiftPM takes whatever is on disk — which | |
| # is why the mlx-swift branch of the previous version of this workflow could only | |
| # ever have produced an empty commit. | |
| - name: Move submodule to the released tag | |
| run: | | |
| set -euo pipefail | |
| git -C "$SOURCE_REPO" fetch --tags --force origin | |
| if ! git -C "$SOURCE_REPO" rev-parse -q --verify "refs/tags/${NEW_TAG}^{commit}" >/dev/null; then | |
| echo "::error::tag $NEW_TAG does not exist in $SOURCE_REPO" | |
| exit 1 | |
| fi | |
| before=$(git rev-parse "HEAD:$SOURCE_REPO") | |
| git -C "$SOURCE_REPO" checkout --detach "refs/tags/$NEW_TAG" | |
| after=$(git -C "$SOURCE_REPO" rev-parse HEAD) | |
| { | |
| echo "BEFORE_SHA=$before" | |
| echo "AFTER_SHA=$after" | |
| } >> "$GITHUB_ENV" | |
| if [ "$before" = "$after" ]; then | |
| echo "ALREADY_CURRENT=1" >> "$GITHUB_ENV" | |
| fi | |
| - name: Report an already-current submodule and stop | |
| if: env.ALREADY_CURRENT == '1' | |
| run: | | |
| { | |
| echo "### Nothing to bump" | |
| echo | |
| echo "\`$SOURCE_REPO\` is already at \`$NEW_TAG\` (\`${AFTER_SHA:0:7}\`)." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Push the bump branch | |
| if: env.ALREADY_CURRENT != '1' | |
| run: | | |
| set -euo pipefail | |
| branch="auto-update/${SOURCE_REPO}-${NEW_TAG}" | |
| git checkout -B "$branch" | |
| git add "$SOURCE_REPO" | |
| git \ | |
| -c user.name='github-actions[bot]' \ | |
| -c user.email='41898282+github-actions[bot]@users.noreply.github.com' \ | |
| commit -m "chore(deps): bump $SOURCE_REPO to $NEW_TAG | |
| Moves the $SOURCE_REPO submodule from ${BEFORE_SHA:0:7} to ${AFTER_SHA:0:7}, | |
| the commit tagged $NEW_TAG. | |
| Prepared automatically; opened by hand so that CI runs against it." | |
| # The auto-update/* namespace belongs to this workflow, so replacing a branch | |
| # left by an earlier run for the same tag is safe and keeps re-runs idempotent. | |
| git push --force origin "$branch" | |
| echo "BUMP_BRANCH=$branch" >> "$GITHUB_ENV" | |
| - name: Summarise, with a link that opens the pull request | |
| if: env.ALREADY_CURRENT != '1' | |
| run: | | |
| { | |
| echo "### \`$SOURCE_REPO\` → \`$NEW_TAG\` is ready" | |
| echo | |
| echo "Branch \`$BUMP_BRANCH\` pushed, moving the submodule from" | |
| echo "\`${BEFORE_SHA:0:7}\` to \`${AFTER_SHA:0:7}\`." | |
| echo | |
| echo "**[Open the pull request](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/compare/main...${BUMP_BRANCH}?expand=1)**" | |
| echo | |
| echo "No PR is opened here on purpose: GitHub does not start workflow runs" | |
| echo "for events raised by \`GITHUB_TOKEN\`, so a bot-opened PR would never" | |
| echo "get CI. Opening it yourself gets the checks this repository gates" | |
| echo "releases on." | |
| } >> "$GITHUB_STEP_SUMMARY" |