# This file was autogenerated by dist: https://opensource.axo.dev/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 {{%- if "axodotdev" in hosting_providers %}} # * on success, uploads the artifacts to Axo Releases and makes an Announcement {{%- endif %}} {{%- if "github" in hosting_providers %}} # * on success, uploads the artifacts to a GitHub Release {{%- if create_release %}} # # Note that the GitHub Release will be created with a generated # title/body based on your changelogs. {{%- else %}} # # Note that a GitHub Release with this tag is assumed to exist as a draft # with the appropriate title/body, and will be undrafted for you. {{%- endif %}} {{%- endif %}} name: Release {{%- if root_permissions %}} permissions: {{%- for perm_name in root_permissions %}} {{{ perm_name }}}: {{{ root_permissions[perm_name] }}} {{%- endfor %}} {{%- endif %}} {{%- if release_branch %}} # This task will run whenever you push to {{{ release_branch }}} {{%- else %}} {{%- if dispatch_releases %}} # This task will run whenever you workflow_dispatch with a tag that looks like a version {{%- else %}} # This task will run whenever you push a git tag that looks like a version {{%- endif %}} # 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. {{%- endif %}} on: {{%- if pr_run_mode != "skip" %}} pull_request: {{%- endif %}} {{%- if dispatch_releases %}} workflow_dispatch: inputs: tag: description: Release Tag required: true default: dry-run type: string {{%- elif release_branch %}} push: branches: - {{{ release_branch }}} # don't let multiple instances of this run at the same time on the release branch concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} {{%- else %}} push: tags: - '{{%- if tag_namespace %}}{{{ tag_namespace | safe }}}{{%- endif %}}**[0-9]+.[0-9]+.[0-9]+*' {{%- endif %}} jobs: # Run 'dist plan' (or host) to determine what tasks we need to do plan: runs-on: {{{ global_task.runner }}} outputs: val: ${{ steps.plan.outputs.manifest }} {{%- if dispatch_releases %}} 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' }} {{%- elif release_branch %}} tag: ${{ steps.plan.outputs.tag }} tag-flag: ${{ steps.plan.outputs.tag-flag }} publishing: ${{ !github.event.pull_request }} {{%- else %}} tag: ${{ !github.event.pull_request && github.ref_name || '' }} tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} publishing: ${{ !github.event.pull_request }} {{%- endif %}} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} {{%- if "axodotdev" in hosting_providers %}} AXO_RELEASES_TOKEN: ${{ secrets.AXO_RELEASES_TOKEN }} {{%- endif %}} steps: - uses: actions/checkout@v4 with: submodules: recursive {{%- if rust_version %}} - name: Install Rust run: rustup update {{{ rust_version }}} --no-self-update && rustup default {{{ rust_version }}} {{%- endif %}} - name: Install dist # we specify bash to get pipefail; it guards against the `curl` command # failing. otherwise `sh` won't catch that `curl` returned non-0 shell: bash run: {{{ install_dist_sh }}} - name: Cache dist uses: actions/upload-artifact@v4 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 run: | dist {{%- if dispatch_releases %}} ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) {{%- elif release_branch %}} ${{ (!github.event.pull_request && 'host --steps=create --tag=timestamp --force-tag') {{%- else %}} ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) {{%- endif %}} {{%- if "axodotdev" in hosting_providers %}} || (env.AXO_RELEASES_TOKEN && 'host --steps=check') {{%- endif %}} {{{- " || 'plan' }} --output-format=json > plan-dist-manifest.json" | safe }}} echo "dist ran successfully" cat plan-dist-manifest.json echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" {{%- if release_branch %}} echo "tag=$(jq --raw-output ".announcement_tag" plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" echo "tag-flag=--tag=$(jq --raw-output ".announcement_tag" plan-dist-manifest.json) --force-tag" >> "$GITHUB_OUTPUT" {{%- endif %}} - name: "Upload dist-manifest.json" uses: actions/upload-artifact@v4 with: name: artifacts-plan-dist-manifest path: plan-dist-manifest.json {{%- for job in plan_jobs %}} custom-{{{ job.name|safe }}}: uses: ./.github/workflows/{{{ job.name|safe }}}.yml secrets: inherit {{%- if job.permissions %}} permissions: {{%- for perm_name in job.permissions %}} {{{ perm_name }}}: {{{ job.permissions[perm_name] }}} {{%- endfor %}} {{%- endif %}} {{%- endfor %}} {{%- if build_local_artifacts %}} # Build and packages all the platform-specific things build-local-artifacts: name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) # Let the initial task tell us to not run (currently very blunt) needs: - plan {{%- for job in plan_jobs %}} - custom-{{{ job.name|safe }}} {{%- endfor %}} if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') {{%- if dispatch_releases %}} || inputs.tag == 'dry-run' {{%- endif %}} {{{- " }}" | safe }}} strategy: fail-fast: {{{ fail_fast }}} # Target platforms/runners are computed by dist in create-release. # Each member of the matrix has the following arguments: # # - runner: the github runner # - dist-args: cli flags to pass to dist # - install-dist: expression to run to install dist on the runner # # Typically there will be: # - 1 "global" task that builds universal installers # - N "local" tasks that build each platform's binaries and platform-specific installers matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} runs-on: ${{ matrix.runner }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json {{%- if ssldotcom_windows_sign %}} SSLDOTCOM_USERNAME: ${{ secrets.SSLDOTCOM_USERNAME }} SSLDOTCOM_PASSWORD: ${{ secrets.SSLDOTCOM_PASSWORD }} SSLDOTCOM_CREDENTIAL_ID: ${{ secrets.SSLDOTCOM_CREDENTIAL_ID }} SSLDOTCOM_TOTP_SECRET: ${{ secrets.SSLDOTCOM_TOTP_SECRET }} {{%- endif %}} {{%- if macos_sign %}} CODESIGN_CERTIFICATE: ${{ secrets.CODESIGN_CERTIFICATE }} CODESIGN_CERTIFICATE_PASSWORD: ${{ secrets.CODESIGN_CERTIFICATE_PASSWORD }} CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }} {{%- endif %}} steps: - name: enable windows longpaths run: | git config --global core.longpaths true - uses: actions/checkout@v4 with: submodules: recursive {{%- if rust_version %}} - name: Use rustup to set correct Rust version run: rustup update {{{ rust_version }}} --no-self-update && rustup default {{{ rust_version }}} {{%- endif %}} {{%- for step in github_build_setup %}} - name: {{{ step.name }}} {{%- if step.id is not undefined %}} id: {{{ step.id }}} {{%- endif %}} {{%- if step.uses is not undefined %}} uses: {{{ step.uses }}} {{%- endif %}} {{%- if step.if is not undefined %}} if: {{{ step.if }}} {{%- endif %}} {{%- if step.run is not undefined %}} {{%- if step.run is multiline %}} run: | {{{ step.run|indent(10) }}} {{%- else %}} run: {{{ step.run }}} {{%- endif %}} {{%- endif %}} {{%- if step.working_directory is not undefined %}} working-directory: {{{ step.working_directory }}} {{%- endif %}} {{%- if step.shell is not undefined %}} shell: {{{ step.shell }}} {{%- endif %}} {{%- if not step.with is empty %}} with: {{%- for (var,value) in step.with|items %}} {{%- if value is mapping %}} {{{ var }}}: {{%- for (var,value) in value|items %}} {{{ var }}}: {{{ value }}} {{%- endfor %}} {{%- else %}} {{{ var }}}: {{{ value }}} {{%- endif %}} {{%- endfor %}} {{%- endif %}} {{%- if not step.env is empty %}} env: {{%- for (var,value) in step.env|items %}} {{{ var }}}: {{{ value }}} {{%- endfor %}} {{%- endif %}} {{%- if step.continue_on_error is not undefined %}} continue-on-error: {{{ step.continue_on_error }}} {{%- endif %}} {{%- if step.timeout_minutes is not undefined %}} timeout-minutes: {{{ step.timeout_minutes }}} {{%- endif %}} {{%- endfor %}} {{%- if cache_builds %}} - uses: swatinem/rust-cache@v2 with: key: ${{ join(matrix.targets, '-') }} cache-provider: ${{ matrix.cache_provider }} {{%- endif %}} - name: Install dist run: ${{ matrix.install_dist }} # Get the dist-manifest - name: Fetch local artifacts uses: actions/download-artifact@v4 with: pattern: artifacts-* path: target/distrib/ merge-multiple: true - name: Install dependencies run: | ${{ matrix.packages_install }} - name: Build artifacts run: | # Actually do builds and make zips and whatnot dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json echo "dist ran successfully" {{%- if github_attestations %}} - name: Attest uses: actions/attest-build-provenance@v1 with: subject-path: "target/distrib/*${{ join(matrix.targets, ', ') }}*" {{%- endif %}} - id: cargo-dist name: Post-build # We force bash here just because github makes it really hard to get values up # to "real" actions without writing to env-vars, and writing to env-vars has # inconsistent syntax between shell and powershell. shell: bash run: | # Parse out what we just built and upload it to scratch storage echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" cp dist-manifest.json "$BUILD_MANIFEST_NAME" - name: "Upload artifacts" uses: actions/upload-artifact@v4 with: name: artifacts-build-local-${{ join(matrix.targets, '_') }} path: | ${{ steps.cargo-dist.outputs.paths }} ${{ env.BUILD_MANIFEST_NAME }} {{%- endif %}} {{%- for job in local_artifacts_jobs %}} custom-{{{ job.name|safe }}}: needs: - plan {{%- for job in plan_jobs %}} - custom-{{{ job.name|safe }}} {{%- endfor %}} if: ${{ needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload' {{%- if dispatch_releases %}} || inputs.tag == 'dry-run' {{%- endif %}} {{{- " }}" | safe }}} uses: ./.github/workflows/{{{ job.name|safe }}}.yml with: plan: ${{ needs.plan.outputs.val }} secrets: inherit {{%- if job.permissions %}} permissions: {{%- for perm_name in job.permissions %}} {{{ perm_name }}}: {{{ job.permissions[perm_name] }}} {{%- endfor %}} {{%- endif %}} {{%- endfor %}} # Build and package all the platform-agnostic(ish) things build-global-artifacts: needs: - plan {{%- if build_local_artifacts %}} - build-local-artifacts {{%- endif %}} {{%- for job in local_artifacts_jobs %}} - custom-{{{ job.name|safe }}} {{%- endfor %}} runs-on: {{{ global_task.runner }}} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json steps: - uses: actions/checkout@v4 with: submodules: recursive {{%- if rust_version %}} - name: Install Rust run: rustup update {{{ rust_version }}} --no-self-update && rustup default {{{ rust_version }}} {{%- endif %}} - name: Install cached dist uses: actions/download-artifact@v4 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@v4 with: pattern: artifacts-* path: target/distrib/ merge-multiple: true - id: cargo-dist shell: bash run: | dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json {{{ global_task.dist_args }}} > dist-manifest.json echo "dist ran successfully" # Parse out what we just built and upload it to scratch storage echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" cp dist-manifest.json "$BUILD_MANIFEST_NAME" - name: "Upload artifacts" uses: actions/upload-artifact@v4 with: name: artifacts-build-global path: | ${{ steps.cargo-dist.outputs.paths }} ${{ env.BUILD_MANIFEST_NAME }} {{%- for job in global_artifacts_jobs %}} custom-{{{ job.name|safe }}}: needs: - plan {{%- if build_local_artifacts %}} - build-local-artifacts {{%- endif %}} {{%- for job in local_artifacts_jobs %}} - custom-{{{ job.name|safe }}} {{%- endfor %}} uses: ./.github/workflows/{{{ job.name|safe }}}.yml with: plan: ${{ needs.plan.outputs.val }} secrets: inherit {{%- if job.permissions %}} permissions: {{%- for perm_name in job.permissions %}} {{{ perm_name }}}: {{{ job.permissions[perm_name] }}} {{%- endfor %}} {{%- endif %}} {{%- endfor %}} {{%- if "axodotdev" in hosting_providers %}} # Uploads the artifacts to Axo Releases and tentatively creates Releases for them. # This makes perma URLs like /v1.0.0/ live for subsequent publish steps to use, but # leaves them "disconnected" from the release history (for the purposes of # "list the releases" or "give me the latest releases"). # # If all the subsequent "publish" steps succeed, the "announce" job will "connect" # the releases and concepts like "latest" will be updated. Otherwise you're hopefully # in a decent position to roll back the release without anyone noticing it! # This is imperfect with things like "publish to crates.io" being irreversible, but # at worst you're in a better position to yank the version with minimum disruption. {{%- else %}} # Determines if we should publish/announce {{%- endif %}} host: needs: - plan {{%- if build_local_artifacts %}} - build-local-artifacts {{%- endif %}} {{%- for job in local_artifacts_jobs %}} - custom-{{{ job.name|safe }}} {{%- endfor %}} - build-global-artifacts {{%- for job in global_artifacts_jobs %}} - custom-{{{ job.name|safe }}} {{%- endfor %}} # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') {{%- for job in global_artifacts_jobs %}} && (needs.custom-{{{ job.name|safe }}}.result == 'skipped' || needs.custom-{{{ job.name|safe }}}.result == 'success') {{%- endfor %}} {{%- if build_local_artifacts %}} && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') {{%- endif %}} {{%- for job in local_artifacts_jobs %}} && (needs.custom-{{{ job.name|safe }}}.result == 'skipped' || needs.custom-{{{ job.name|safe }}}.result == 'success') {{%- endfor %}} {{{- " }}" | safe }}} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} {{%- if "axodotdev" in hosting_providers %}} AXO_RELEASES_TOKEN: ${{ secrets.AXO_RELEASES_TOKEN }} {{%- endif %}} runs-on: {{{ global_task.runner }}} outputs: val: ${{ steps.host.outputs.manifest }} steps: - uses: actions/checkout@v4 with: submodules: recursive {{%- if rust_version %}} - name: Install Rust run: rustup update {{{ rust_version }}} --no-self-update && rustup default {{{ rust_version }}} {{%- endif %}} - name: Install cached dist uses: actions/download-artifact@v4 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@v4 with: pattern: artifacts-* path: target/distrib/ merge-multiple: true {{%- if "axodotdev" in hosting_providers %}} # Upload files to Axo Releases and create the Releases {{%- endif %}} {{%- if "github" in hosting_providers and release_phase == "announce" %}} # This is a harmless no-op for GitHub Releases, hosting for that happens in "announce" {{%- endif %}} - id: host shell: bash run: | dist host ${{ needs.plan.outputs.tag-flag }} --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" - name: "Upload dist-manifest.json" uses: actions/upload-artifact@v4 with: # Overwrite the previous copy name: artifacts-dist-manifest path: dist-manifest.json {{%- if "github" in hosting_providers and release_phase == "host" %}} {{% include 'ci/github/partials/publish_github.yml' %}} {{%- endif %}} {{%- for job in host_jobs %}} custom-{{{ job.name|safe }}}: needs: - plan {{%- if build_local_artifacts %}} - build-local-artifacts {{%- endif %}} - build-global-artifacts {{%- for job in global_artifacts_jobs %}} - custom-{{{ job.name|safe }}} {{%- endfor %}} uses: ./.github/workflows/{{{ job.name|safe }}}.yml with: plan: ${{ needs.plan.outputs.val }} secrets: inherit {{%- if job.permissions %}} permissions: {{%- for perm_name in job.permissions %}} {{{ perm_name }}}: {{{ job.permissions[perm_name] }}} {{%- endfor %}} {{%- endif %}} {{%- endfor %}} {{%- if 'homebrew' in publish_jobs and tap %}} {{% include 'ci/github/partials/publish_homebrew.yml' %}} {{%- endif %}} {{%- if 'npm' in publish_jobs %}} {{% include 'ci/github/partials/publish_npm.yml' %}} {{%- endif %}} {{%- for job in user_publish_jobs %}} custom-{{{ job.name|safe }}}: needs: - plan - host {{%- for job in host_jobs %}} - custom-{{{ job.name|safe }}} {{%- endfor %}} if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} uses: ./.github/workflows/{{{ job.name|safe }}}.yml with: plan: ${{ needs.plan.outputs.val }} secrets: inherit # publish jobs get escalated permissions {{%- if job.permissions %}} permissions: {{%- for perm_name in job.permissions %}} {{{ perm_name }}}: {{{ job.permissions[perm_name] }}} {{%- endfor %}} {{%- endif %}} {{%- endfor %}} {{#- being extremely Normal about whitespace/newlines -#}} {{{- " " | safe }}} {{%- if "axodotdev" in hosting_providers %}} # Create an Announcement for all the Axo Releases, updating the "latest" release {{%- endif %}} {{%- if "github" in hosting_providers and release_phase == "announce" %}} # Create a GitHub Release while uploading all files to it {{%- endif %}} announce: needs: - plan - host {{%- if 'homebrew' in publish_jobs and tap %}} - publish-homebrew-formula {{%- endif %}} {{%- if 'npm' in publish_jobs %}} - publish-npm {{%- endif %}} {{%- for job in user_publish_jobs %}} - custom-{{{ job.name|safe }}} {{%- endfor %}} {{%- for job in host_jobs %}} - custom-{{{ job.name|safe }}} {{%- endfor %}} # 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! if: ${{ always() && needs.host.result == 'success' {{%- if 'homebrew' in publish_jobs and tap %}} && (needs.publish-homebrew-formula.result == 'skipped' || needs.publish-homebrew-formula.result == 'success') {{%- endif %}} {{%- if 'npm' in publish_jobs %}} && (needs.publish-npm.result == 'skipped' || needs.publish-npm.result == 'success') {{%- endif %}} {{%- for job in user_publish_jobs %}} && (needs.custom-{{{ job.name|safe }}}.result == 'skipped' || needs.custom-{{{ job.name|safe }}}.result == 'success') {{%- endfor %}} {{{- " }}" | safe }}} runs-on: {{{ global_task.runner }}} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} {{%- if "axodotdev" in hosting_providers %}} AXO_RELEASES_TOKEN: ${{ secrets.AXO_RELEASES_TOKEN }} {{%- endif %}} steps: - uses: actions/checkout@v4 with: submodules: recursive {{%- if "axodotdev" in hosting_providers %}} {{%- if rust_version %}} - name: Install Rust run: rustup update {{{ rust_version }}} --no-self-update && rustup default {{{ rust_version }}} {{%- endif %}} - name: Install cached dist uses: actions/download-artifact@v4 with: name: cargo-dist-cache path: ~/.cargo/bin/ - run: chmod +x ~/.cargo/bin/dist - name: Fetch Axo Artifacts uses: actions/download-artifact@v4 with: pattern: artifacts-* path: target/distrib/ merge-multiple: true - name: Announce Axo Releases run: | dist host --steps=announce ${{ needs.plan.outputs.tag-flag }} {{%- endif %}} {{%- if "github" in hosting_providers and release_phase == "announce" %}} {{% include 'ci/github/partials/publish_github.yml' %}} {{%- endif %}} {{%- for job in post_announce_jobs %}} custom-{{{ job.name|safe }}}: needs: - plan - announce uses: ./.github/workflows/{{{ job.name|safe }}}.yml with: plan: ${{ needs.plan.outputs.val }} secrets: inherit {{%- if job.permissions %}} permissions: {{%- for perm_name in job.permissions %}} {{{ perm_name }}}: {{{ job.permissions[perm_name] }}} {{%- endfor %}} {{%- endif %}} {{%- endfor %}}