name: Release
on:
push:
tags: ["v*.*.*"]
workflow_dispatch: {}
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
tag-check:
name: Tag Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with:
persist-credentials: false
- name: Validate tag matches package version
if: github.event_name == 'push'
env:
TAG_NAME: ${{ github.ref_name }}
run: |
python3 - <<'PY'
import os
import pathlib
import tomllib
tag_name = os.environ["TAG_NAME"]
if not tag_name.startswith("v"):
raise SystemExit(f"expected v-prefixed tag, got {tag_name}")
tag_version = tag_name[1:]
cargo = tomllib.loads(pathlib.Path("Cargo.toml").read_text())
package_version = cargo["package"]["version"]
if tag_version != package_version:
raise SystemExit(
f"tag version {tag_version} does not match package version {package_version}"
)
PY
- name: Skip tag validation for manual run
if: github.event_name != 'push'
run: echo "workflow_dispatch run; skipping version check"
build:
needs: tag-check
name: Build (${{ matrix.target }}${{ matrix.feature && format('-{0}', matrix.feature) || '' }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
runner: macos-14
- target: x86_64-apple-darwin
runner: macos-15-intel
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
- target: x86_64-pc-windows-msvc
runner: windows-latest
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
feature: cuda
- target: x86_64-pc-windows-msvc
runner: windows-latest
feature: cuda
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
feature: opencl
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
feature: opencl
- target: x86_64-pc-windows-msvc
runner: windows-latest
feature: opencl
- target: aarch64-apple-darwin
runner: macos-14
feature: opencl
- target: x86_64-apple-darwin
runner: macos-15-intel
feature: opencl
env:
TAG_NAME: ${{ github.ref_name }}
TARGET: ${{ matrix.target }}
FEATURE: ${{ matrix.feature }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
with:
toolchain: stable
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@bc2d2e71bd35c5549942babaa51a89c586b981d1 with:
key: release-${{ matrix.target }}${{ matrix.feature && format('-{0}', matrix.feature) || '' }}
- name: Install CUDA toolkit
if: matrix.feature == 'cuda'
uses: Jimver/cuda-toolkit@4bd727d5619dc6fa323b1e76c3aa5dca94f5ec6d with:
cuda: '12.5.0'
method: 'network'
- name: Install OpenCL (Linux)
if: matrix.feature == 'opencl' && runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends ocl-icd-opencl-dev opencl-headers
- name: Install OpenCL (Windows)
if: matrix.feature == 'opencl' && runner.os == 'Windows'
shell: pwsh
run: |
vcpkg install opencl:x64-windows
"CMAKE_TOOLCHAIN_FILE=$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" `
| Out-File -FilePath $env:GITHUB_ENV -Append
- name: Build
shell: bash
run: |
set -euo pipefail
features=""
if [ -n "$FEATURE" ]; then
features="--features $FEATURE"
fi
cargo build --release --locked --target "$TARGET" --bins $features
- name: Package binaries (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
set -euo pipefail
suffix=""
if [ -n "$FEATURE" ]; then
suffix="-$FEATURE"
fi
name="cruzbit-${TAG_NAME}-${TARGET}${suffix}"
mkdir "$name"
cp "target/$TARGET/release/client" "$name/client"
cp "target/$TARGET/release/wallet" "$name/wallet"
cp README.md LICENSE "$name/"
chmod +x "$name/client" "$name/wallet"
tar -czf "$name.tar.gz" "$name"
echo "ASSET=$name.tar.gz" >> "$GITHUB_ENV"
- name: Package binaries (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$suffix = ""
if ($env:FEATURE) {
$suffix = "-$env:FEATURE"
}
$name = "cruzbit-$env:TAG_NAME-$env:TARGET$suffix"
New-Item -ItemType Directory -Path $name | Out-Null
Copy-Item "target/$env:TARGET/release/client.exe" "$name/client.exe"
Copy-Item "target/$env:TARGET/release/wallet.exe" "$name/wallet.exe"
Copy-Item README.md, LICENSE $name
Compress-Archive -Path $name -DestinationPath "$name.zip"
"ASSET=$name.zip" | Out-File -FilePath $env:GITHUB_ENV -Append
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with:
name: dist-${{ matrix.target }}${{ matrix.feature && format('-{0}', matrix.feature) || '' }}
path: ${{ env.ASSET }}
release:
name: Release
needs: build
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with:
persist-credentials: false
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with:
path: dist
pattern: dist-*
merge-multiple: true
- name: Generate checksums
working-directory: dist
run: sha256sum * > SHA256SUMS.txt
- name: Extract release notes from changelog
env:
TAG_NAME: ${{ github.ref_name }}
run: |
python3 - <<'PY'
import os
import pathlib
import re
tag_name = os.environ["TAG_NAME"]
version = tag_name[1:] if tag_name.startswith("v") else tag_name
changelog = pathlib.Path("CHANGELOG.md").read_text()
pattern = re.compile(rf"^## \[{re.escape(version)}\]\s+-\s+.+$")
lines = changelog.splitlines()
capture = []
inside = False
for line in lines:
if line.startswith("## ["):
if inside:
break
if pattern.match(line):
inside = True
if inside:
capture.append(line)
notes = "\n".join(capture).strip()
if not notes:
raise SystemExit(f"missing CHANGELOG entry for {version}")
pathlib.Path("RELEASE_NOTES.md").write_text(notes + "\n")
PY
- name: Create release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ github.ref_name }}
run: |
gh release create "$TAG_NAME" \
--title "$TAG_NAME" \
--notes-file RELEASE_NOTES.md \
dist/*