Skip to content

Release

Release #455

Workflow file for this run

name: Release
on:
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
inputs:
tag:
description: 'Tag to build (e.g. aptos-cli-v9.0.0)'
required: false
type: string
backfill:
description: 'Backfill: build Windows artifacts for an existing release without recreating it'
required: false
type: boolean
default: false
permissions:
contents: read
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
jobs:
# ────────────────────────────────────────────────────────────────────
# Job 1: Resolve the tag, decide whether to build, and emit the matrix
# ────────────────────────────────────────────────────────────────────
check:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.resolve.outputs.tag }}
version: ${{ steps.resolve.outputs.version }}
should_build: ${{ steps.resolve.outputs.should_build }}
backfill: ${{ steps.resolve.outputs.backfill }}
matrix: ${{ steps.resolve.outputs.matrix }}
steps:
- name: Resolve tag, mode, and matrix
id: resolve
env:
GH_TOKEN: ${{ github.token }}
INPUT_TAG: ${{ inputs.tag }}
INPUT_BACKFILL: ${{ inputs.backfill }}
GH_REPO: ${{ github.repository }}
run: |
set -euo pipefail
BACKFILL="${INPUT_BACKFILL:-false}"
if [ -n "$INPUT_TAG" ]; then
TAG="$INPUT_TAG"
else
# Find the latest aptos-cli-v* release from aptos-core
TAG=$(gh api repos/aptos-labs/aptos-core/releases \
--jq '[.[] | select(.tag_name | startswith("aptos-cli-v"))][0].tag_name')
if [ -z "$TAG" ] || [ "$TAG" = "null" ]; then
echo "No aptos-cli release found upstream"
echo "should_build=false" >> "$GITHUB_OUTPUT"
exit 0
fi
fi
VERSION="${TAG#aptos-cli-v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "backfill=$BACKFILL" >> "$GITHUB_OUTPUT"
# Full matrix used for fresh releases.
FULL_MATRIX=$(cat <<'JSON'
[
{"name":"macOS x86", "runner":"macos-15-intel", "target":"x86_64-apple-darwin"},
{"name":"macOS ARM", "runner":"macos-latest", "target":"aarch64-apple-darwin"},
{"name":"Linux x86", "runner":"ubuntu-24.04", "target":"x86_64-unknown-linux-gnu"},
{"name":"Linux ARM", "runner":"ubuntu-24.04-arm", "target":"aarch64-unknown-linux-gnu"},
{"name":"Linux x86 compat", "runner":"ubuntu-latest", "target":"x86_64-unknown-linux-gnu", "compat":true, "container":"ubuntu:20.04"},
{"name":"Linux ARM compat", "runner":"ubuntu-24.04-arm", "target":"aarch64-unknown-linux-gnu", "compat":true, "container":"ubuntu:20.04"},
{"name":"Windows x86", "runner":"windows-2025", "target":"x86_64-pc-windows-msvc"}
]
JSON
)
# Backfill matrix: only Windows targets.
BACKFILL_MATRIX=$(cat <<'JSON'
[
{"name":"Windows x86", "runner":"windows-2025", "target":"x86_64-pc-windows-msvc"}
]
JSON
)
if [ "$BACKFILL" = "true" ]; then
# Verify the release exists; backfill only makes sense for an existing release.
if ! gh release view "$TAG" --repo "$GH_REPO" &>/dev/null; then
echo "::error::Backfill requested but release $TAG does not exist"
exit 1
fi
echo "Backfilling Windows artifacts for $TAG"
MATRIX=$(echo "$BACKFILL_MATRIX" | jq -c .)
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
echo "should_build=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Skip if we already published this release
if gh release view "$TAG" --repo "$GH_REPO" &>/dev/null; then
echo "Release $TAG already exists, skipping (use backfill=true to add Windows artifacts)"
echo "should_build=false" >> "$GITHUB_OUTPUT"
else
echo "Will build $TAG (version $VERSION)"
MATRIX=$(echo "$FULL_MATRIX" | jq -c .)
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
echo "should_build=true" >> "$GITHUB_OUTPUT"
fi
# ────────────────────────────────────────────────────────────────────
# Job 2: Build the CLI for every selected platform
# ────────────────────────────────────────────────────────────────────
build:
needs: check
if: needs.check.outputs.should_build == 'true'
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.check.outputs.matrix) }}
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
container: ${{ matrix.container || '' }}
env:
TAG: ${{ needs.check.outputs.tag }}
VERSION: ${{ needs.check.outputs.version }}
steps:
# ── Container prerequisites (compat builds only) ──────────────
- name: Install container prerequisites
if: matrix.container
run: |
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y curl ca-certificates git sudo
git config --global --add safe.directory "$GITHUB_WORKSPACE"
# ── Windows long-path support (aptos-core has very deep paths) ─
- name: Enable Windows long paths
if: runner.os == 'Windows'
shell: bash
run: git config --global core.longpaths true
# ── Checkout ──────────────────────────────────────────────────
- name: Checkout release repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Checkout aptos-core
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: aptos-labs/aptos-core
ref: ${{ needs.check.outputs.tag }}
path: aptos-core
fetch-depth: 1
# ── System dependencies ───────────────────────────────────────
- name: Install Linux build deps
if: runner.os == 'Linux'
run: bash .github/workflows/build-scripts/setup-linux.sh
- name: Install macOS build deps
if: runner.os == 'macOS'
run: |
brew install pkg-config openssl cmake llvm lld
OPENSSL_PREFIX="$(brew --prefix openssl)"
echo "OPENSSL_DIR=${OPENSSL_PREFIX}" >> "$GITHUB_ENV"
echo "PKG_CONFIG_PATH=${OPENSSL_PREFIX}/lib/pkgconfig" >> "$GITHUB_ENV"
- name: Install Windows build deps
if: runner.os == 'Windows'
shell: pwsh
run: |
# vcpkg is preinstalled on GitHub-hosted Windows runners at C:\vcpkg.
# Match aptos-core's own Windows release script.
$env:VCPKG_ROOT = 'C:\vcpkg\'
vcpkg install openssl:x64-windows-static-md --clean-after-build
"VCPKG_ROOT=C:\vcpkg\" | Out-File -FilePath $env:GITHUB_ENV -Append
"OPENSSL_DIR=C:\vcpkg\installed\x64-windows-static-md" | Out-File -FilePath $env:GITHUB_ENV -Append
"OPENSSL_STATIC=1" | Out-File -FilePath $env:GITHUB_ENV -Append
# ── Rust toolchain ────────────────────────────────────────────
- name: Read Rust toolchain version
id: rust-version
shell: bash
run: |
CHANNEL=$(grep '^channel' aptos-core/rust-toolchain.toml | sed 's/.*"\(.*\)"/\1/')
echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT"
echo "Using Rust toolchain: $CHANNEL"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master
with:
toolchain: ${{ steps.rust-version.outputs.channel }}
targets: ${{ matrix.target }}
- name: Rust cache
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
workspaces: aptos-core -> target
key: ${{ matrix.target }}${{ matrix.compat && '-compat' || '' }}
# ── Compat build configuration ────────────────────────────────
- name: Patch .cargo/config.toml for compat (x86)
if: matrix.compat && matrix.target == 'x86_64-unknown-linux-gnu'
run: |
cd aptos-core
# Rust target-cpu: v3 → v2 (rustc supports this on all versions)
sed -i 's/target-cpu=x86-64-v3/target-cpu=x86-64-v2/g' .cargo/config.toml
# C compiler -march: v3 → baseline (Clang 10 in ubuntu:20.04 doesn't know v2 syntax)
sed -i 's/-march=x86-64-v3/-march=x86-64/g' .cargo/config.toml
echo "Patched .cargo/config.toml for x86-64-v2 compat"
# ── Build ─────────────────────────────────────────────────────
- name: Build
working-directory: aptos-core
run: cargo build -p aptos --profile cli
# ── Package ───────────────────────────────────────────────────
- name: Package binary (Unix)
if: runner.os != 'Windows'
env:
COMPAT: ${{ matrix.compat }}
TARGET: ${{ matrix.target }}
run: |
SUFFIX=""
if [ "$COMPAT" = "true" ]; then
SUFFIX="-compat"
fi
ARCHIVE="aptos-cli-${VERSION}-${TARGET}${SUFFIX}.zip"
cd aptos-core/target/cli
zip "${GITHUB_WORKSPACE}/${ARCHIVE}" aptos
echo "Packaged: ${ARCHIVE}"
- name: Package binary (Windows)
if: runner.os == 'Windows'
shell: pwsh
env:
TARGET: ${{ matrix.target }}
run: |
$Archive = "aptos-cli-${env:VERSION}-${env:TARGET}.zip"
Compress-Archive -Path "aptos-core\target\cli\aptos.exe" -DestinationPath $Archive
Write-Output "Packaged: ${Archive}"
# ── Upload artifact ───────────────────────────────────────────
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: aptos-cli-${{ matrix.target }}${{ matrix.compat && '-compat' || '' }}
path: '*.zip'
# ────────────────────────────────────────────────────────────────────
# Job 3: Create the GitHub Release with all artifacts (fresh releases)
# ────────────────────────────────────────────────────────────────────
release:
needs: [check, build]
if: needs.check.outputs.should_build == 'true' && needs.check.outputs.backfill != 'true'
runs-on: ubuntu-latest
permissions:
contents: write
env:
TAG: ${{ needs.check.outputs.tag }}
VERSION: ${{ needs.check.outputs.version }}
steps:
- name: Checkout release repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts
merge-multiple: true
- name: Generate checksums
working-directory: artifacts
run: sha256sum aptos-cli-*.zip > SHA256SUMS
- name: Fetch changelog from aptos-core
run: |
# Download the CHANGELOG from the tagged version of aptos-core
curl -fsSL "https://raw.githubusercontent.com/aptos-labs/aptos-core/${TAG}/crates/aptos/CHANGELOG.md" \
-o CHANGELOG.md
# Extract the section for this version (between ## [VERSION] and the next ## [)
awk -v ver="$VERSION" '
/^## \[/ {
if (found) exit
if (index($0, "[" ver "]")) found=1
}
found { print }
' CHANGELOG.md > release_notes_section.md
# If we found a section, use it; otherwise note it was not found
if [ -s release_notes_section.md ]; then
echo "Extracted changelog for version $VERSION"
else
echo "No changelog section found for version $VERSION"
echo "_No changelog entry found for this version._" > release_notes_section.md
fi
- name: Build release body
run: |
cat > release_body.md << 'HEADER'
## Aptos CLI ${{ needs.check.outputs.version }}
Built from [aptos-core `${{ needs.check.outputs.tag }}`](https://github.com/aptos-labs/aptos-core/releases/tag/${{ needs.check.outputs.tag }}).
### What's Changed
HEADER
cat release_notes_section.md >> release_body.md
# Add checksums table
cat >> release_body.md << 'TABLE_HEADER'
### Downloads
| Platform | Archive | SHA-256 |
|----------|---------|---------|
TABLE_HEADER
# Generate table rows with checksums
cd artifacts
for zip in aptos-cli-*.zip; do
HASH=$(sha256sum "$zip" | cut -d' ' -f1)
SHORT_HASH="${HASH:0:16}..."
case "$zip" in
*x86_64-apple-darwin*) PLATFORM="macOS x86_64" ;;
*aarch64-apple-darwin*) PLATFORM="macOS ARM64" ;;
*x86_64*linux*compat*) PLATFORM="Linux x86_64 (compat, GLIBC 2.31+)" ;;
*aarch64*linux*compat*) PLATFORM="Linux ARM64 (compat, GLIBC 2.31+)" ;;
*x86_64*linux*) PLATFORM="Linux x86_64" ;;
*aarch64*linux*) PLATFORM="Linux ARM64" ;;
*x86_64*windows*) PLATFORM="Windows x86_64" ;;
*) PLATFORM="$zip" ;;
esac
echo "| $PLATFORM | \`$zip\` | \`$SHORT_HASH\` |" >> ../release_body.md
done
cat >> ../release_body.md << 'FOOTER'
### Verification
```bash
sha256sum -c SHA256SUMS
```
FOOTER
- name: Determine pre-release status
id: prerelease
run: |
if echo "$TAG" | grep -qE '-(rc|beta|alpha)'; then
echo "prerelease=true" >> "$GITHUB_OUTPUT"
else
echo "prerelease=false" >> "$GITHUB_OUTPUT"
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
tag_name: ${{ needs.check.outputs.tag }}
name: Aptos CLI ${{ needs.check.outputs.version }}
prerelease: ${{ steps.prerelease.outputs.prerelease == 'true' }}
body_path: release_body.md
files: |
artifacts/aptos-cli-*.zip
artifacts/SHA256SUMS
- name: Update CHANGELOG in this repo
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add CHANGELOG.md
if ! git diff --cached --quiet; then
git commit -m "Update CHANGELOG from aptos-core ${TAG}"
git push
else
echo "CHANGELOG.md unchanged, skipping commit"
fi
# ────────────────────────────────────────────────────────────────────
# Job 4: Backfill — upload new artifacts to an existing release and
# merge their checksums into SHA256SUMS without touching anything else.
# ────────────────────────────────────────────────────────────────────
backfill:
needs: [check, build]
if: needs.check.outputs.should_build == 'true' && needs.check.outputs.backfill == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
env:
TAG: ${{ needs.check.outputs.tag }}
VERSION: ${{ needs.check.outputs.version }}
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
steps:
- name: Download newly built artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: new-artifacts
merge-multiple: true
- name: List new artifacts
run: ls -la new-artifacts/
- name: Download existing SHA256SUMS from release
run: |
mkdir -p existing
if gh release download "$TAG" --repo "$GH_REPO" --pattern "SHA256SUMS" --dir existing; then
echo "Found existing SHA256SUMS"
else
echo "No existing SHA256SUMS in release; will create one"
: > existing/SHA256SUMS
fi
- name: Merge checksums
run: |
set -euo pipefail
cd new-artifacts
# Generate checksums for the new (Windows) zips.
sha256sum aptos-cli-*.zip > new.sums
# Start with the existing checksums...
cp ../existing/SHA256SUMS merged.sums || : > merged.sums
# ...and replace any rows for files we are about to upload, then
# append the new ones. (`grep -v` removes existing entries with the
# same filename so we don't end up with duplicates.)
for f in aptos-cli-*.zip; do
grep -v " $f\$" merged.sums > merged.sums.tmp || true
mv merged.sums.tmp merged.sums
done
cat new.sums >> merged.sums
# Stable, predictable ordering.
sort -k2 merged.sums -o merged.sums
mv merged.sums SHA256SUMS
echo "Merged SHA256SUMS:"
cat SHA256SUMS
- name: Upload artifacts to existing release
working-directory: new-artifacts
run: |
gh release upload "$TAG" \
--repo "$GH_REPO" \
--clobber \
aptos-cli-*.zip SHA256SUMS
- name: Summary
run: |
echo "Backfilled the following assets to $TAG:"
ls -1 new-artifacts/aptos-cli-*.zip
echo "Updated SHA256SUMS in the release."