| # This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist |
| # |
| # Copyright 2022-2024, axodotdev |
| # SPDX-License-Identifier: MIT or Apache-2.0 |
| # |
| # CI that: |
| # |
| # * checks for a Git Tag that looks like a release |
| # * builds artifacts with dist (archives, installers, hashes) |
| # * uploads those artifacts to temporary workflow zip |
| # * on success, uploads the artifacts to a GitHub Release |
| # |
| # Note that the GitHub Release will be created with a generated |
| # title/body based on your changelogs. |
| |
| name: Release |
| permissions: {} |
| |
| # This task will run whenever you workflow_dispatch with a tag that looks like a version |
| # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. |
| # Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where |
| # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION |
| # must be a Cargo-style SemVer Version (must have at least major.minor.patch). |
| # |
| # If PACKAGE_NAME is specified, then the announcement will be for that |
| # package (erroring out if it doesn't have the given version or isn't dist-able). |
| # |
| # If PACKAGE_NAME isn't specified, then the announcement will be for all |
| # (dist-able) packages in the workspace with that version (this mode is |
| # intended for workspaces with only one dist-able package, or with all dist-able |
| # packages versioned/released in lockstep). |
| # |
| # If you push multiple tags at once, separate instances of this workflow will |
| # spin up, creating an independent announcement for each one. However, GitHub |
| # will hard limit this to 3 tags per commit, as it will assume more tags is a |
| # mistake. |
| # |
| # If there's a prerelease-style suffix to the version, then the release(s) |
| # will be marked as a prerelease. |
| on: |
| pull_request: |
| workflow_dispatch: |
| inputs: |
| tag: |
| description: Release Tag |
| required: true |
| default: dry-run |
| type: string |
| |
| env: |
| UV_LOCKED: 1 |
| # Release actions must not consume shared GitHub Actions caches. |
| ACTIONS_CACHE_MODE: none |
| CARGO_DIST_VERSION: "0.31.0" |
| CARGO_DIST_CHECKSUM: "cd355dab0b4c02fb59038fef87655550021d07f45f1d82f947a34ef98560abb8" |
| |
| jobs: |
| release-gate: |
| # N.B. This name should not change, it is used for downstream checks. |
| name: release-gate |
| if: ${{ github.event_name == 'workflow_dispatch' && inputs.tag != 'dry-run' }} |
| runs-on: ${{ github.repository == 'astral-sh/ruff' && 'github-ubuntu-24.04-x86_64-4' || 'ubuntu-latest' }} |
| # This environment requires a 2-factor approval, i.e., the workflow must be approved by another |
| # team member. GitHub fires approval events on every job that deploys to an environment, so we |
| # have a dedicated environment for this purpose instead of using the `release` environment. |
| # We use a GitHub App with a deployment protection rule webhook to ensure that the `release` |
| # environment is only approved when the `release-gate` job succeeds. |
| environment: |
| name: release-gate |
| deployment: true |
| steps: |
| - run: echo "Release approved" |
| |
| # Run 'dist plan' (or host) to determine what tasks we need to do |
| plan: |
| permissions: |
| "contents": "read" |
| runs-on: "depot-ubuntu-24.04-4" |
| outputs: |
| val: ${{ steps.plan.outputs.manifest }} |
| tag: ${{ (inputs.tag != 'dry-run' && inputs.tag) || '' }} |
| tag-flag: ${{ inputs.tag && inputs.tag != 'dry-run' && format('--tag={0}', inputs.tag) || '' }} |
| publishing: ${{ inputs.tag && inputs.tag != 'dry-run' }} |
| env: |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
| submodules: recursive |
| - name: Install dist |
| shell: bash |
| run: | |
| curl --proto '=https' --tlsv1.2 -LsSf "https://github.com/axodotdev/cargo-dist/releases/download/v${CARGO_DIST_VERSION}/cargo-dist-x86_64-unknown-linux-gnu.tar.xz" -o /tmp/cargo-dist.tar.xz |
| echo "${CARGO_DIST_CHECKSUM} /tmp/cargo-dist.tar.xz" | sha256sum -c - |
| tar -xf /tmp/cargo-dist.tar.xz -C /tmp |
| install /tmp/cargo-dist-x86_64-unknown-linux-gnu/dist ~/.cargo/bin/ |
| - name: Cache dist |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: cargo-dist-cache |
| path: ~/.cargo/bin/dist |
| # sure would be cool if github gave us proper conditionals... |
| # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible |
| # functionality based on whether this is a pull_request, and whether it's from a fork. |
| # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* |
| # but also really annoying to build CI around when it needs secrets to work right.) |
| - id: plan |
| shell: bash |
| run: | |
| dist_args=(plan) |
| if [[ -n "$DIST_TAG_FLAG" ]]; then |
| dist_args=(host --steps=create "$DIST_TAG_FLAG") |
| fi |
| dist "${dist_args[@]}" --output-format=json > plan-dist-manifest.json |
| echo "dist ran successfully" |
| cat plan-dist-manifest.json |
| echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" |
| env: |
| DIST_TAG_FLAG: ${{ inputs.tag && inputs.tag != 'dry-run' && format('--tag={0}', inputs.tag) || '' }} |
| - name: "Upload dist-manifest.json" |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: artifacts-plan-dist-manifest |
| path: plan-dist-manifest.json |
| |
| custom-build-binaries: |
| needs: |
| - plan |
| if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }} |
| uses: $/.github/workflows/build-binaries.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| |
| custom-build-docker: |
| needs: |
| - plan |
| - release-gate |
| if: ${{ always() && needs.plan.result == 'success' && (needs.release-gate.result == 'success' || needs.release-gate.result == 'skipped') && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run') }} |
| uses: $/.github/workflows/build-docker.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| permissions: |
| "attestations": "write" |
| "contents": "read" |
| "id-token": "write" |
| "packages": "write" |
| |
| custom-build-wasm: |
| needs: |
| - plan |
| if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }} |
| uses: $/.github/workflows/build-wasm.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| |
| # The custom binary jobs produce checksum sidecars outside cargo-dist. |
| # Include them in a local manifest before generating the installers. |
| generate-checksum-manifest: |
| needs: |
| - plan |
| - custom-build-binaries |
| if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' || inputs.tag == 'dry-run' }} |
| permissions: |
| "contents": "read" |
| runs-on: "depot-ubuntu-24.04-4" |
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
| submodules: recursive |
| - name: "Install uv" |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 |
| with: |
| version: "0.12.11" |
| enable-cache: false |
| - name: Install cached dist |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: cargo-dist-cache |
| path: ~/.cargo/bin/ |
| - run: chmod +x ~/.cargo/bin/dist |
| - name: Fetch local artifacts |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| pattern: artifacts-* |
| path: target/distrib/ |
| merge-multiple: true |
| - name: Generate local dist manifest |
| shell: bash |
| env: |
| DIST_TAG_FLAG: ${{ needs.plan.outputs.tag-flag }} |
| run: | |
| dist_args=() |
| if [[ -n "$DIST_TAG_FLAG" ]]; then |
| dist_args+=("$DIST_TAG_FLAG") |
| fi |
| temp_manifest=target/local-dist-manifest.json.tmp |
| dist manifest "${dist_args[@]}" --output-format=json --no-local-paths --artifacts=local > "$temp_manifest" |
| uv run --locked scripts/patch-dist-manifest-checksums.py --manifest "$temp_manifest" --artifacts-dir target/distrib |
| mv "$temp_manifest" target/distrib/local-dist-manifest.json |
| - name: Upload synthesized local dist manifest |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: artifacts-build-local-manifest |
| path: target/distrib/local-dist-manifest.json |
| |
| # Build and package all the platform-agnostic(ish) things |
| build-global-artifacts: |
| needs: |
| - plan |
| - custom-build-binaries |
| - custom-build-docker |
| - custom-build-wasm |
| - generate-checksum-manifest |
| permissions: |
| "contents": "read" |
| runs-on: "depot-ubuntu-24.04-4" |
| env: |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json |
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
| submodules: recursive |
| - name: Install cached dist |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: cargo-dist-cache |
| path: ~/.cargo/bin/ |
| - run: chmod +x ~/.cargo/bin/dist |
| # Get all the local artifacts for the global tasks to use (for e.g. checksums) |
| - name: Fetch local artifacts |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| pattern: artifacts-* |
| path: target/distrib/ |
| merge-multiple: true |
| - id: cargo-dist |
| shell: bash |
| run: | |
| dist_args=() |
| if [[ -n "$DIST_TAG_FLAG" ]]; then |
| dist_args+=("$DIST_TAG_FLAG") |
| fi |
| dist build "${dist_args[@]}" --output-format=json "--artifacts=global" > dist-manifest.json |
| echo "dist ran successfully" |
| |
| # Parse out what we just built and upload it to scratch storage |
| echo "paths<<EOF" >> "$GITHUB_OUTPUT" |
| jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" |
| echo "EOF" >> "$GITHUB_OUTPUT" |
| |
| cp dist-manifest.json "$BUILD_MANIFEST_NAME" |
| env: |
| DIST_TAG_FLAG: ${{ needs.plan.outputs.tag-flag }} |
| - name: "Upload artifacts" |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| name: artifacts-build-global |
| path: | |
| ${{ steps.cargo-dist.outputs.paths }} |
| ${{ env.BUILD_MANIFEST_NAME }} |
| # Determines if we should publish/announce |
| host: |
| needs: |
| - plan |
| - custom-build-binaries |
| - custom-build-docker |
| - custom-build-wasm |
| - build-global-artifacts |
| - generate-checksum-manifest |
| # Require checksums for all built archives before publishing. Other build jobs may be skipped. |
| if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && needs.generate-checksum-manifest.result == 'success' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') && (needs.custom-build-wasm.result == 'skipped' || needs.custom-build-wasm.result == 'success') }} |
| permissions: |
| "contents": "read" |
| env: |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| runs-on: "depot-ubuntu-24.04-4" |
| outputs: |
| val: ${{ steps.host.outputs.manifest }} |
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
| submodules: recursive |
| - name: Install cached dist |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| name: cargo-dist-cache |
| path: ~/.cargo/bin/ |
| - run: chmod +x ~/.cargo/bin/dist |
| # Fetch artifacts from scratch-storage |
| - name: Fetch artifacts |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| pattern: artifacts-* |
| path: target/distrib/ |
| merge-multiple: true |
| # This is a harmless no-op for GitHub Releases, hosting for that happens in "announce" |
| - id: host |
| shell: bash |
| run: | |
| dist_args=() |
| if [[ -n "$DIST_TAG_FLAG" ]]; then |
| dist_args+=("$DIST_TAG_FLAG") |
| fi |
| dist host "${dist_args[@]}" --steps=upload --steps=release --output-format=json > dist-manifest.json |
| echo "artifacts uploaded and released successfully" |
| cat dist-manifest.json |
| echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" |
| env: |
| DIST_TAG_FLAG: ${{ needs.plan.outputs.tag-flag }} |
| - name: "Upload dist-manifest.json" |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f |
| with: |
| # Overwrite the previous copy |
| name: artifacts-dist-manifest |
| path: dist-manifest.json |
| |
| custom-publish-pypi: |
| needs: |
| - plan |
| - host |
| - release-gate |
| if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} |
| uses: $/.github/workflows/publish-pypi.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| # publish jobs get escalated permissions |
| permissions: |
| "id-token": "write" |
| "packages": "write" |
| |
| custom-publish-wasm: |
| needs: |
| - plan |
| - host |
| - release-gate |
| if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} |
| uses: $/.github/workflows/publish-wasm.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| # publish jobs get escalated permissions |
| permissions: |
| "contents": "read" |
| "id-token": "write" |
| "packages": "write" |
| |
| custom-publish-crates: |
| needs: |
| - plan |
| - host |
| - release-gate |
| - custom-publish-pypi # DIRTY: see #16989 |
| - custom-publish-wasm # DIRTY: see #16989 |
| if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} |
| uses: $/.github/workflows/publish-crates.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| # publish jobs get escalated permissions |
| permissions: |
| "contents": "read" |
| "id-token": "write" |
| |
| # Create a GitHub Release while uploading all files to it |
| announce: |
| needs: |
| - plan |
| - host |
| - release-gate |
| - custom-publish-pypi |
| - custom-publish-wasm |
| # use "always() && ..." to allow us to wait for all publish jobs while |
| # still allowing individual publish jobs to skip themselves (for prereleases). |
| # "host" however must run to completion, no skipping allowed! |
| # `custom-publish-crates` is intentionally not a dependency because a crates.io outage should |
| # not block the rest of the release. |
| if: ${{ always() && needs.host.result == 'success' && needs.release-gate.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') && (needs.custom-publish-wasm.result == 'skipped' || needs.custom-publish-wasm.result == 'success') }} |
| runs-on: "depot-ubuntu-24.04-4" |
| environment: |
| name: release |
| permissions: |
| "attestations": "write" |
| "contents": "read" |
| "id-token": "write" |
| steps: |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd |
| with: |
| persist-credentials: false |
| submodules: recursive |
| # Create a GitHub Release while uploading all files to it |
| - name: "Download GitHub Artifacts" |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c |
| with: |
| pattern: artifacts-* |
| path: artifacts |
| merge-multiple: true |
| - name: Cleanup |
| run: | |
| # Remove the granular manifests |
| rm -f artifacts/*-dist-manifest.json |
| - name: Attest |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 |
| with: |
| subject-path: | |
| artifacts/*.json |
| artifacts/*.sh |
| artifacts/*.ps1 |
| artifacts/*.zip |
| artifacts/*.tar.gz |
| - name: Get release token |
| id: release-token |
| uses: open-security-tools/ost-simple-sts@974a63a2daaaa40f7c6dec40d334f3da4421469e |
| with: |
| exchange-url: ${{ secrets.STS_API_URL }}/exchange |
| audience: ${{ secrets.STS_API_URL }} |
| repository: astral-sh/ruff |
| # Workflow permission is required to create a release tag after `main` advances across workflow changes. |
| permissions: | |
| contents: write |
| workflows: write |
| - name: Create GitHub Release |
| env: |
| GH_TOKEN: ${{ steps.release-token.outputs.token }} |
| PRERELEASE_FLAG: "${{ fromJson(needs.host.outputs.val).announcement_is_prerelease && '--prerelease' || '' }}" |
| ANNOUNCEMENT_TITLE: "${{ fromJson(needs.host.outputs.val).announcement_title }}" |
| ANNOUNCEMENT_BODY: "${{ fromJson(needs.host.outputs.val).announcement_github_body }}" |
| RELEASE_COMMIT: "${{ github.sha }}" |
| NEEDS_PLAN_OUTPUTS_TAG: ${{ needs.plan.outputs.tag }} |
| run: | |
| # Write and read notes from a file to avoid quoting breaking things |
| echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt |
| |
| gh release create "${NEEDS_PLAN_OUTPUTS_TAG}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* |
| |
| custom-notify-dependents: |
| needs: |
| - plan |
| - announce |
| uses: $/.github/workflows/notify-dependents.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| |
| custom-publish-docs: |
| needs: |
| - plan |
| - announce |
| uses: $/.github/workflows/publish-docs.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| permissions: |
| "contents": "read" |
| |
| custom-publish-playground: |
| needs: |
| - plan |
| - announce |
| uses: $/.github/workflows/publish-playground.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| |
| custom-publish-versions: |
| needs: |
| - plan |
| - announce |
| uses: $/.github/workflows/publish-versions.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| |
| custom-publish-mirror: |
| needs: |
| - plan |
| - announce |
| uses: $/.github/workflows/publish-mirror.yml |
| with: |
| plan: ${{ needs.plan.outputs.val }} |
| secrets: inherit # zizmor: ignore[secrets-inherit] |
| permissions: |
| "contents": "read" |