Skip to content

Build and Release

Build and Release #5

Workflow file for this run

name: Build and Release
on:
push:
tags:
- "*.*.*"
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g., 0.1.0). If provided, will build in release mode."
required: false
type: string
env:
CARGO_TERM_COLOR: always
jobs:
# Build strategy check - determine build type and version
build-check:
name: Build Strategy Check
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
version: ${{ steps.check.outputs.version }}
is_release: ${{ steps.check.outputs.is_release }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine build strategy
id: check
run: |
version=""
is_release=false
if [[ "${{ github.event_name }}" == "release" ]]; then
# Release event - get version from release tag
version="${{ github.event.release.tag_name }}"
is_release=true
echo "📦 Release build detected: $version"
elif [[ "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]]; then
# Tag push - get version from tag
version="${GITHUB_REF#refs/tags/}"
is_release=true
echo "🏷️ Tag build detected: $version"
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]] && [[ -n "${{ github.event.inputs.tag }}" ]]; then
# Manual trigger with tag input - treat as release
version="${{ github.event.inputs.tag }}"
is_release=true
echo "🏷️ Manual release build detected: $version"
else
# Manual trigger without tag or other
version="dev-$(git rev-parse --short HEAD)"
echo "🛠️ Development build detected: $version"
fi
# Ensure version starts with 'v' for release builds
if [[ "$is_release" == "true" ]] && [[ ! "$version" =~ ^v ]]; then
version="v${version}"
echo "ℹ️ Version normalized to: $version"
fi
echo "version=$version" >> $GITHUB_OUTPUT
echo "is_release=$is_release" >> $GITHUB_OUTPUT
echo "📊 Build Summary:"
echo " - Version: $version"
echo " - Is release: $is_release"
build:
name: Build (${{ matrix.os_name }}-${{ matrix.arch }})
needs: build-check
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
# Linux
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
os_name: linux
arch: amd64
artifact_name: rc
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
os_name: linux
arch: arm64
artifact_name: rc
cross: true
# macOS (both targets built on ARM64 runner with cross-compilation)
- os: macos-latest
target: x86_64-apple-darwin
os_name: macos
arch: amd64
artifact_name: rc
- os: macos-latest
target: aarch64-apple-darwin
os_name: macos
arch: arm64
artifact_name: rc
# Windows
- os: windows-latest
target: x86_64-pc-windows-msvc
os_name: windows
arch: amd64
artifact_name: rc.exe
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
# Install cross for cross-compilation
- name: Install cross
if: matrix.cross
run: cargo install cross --git https://github.com/cross-rs/cross
# Build
- name: Build (native)
if: "!matrix.cross"
run: cargo build --release --target ${{ matrix.target }}
- name: Build (cross)
if: matrix.cross
run: cross build --release --target ${{ matrix.target }}
# Package and prepare artifacts
- name: Prepare and package artifacts (Unix)
if: runner.os != 'Windows'
env:
VERSION: ${{ needs.build-check.outputs.version }}
run: |
mkdir -p artifacts
# Format: rustfs-cli-{os}-{arch}-{version}
ARTIFACT_PREFIX="rustfs-cli-${{ matrix.os_name }}-${{ matrix.arch }}"
# Create versioned archive
cd target/${{ matrix.target }}/release
tar -czvf "../../../artifacts/${ARTIFACT_PREFIX}-${VERSION}.tar.gz" ${{ matrix.artifact_name }}
cd ../../..
# Create latest archive (copy of versioned)
cp "artifacts/${ARTIFACT_PREFIX}-${VERSION}.tar.gz" "artifacts/${ARTIFACT_PREFIX}-latest.tar.gz"
# Generate checksums
cd artifacts
shasum -a 256 "${ARTIFACT_PREFIX}-${VERSION}.tar.gz" > "${ARTIFACT_PREFIX}-${VERSION}.tar.gz.sha256"
shasum -a 256 "${ARTIFACT_PREFIX}-latest.tar.gz" > "${ARTIFACT_PREFIX}-latest.tar.gz.sha256"
cd ..
echo "📦 Artifacts created:"
ls -la artifacts/
- name: Prepare and package artifacts (Windows)
if: runner.os == 'Windows'
env:
VERSION: ${{ needs.build-check.outputs.version }}
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path artifacts
# Format: rustfs-cli-{os}-{arch}-{version}
$ARTIFACT_PREFIX = "rustfs-cli-${{ matrix.os_name }}-${{ matrix.arch }}"
# Create versioned archive
$sourcePath = "target/${{ matrix.target }}/release/${{ matrix.artifact_name }}"
$versionedZip = "artifacts/${ARTIFACT_PREFIX}-${env:VERSION}.zip"
Compress-Archive -Path $sourcePath -DestinationPath $versionedZip
# Create latest archive (copy of versioned)
$latestZip = "artifacts/${ARTIFACT_PREFIX}-latest.zip"
Copy-Item $versionedZip -Destination $latestZip
# Generate checksums
$versionedHash = (Get-FileHash -Path $versionedZip -Algorithm SHA256).Hash.ToLower()
$latestHash = (Get-FileHash -Path $latestZip -Algorithm SHA256).Hash.ToLower()
"${versionedHash} ${ARTIFACT_PREFIX}-${env:VERSION}.zip" | Out-File -FilePath "artifacts/${ARTIFACT_PREFIX}-${env:VERSION}.zip.sha256" -Encoding utf8 -NoNewline
"${latestHash} ${ARTIFACT_PREFIX}-latest.zip" | Out-File -FilePath "artifacts/${ARTIFACT_PREFIX}-latest.zip.sha256" -Encoding utf8 -NoNewline
Write-Host "📦 Artifacts created:"
Get-ChildItem -Path artifacts | Format-Table Name, Length -AutoSize
# Upload artifacts for later jobs
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: rustfs-cli-${{ matrix.os_name }}-${{ matrix.arch }}
path: artifacts/*
if-no-files-found: error
# Upload to Aliyun OSS
- name: Upload to Aliyun OSS
if: needs.build-check.outputs.is_release == 'true'
env:
OSS_ACCESS_KEY_ID: ${{ secrets.ALICLOUDOSS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.ALICLOUDOSS_KEY_SECRET }}
OSS_REGION: cn-beijing
OSS_ENDPOINT: https://oss-cn-beijing.aliyuncs.com
shell: bash
run: |
if [[ -z "$OSS_ACCESS_KEY_ID" ]]; then
echo "⚠️ OSS credentials not available, skipping OSS upload"
exit 0
fi
# Install ossutil
OSSUTIL_VERSION="2.1.1"
case "${{ matrix.os_name }}" in
macos)
if [[ "$(uname -m)" == "arm64" ]]; then
ARCH="arm64"
else
ARCH="amd64"
fi
OSSUTIL_ZIP="ossutil-${OSSUTIL_VERSION}-mac-${ARCH}.zip"
OSSUTIL_DIR="ossutil-${OSSUTIL_VERSION}-mac-${ARCH}"
curl -sL -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}"
unzip -q "$OSSUTIL_ZIP"
mv "${OSSUTIL_DIR}/ossutil" /usr/local/bin/
rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP"
chmod +x /usr/local/bin/ossutil
OSSUTIL_BIN=ossutil
;;
linux)
OSSUTIL_ZIP="ossutil-${OSSUTIL_VERSION}-linux-amd64.zip"
OSSUTIL_DIR="ossutil-${OSSUTIL_VERSION}-linux-amd64"
curl -sL -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}"
unzip -q "$OSSUTIL_ZIP"
mv "${OSSUTIL_DIR}/ossutil" ./ossutil
rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP"
chmod +x ./ossutil
OSSUTIL_BIN=./ossutil
;;
windows)
OSSUTIL_ZIP="ossutil-${OSSUTIL_VERSION}-windows-amd64.zip"
OSSUTIL_DIR="ossutil-${OSSUTIL_VERSION}-windows-amd64"
curl -sL -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}"
unzip -q "$OSSUTIL_ZIP"
mv "${OSSUTIL_DIR}/ossutil.exe" ./ossutil.exe
rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP"
OSSUTIL_BIN=./ossutil.exe
;;
esac
OSS_PATH="oss://rustfs-artifacts/artifacts/rustfs-cli/release/"
# Upload all artifacts to OSS
echo "📤 Uploading artifacts to $OSS_PATH..."
for file in artifacts/*; do
if [[ -f "$file" ]]; then
echo "Uploading: $file"
$OSSUTIL_BIN cp "$file" "$OSS_PATH" --force
echo "✅ Uploaded: $(basename "$file")"
fi
done
echo "✅ OSS upload completed successfully"
# Generate shell completions
completions:
name: Generate Shell Completions
needs: build-check
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
- name: Build
run: cargo build --release
- name: Generate completions
run: |
mkdir -p completions
./target/release/rc completions bash > completions/rc.bash
./target/release/rc completions zsh > completions/_rc
./target/release/rc completions fish > completions/rc.fish
./target/release/rc completions powershell > completions/rc.ps1
tar -czvf completions.tar.gz completions/
continue-on-error: true
- name: Upload completions artifact
uses: actions/upload-artifact@v4
with:
name: completions
path: completions.tar.gz
if-no-files-found: warn
# Upload release assets to GitHub Release
upload-release-assets:
name: Upload Release Assets
needs: [build-check, build, completions]
runs-on: ubuntu-latest
if: needs.build-check.outputs.is_release == 'true'
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: rustfs-cli-*
merge-multiple: true
- name: Download completions
uses: actions/download-artifact@v4
with:
name: completions
path: artifacts
continue-on-error: true
- name: Display structure of downloaded files
run: |
echo "📦 Downloaded artifacts:"
ls -laR artifacts/
- name: Prepare release assets
run: |
mkdir -p release-assets
# Copy all artifacts (exclude latest versions for GitHub Release)
if [ -d "artifacts" ] && [ "$(ls -A artifacts)" ]; then
for file in artifacts/*; do
filename=$(basename "$file")
# Skip latest versions - only upload versioned artifacts to GitHub Release
if [[ "$filename" != *"-latest"* ]]; then
cp "$file" "release-assets/"
echo "✅ Added: $filename"
else
echo "⏭️ Skipped (latest): $filename"
fi
done
else
echo "⚠️ No artifacts found to copy"
fi
# Generate combined checksums file
cd release-assets
if ls *.tar.gz *.zip 2>/dev/null; then
sha256sum *.tar.gz *.zip 2>/dev/null > SHA256SUMS || true
echo "✅ SHA256SUMS generated"
fi
echo "📦 Release assets:"
ls -la
- name: Upload to GitHub Release (release event)
if: github.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.release.tag_name }}
files: release-assets/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload to GitHub Release (tag push or manual)
if: github.event_name != 'release'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.build-check.outputs.version }}
name: Release ${{ needs.build-check.outputs.version }}
files: release-assets/*
draft: true
prerelease: false
body: |
## Changes
See [CHANGELOG.md](CHANGELOG.md) for details.
## Installation
### Binary Download
Download the appropriate binary for your platform from the assets below.
### Homebrew (macOS/Linux)
```bash
brew install rustfs/tap/rc
```
### Cargo
```bash
cargo install rustfs-cli
```
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Publish to crates.io
publish-crates:
name: Publish to crates.io
needs: [build-check, build]
runs-on: ubuntu-latest
if: needs.build-check.outputs.is_release == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Publish rc-core
run: cargo publish --package rc-core --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
continue-on-error: true
- name: Wait for crates.io
run: sleep 30
- name: Publish rc-s3
run: cargo publish --package rc-s3 --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
continue-on-error: true
- name: Wait for crates.io
run: sleep 30
- name: Publish rustfs-cli
run: cargo publish --package rustfs-cli --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
continue-on-error: true