Skip to content

release

release #1979

Workflow file for this run

name: release
permissions: {}
on:
push:
tags:
- "v*.*.*"
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
IS_NIGHTLY: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
# Keep in sync with `docker-publish.yml`.
RUST_PROFILE: dist
RUST_FEATURES: aws-kms,gcp-kms,turnkey,cli,asm-keccak,js-tracer
jobs:
prepare:
name: Prepare release
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: read
outputs:
tag_name: ${{ steps.release_info.outputs.tag_name }}
release_name: ${{ steps.release_info.outputs.release_name }}
changelog: ${{ steps.build_changelog.outputs.changelog }}
skip_release: ${{ steps.existing_release.outputs.skip_release || 'false' }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
fetch-depth: 0
- name: Compute release name and tag
id: release_info
run: |
if [[ ${IS_NIGHTLY} == 'true' ]]; then
printf 'tag_name=%s\n' "nightly-${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
printf 'release_name=%s\n' "Nightly ($(date '+%Y-%m-%d'))" >> "$GITHUB_OUTPUT"
# Find the previous nightly tag for changelog generation,
# sorted by tag creation date (most recent first).
PREV_NIGHTLY=$(git tag -l 'nightly-*' --sort=-creatordate | head -n1)
printf 'from_tag=%s\n' "$PREV_NIGHTLY" >> "$GITHUB_OUTPUT"
else
printf 'tag_name=%s\n' "$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
printf 'release_name=%s\n' "$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
# Find the previous stable release tag (v*.*.*) for changelog generation,
# skipping the current tag so we diff from the last stable release.
PREV_STABLE=$(git tag -l 'v*.*.*' --sort=-v:refname | grep -v "^${GITHUB_REF_NAME}$" | head -n1)
printf 'from_tag=%s\n' "$PREV_STABLE" >> "$GITHUB_OUTPUT"
fi
- name: Check existing nightly release
id: existing_release
if: ${{ env.IS_NIGHTLY == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG_NAME: ${{ steps.release_info.outputs.tag_name }}
shell: bash
run: |
set -euo pipefail
skip_release=false
if is_draft="$(gh release view "$TAG_NAME" --json isDraft --jq .isDraft 2>/dev/null)"; then
if [[ "$is_draft" == "true" ]]; then
echo "Draft nightly release $TAG_NAME already exists; continuing so assets can be uploaded."
else
echo "Published nightly release $TAG_NAME already exists; skipping duplicate nightly release."
skip_release=true
fi
fi
printf 'skip_release=%s\n' "$skip_release" >> "$GITHUB_OUTPUT"
# Creates a `nightly-SHA` tag for this specific nightly
# This tag is used for this specific nightly version's release
# which allows users to roll back. It is also used to build
# the changelog.
- name: Create build-specific nightly tag
if: ${{ env.IS_NIGHTLY == 'true' && steps.existing_release.outputs.skip_release != 'true' }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
TAG_NAME: ${{ steps.release_info.outputs.tag_name }}
with:
script: |
const createTag = require('./.github/scripts/create-tag.js')
await createTag({ github, context }, process.env.TAG_NAME)
- name: Build changelog
id: build_changelog
if: ${{ steps.existing_release.outputs.skip_release != 'true' }}
uses: mikepenz/release-changelog-builder-action@c9bcd8238b6f41e05561348339429d360b1c0247 # v6.2.3
with:
configuration: "./.github/changelog.json"
fromTag: ${{ steps.release_info.outputs.from_tag || '' }}
toTag: ${{ steps.release_info.outputs.tag_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Create the GitHub release as a draft up-front so that all matrix jobs
# only upload assets to an existing draft. With immutable releases
# enabled, an immutable release is sealed when it is published, so all
# assets must be attached before that. Stable releases are then left as
# a draft for a maintainer to publish manually; nightlies are auto-
# published by the `publish-nightly` job at the end of the workflow.
- name: Create draft release
if: ${{ steps.existing_release.outputs.skip_release != 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG_NAME: ${{ steps.release_info.outputs.tag_name }}
RELEASE_NAME: ${{ steps.release_info.outputs.release_name }}
CHANGELOG: ${{ steps.build_changelog.outputs.changelog }}
shell: bash
run: |
set -euo pipefail
if release_json="$(gh release view "$TAG_NAME" --json isDraft 2>/dev/null)"; then
is_draft="$(printf '%s' "$release_json" | grep -o '"isDraft":[^,}]*' | cut -d: -f2 | tr -d ' ')"
if [[ "$is_draft" == "true" ]]; then
echo "Draft release $TAG_NAME already exists; reusing it."
exit 0
fi
echo "::error::Release $TAG_NAME is already published; cannot upload more assets to an immutable release."
exit 1
fi
notes_file="$(mktemp)"
printf '%s' "$CHANGELOG" > "$notes_file"
flags=(--draft --verify-tag --title "$RELEASE_NAME" --notes-file "$notes_file")
if [[ "$IS_NIGHTLY" == "true" ]]; then
flags+=(--prerelease)
fi
gh release create "$TAG_NAME" "${flags[@]}"
release-docker:
name: Release Docker
needs: prepare
if: ${{ needs.prepare.outputs.skip_release != 'true' }}
uses: ./.github/workflows/docker-publish.yml
permissions:
attestations: write
artifact-metadata: write
contents: read
id-token: write
packages: write
with:
tag_name: ${{ needs.prepare.outputs.tag_name }}
# This job uploads assets to the draft release created in `prepare`.
# Stable releases stay as drafts for a maintainer to publish manually;
# nightlies are auto-published by the `publish-nightly` job below. Either
# way, GitHub's immutable-releases setting seals the release at publish.
release:
permissions:
attestations: write
artifact-metadata: write
contents: write
id-token: write
name: release ${{ matrix.target }} (${{ matrix.runner }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 240
needs: prepare
if: ${{ needs.prepare.outputs.skip_release != 'true' }}
strategy:
fail-fast: false
matrix:
include:
# `runner`: GHA runner label
# `target`: Rust build target triple
# `platform` and `arch`: Used in tarball names
# `svm`: target platform to use for the Solc binary: https://github.com/roynalnaruto/svm-rs/blob/84cbe0ac705becabdc13168bae28a45ad2299749/svm-builds/build.rs#L4-L24
# These are pinned to the oldest runner versions to support old libc/SDK versions.
- runner: depot-ubuntu-22.04-16
target: x86_64-unknown-linux-gnu
svm_target_platform: linux-amd64
platform: linux
arch: amd64
- runner: depot-ubuntu-22.04-16
target: x86_64-unknown-linux-musl
svm_target_platform: linux-amd64
platform: alpine
arch: amd64
- runner: depot-ubuntu-22.04-arm-16
target: aarch64-unknown-linux-gnu
svm_target_platform: linux-aarch64
platform: linux
arch: arm64
- runner: depot-ubuntu-22.04-16
target: aarch64-unknown-linux-musl
svm_target_platform: linux-aarch64
platform: alpine
arch: arm64
- runner: macos-14-large
target: x86_64-apple-darwin
svm_target_platform: macosx-amd64
platform: darwin
arch: amd64
- runner: macos-latest-large
target: aarch64-apple-darwin
svm_target_platform: macosx-aarch64
platform: darwin
arch: arm64
- runner: depot-windows-latest-16
target: x86_64-pc-windows-msvc
svm_target_platform: windows-amd64
platform: win32
arch: amd64
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
targets: ${{ matrix.target }}
- uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1
- name: Apple M1 setup
if: matrix.target == 'aarch64-apple-darwin'
run: |
printf 'SDKROOT=%s\n' "$(xcrun -sdk macosx --show-sdk-path)" >> "$GITHUB_ENV"
printf 'MACOSX_DEPLOYMENT_TARGET=%s\n' "$(xcrun -sdk macosx --show-sdk-platform-version)" >> "$GITHUB_ENV"
- name: cross setup
if: contains(matrix.target, 'musl')
run: |
cargo install cross --locked \
--git https://github.com/cross-rs/cross \
--rev 64b5bb4d3d34de062552b9a2093affe77b4ad16a
- name: Build binaries
env:
TAG_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }}
SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }}
PLATFORM_NAME: ${{ matrix.platform }}
TARGET: ${{ matrix.target }}
OUT_DIR: target/${{ matrix.target }}/${{ env.RUST_PROFILE }}
shell: bash
run: |
set -eo pipefail
flags=(--locked --target "$TARGET" --profile "$RUST_PROFILE" --bins
--no-default-features --features "$RUST_FEATURES")
# `jemalloc` is not fully supported on MSVC or aarch64 Linux.
if [[ "$TARGET" != *msvc* && "$TARGET" != "aarch64-unknown-linux-gnu" ]]; then
flags+=(--features jemalloc)
fi
[[ "$TARGET" == *windows* ]] && ext=".exe"
if [[ "$TARGET" == *-musl ]]; then
cross build "${flags[@]}"
else
cargo build "${flags[@]}"
fi
bins=(anvil cast chisel forge solar)
for name in "${bins[@]}"; do
bin="$OUT_DIR/$name$ext"
printf '\n'
file "$bin" || true
du -h "$bin" || true
ldd "$bin" || true
$bin --version || true
printf '%s_bin_path=%s\n' "$name" "$bin" >> "$GITHUB_ENV"
done
- name: Archive binaries
id: artifacts
env:
PLATFORM_NAME: ${{ matrix.platform }}
OUT_DIR: target/${{ matrix.target }}/${{ env.RUST_PROFILE }}
VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }}
ARCH: ${{ matrix.arch }}
shell: bash
run: |
if [[ "$PLATFORM_NAME" == "linux" || "$PLATFORM_NAME" == "alpine" ]]; then
tar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C "$OUT_DIR" forge cast anvil chisel solar
file_name="foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz"
elif [ "$PLATFORM_NAME" == "darwin" ]; then
# We need to use gtar here otherwise the archive is corrupt.
# See: https://github.com/actions/virtual-environments/issues/2619
gtar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C "$OUT_DIR" forge cast anvil chisel solar
file_name="foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz"
else
cd "$OUT_DIR"
7z a -tzip "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" forge.exe cast.exe anvil.exe chisel.exe solar.exe
mv "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" ../../../
file_name="foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip"
fi
{
printf "file_name=%s\n" "$file_name"
printf "foundry_attestation=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.attestation.txt"
printf "foundry_sbom=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.spdx.json"
printf "foundry_checksum=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.sha256"
printf "foundry_signature=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.sigstore.json"
} >> "$GITHUB_OUTPUT"
- name: Generate archive checksum
env:
FILE_NAME: ${{ steps.artifacts.outputs.file_name }}
FOUNDRY_CHECKSUM: ${{ steps.artifacts.outputs.foundry_checksum }}
shell: bash
run: |
set -euo pipefail
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$FILE_NAME" > "$FOUNDRY_CHECKSUM"
else
shasum -a 256 "$FILE_NAME" > "$FOUNDRY_CHECKSUM"
fi
cat "$FOUNDRY_CHECKSUM"
- name: Install Syft
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
- name: Generate SBOM (SPDX)
env:
FOUNDRY_SBOM: ${{ steps.artifacts.outputs.foundry_sbom }}
VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }}
shell: bash
run: |
set -euo pipefail
syft scan dir:. \
--source-name foundry \
--source-version "$VERSION_NAME" \
-o spdx-json="$FOUNDRY_SBOM"
- name: Upload build artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
retention-days: 1
name: ${{ steps.artifacts.outputs.file_name }}
path: ${{ steps.artifacts.outputs.file_name }}
- name: Build man page
id: man
if: matrix.target == 'x86_64-unknown-linux-gnu'
env:
OUT_DIR: target/${{ matrix.target }}/${{ env.RUST_PROFILE }}
VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }}
shell: bash
run: |
sudo apt-get -y install help2man
help2man -N "$OUT_DIR/forge" > forge.1
help2man -N "$OUT_DIR/cast" > cast.1
help2man -N "$OUT_DIR/anvil" > anvil.1
help2man -N "$OUT_DIR/chisel" > chisel.1
help2man -N "$OUT_DIR/solar" > solar.1
gzip forge.1
gzip cast.1
gzip anvil.1
gzip chisel.1
gzip solar.1
tar -czvf "foundry_man_${VERSION_NAME}.tar.gz" forge.1.gz cast.1.gz anvil.1.gz chisel.1.gz solar.1.gz
printf 'foundry_man=%s\n' "foundry_man_${VERSION_NAME}.tar.gz" >> "$GITHUB_OUTPUT"
- name: Binaries and archive provenance attestation
id: attestation
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
with:
subject-path: |
${{ env.anvil_bin_path }}
${{ env.cast_bin_path }}
${{ env.chisel_bin_path }}
${{ env.forge_bin_path }}
${{ env.solar_bin_path }}
${{ steps.artifacts.outputs.file_name }}
- name: Archive SBOM attestation
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
with:
subject-path: ${{ steps.artifacts.outputs.file_name }}
sbom-path: ${{ steps.artifacts.outputs.foundry_sbom }}
- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Sign archive with cosign (keyless)
env:
FILE_NAME: ${{ steps.artifacts.outputs.file_name }}
FOUNDRY_SIGNATURE: ${{ steps.artifacts.outputs.foundry_signature }}
shell: bash
run: |
set -euo pipefail
cosign sign-blob \
--yes \
--bundle "$FOUNDRY_SIGNATURE" \
"$FILE_NAME"
- name: Record attestation URL
env:
ATTESTATION_URL: ${{ steps.attestation.outputs.attestation-url }}
FOUNDRY_ATTESTATION: ${{ steps.artifacts.outputs.foundry_attestation }}
shell: bash
run: |
set -euo pipefail
printf '%s\n' "$ATTESTATION_URL" > "$FOUNDRY_ATTESTATION"
# Upload assets to the draft release created in `prepare`. Stable
# releases stay as drafts after this workflow finishes; a maintainer
# publishes them manually. Nightlies are auto-published below.
- name: Upload assets to draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG_NAME: ${{ needs.prepare.outputs.tag_name }}
FILE_NAME: ${{ steps.artifacts.outputs.file_name }}
FOUNDRY_ATTESTATION: ${{ steps.artifacts.outputs.foundry_attestation }}
FOUNDRY_SBOM: ${{ steps.artifacts.outputs.foundry_sbom }}
FOUNDRY_CHECKSUM: ${{ steps.artifacts.outputs.foundry_checksum }}
FOUNDRY_SIGNATURE: ${{ steps.artifacts.outputs.foundry_signature }}
FOUNDRY_MAN: ${{ steps.man.outputs.foundry_man }}
shell: bash
run: |
set -euo pipefail
files=(
"$FILE_NAME"
"$FOUNDRY_ATTESTATION"
"$FOUNDRY_SBOM"
"$FOUNDRY_CHECKSUM"
"$FOUNDRY_SIGNATURE"
)
if [[ -n "${FOUNDRY_MAN:-}" ]]; then
files+=("$FOUNDRY_MAN")
fi
gh release upload "$TAG_NAME" "${files[@]}" --clobber
# Auto-publish nightly releases once all assets have been uploaded so that
# foundryup and other consumers see them immediately. Stable releases are
# left as drafts for a maintainer to publish manually.
publish-nightly:
name: Publish nightly release
runs-on: ubuntu-latest
needs: [prepare, release-docker, release]
if: ${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && needs.prepare.outputs.skip_release != 'true' }}
permissions:
contents: write
steps:
- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG_NAME: ${{ needs.prepare.outputs.tag_name }}
shell: bash
run: gh release edit "$TAG_NAME" --draft=false
# If any of the jobs fail, this will create a high-priority issue to signal so.
issue:
name: Open an issue
runs-on: ubuntu-latest
needs: [prepare, release-docker, release, publish-nightly]
if: failure()
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WORKFLOW_URL: |
${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
update_existing: true
filename: .github/RELEASE_FAILURE_ISSUE_TEMPLATE.md