Skip to content

feat(phase-2): release v0.1.35 #71

feat(phase-2): release v0.1.35

feat(phase-2): release v0.1.35 #71

Workflow file for this run

name: Build and Release
on:
push:
tags:
- "v*.*.*"
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
permissions:
contents: read
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 }}
has_token: ${{ steps.check.outputs.has_token }}
source_ref: ${{ steps.check.outputs.source_ref }}
source_sha: ${{ steps.check.outputs.source_sha }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
- name: Determine build strategy
id: check
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_TAG: ${{ github.event.inputs.tag }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
version=""
is_release=false
source_ref="${GITHUB_SHA}"
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
# Tag push - get version from tag
version="${GITHUB_REF#refs/tags/}"
source_ref="${GITHUB_REF}"
is_release=true
echo "🏷️ Tag build detected: $version"
elif [[ "$EVENT_NAME" == "workflow_dispatch" ]] && [[ -n "$INPUT_TAG" ]]; then
# Manual trigger with tag input - treat as release
version="$INPUT_TAG"
if [[ ! "$version" =~ ^v ]]; then
version="v${version}"
fi
source_ref="refs/tags/${version}"
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
if [[ "$is_release" == "true" ]]; then
git rev-parse --verify "${source_ref}^{commit}" >/dev/null
workspace_version=$(git show "${source_ref}:Cargo.toml" | awk -F'"' '/^version = "/ { print $2; exit }')
if [[ -z "$workspace_version" || "${version#v}" != "$workspace_version" ]]; then
echo "Release tag '$version' does not match workspace version '$workspace_version'"
exit 1
fi
fi
source_sha=$(git rev-parse "${source_ref}^{commit}")
# Ensure version starts with 'v' for release builds
if [[ "$is_release" == "true" ]] && [[ ! "$version" =~ ^v ]]; then
version="v${version}"
echo "ℹ️ Version normalized to: $version"
fi
has_token="false"
if [[ -n "$CARGO_REGISTRY_TOKEN" ]]; then
has_token="true"
fi
{
echo "version=$version"
echo "is_release=$is_release"
echo "has_token=$has_token"
echo "source_ref=$source_ref"
echo "source_sha=$source_sha"
} >> "$GITHUB_OUTPUT"
echo "📊 Build Summary:"
echo " - Version: $version"
echo " - Is release: $is_release"
echo " - Publish crate: $has_token"
verify:
name: Release Verification
needs: build-check
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ needs.build-check.outputs.source_ref }}
- name: Setup Rust
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
with:
components: rustfmt, clippy
- name: Setup Rust cache
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Check formatting
run: cargo fmt --all --check
- name: Run Clippy
run: cargo clippy --workspace --all-targets --locked -- -D warnings
- name: Run tests
run: cargo test --workspace --locked
- name: Check dependency advisories
uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2
with:
command: check advisories
build:
name: Build (${{ matrix.os_name }}-${{ matrix.arch }})
needs: [build-check, verify]
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-gnu
artifact_name: rc
cross: false
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
os_name: linux
arch: arm64-gnu
artifact_name: rc
cross: true
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
os_name: linux
arch: amd64
artifact_name: rc
cross: false
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ needs.build-check.outputs.source_ref }}
- name: Setup Rust
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
with:
targets: ${{ matrix.target }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
key: ${{ matrix.target }}
- name: Add Rust target
run: rustup target add ${{ matrix.target }}
- name: Install musl-tools
if: contains(matrix.target, 'musl')
run: sudo apt-get update && sudo apt-get install -y musl-tools
# Install cross for cross-compilation
- name: Install cross
if: matrix.cross
run: cargo install cross --version 0.2.5 --locked
# Build
- name: Build (native)
if: "!matrix.cross"
run: cargo build --release --locked --target ${{ matrix.target }}
- name: Build (cross)
if: matrix.cross
run: cross build --release --locked --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
cp LICENSE-MIT LICENSE-APACHE target/${{ matrix.target }}/release/
cd target/${{ matrix.target }}/release
tar -czvf "../../../artifacts/${ARTIFACT_PREFIX}-${VERSION}.tar.gz" ${{ matrix.artifact_name }} LICENSE-MIT LICENSE-APACHE
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, "LICENSE-MIT", "LICENSE-APACHE" -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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: rustfs-cli-${{ matrix.os_name }}-${{ matrix.arch }}
path: artifacts/*
if-no-files-found: error
# Upload to Cloudflare R2
- name: Upload to Cloudflare R2
if: needs.build-check.outputs.is_release == 'true'
env:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
shell: bash
run: |
if [[ -z "$R2_ACCESS_KEY_ID" || -z "$R2_SECRET_ACCESS_KEY" || -z "$R2_ENDPOINT" || -z "$R2_BUCKET" ]]; then
echo "⚠️ R2 credentials or endpoint missing, skipping artifact upload"
exit 0
fi
command -v aws >/dev/null 2>&1 || { echo "AWS CLI is required on the release runner"; exit 1; }
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY_ID"
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY"
export AWS_DEFAULT_REGION="auto"
# AWS CLI v2.23+ enables data integrity checksums by default, which
# Cloudflare R2 does not fully support; only send them when required.
export AWS_REQUEST_CHECKSUM_CALCULATION="when_required"
export AWS_RESPONSE_CHECKSUM_VALIDATION="when_required"
R2_PATH="s3://${R2_BUCKET}/artifacts/rustfs-cli/release/"
# Upload all artifacts to R2
echo "📤 Uploading artifacts to $R2_PATH..."
for file in artifacts/*; do
if [[ -f "$file" ]]; then
echo "Uploading: $file"
aws s3 cp "$file" "$R2_PATH" --endpoint-url "$R2_ENDPOINT" --only-show-errors
echo "✅ Uploaded: $(basename "$file")"
fi
done
echo "✅ R2 upload completed successfully"
# Generate shell completions
completions:
name: Generate Shell Completions
needs: [build-check, verify]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ needs.build-check.outputs.source_ref }}
- name: Setup Rust
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
- name: Setup Rust cache
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Build
run: cargo build --release --locked
- 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/
- name: Upload completions artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: completions
path: completions.tar.gz
if-no-files-found: error
# 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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
path: artifacts
pattern: rustfs-cli-*
merge-multiple: true
- name: Download completions
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: completions
path: artifacts
- 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)
shopt -s nullglob
artifact_files=(./artifacts/*)
if (( ${#artifact_files[@]} > 0 )); then
for file in "${artifact_files[@]}"; 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
release_files=(*.tar.gz *.zip)
if (( ${#release_files[@]} > 0 )); then
sha256sum -- "${release_files[@]}" > SHA256SUMS
echo "✅ SHA256SUMS generated"
fi
echo "📦 Release assets:"
ls -la
- name: Upload release assets to Cloudflare R2
continue-on-error: true
env:
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
RELEASE_VERSION: ${{ needs.build-check.outputs.version }}
shell: bash
run: |
if [[ -z "$R2_ACCESS_KEY_ID" || -z "$R2_SECRET_ACCESS_KEY" || -z "$R2_ENDPOINT" || -z "$R2_BUCKET" ]]; then
echo "⚠️ R2 credentials or endpoint missing, skipping release asset upload"
exit 0
fi
command -v aws >/dev/null 2>&1 || { echo "AWS CLI is required on the release runner"; exit 1; }
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY_ID"
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY"
export AWS_DEFAULT_REGION="auto"
# AWS CLI v2.23+ enables data integrity checksums by default, which
# Cloudflare R2 does not fully support; only send them when required.
export AWS_REQUEST_CHECKSUM_CALCULATION="when_required"
export AWS_RESPONSE_CHECKSUM_VALIDATION="when_required"
R2_PATH="s3://${R2_BUCKET}/artifacts/rustfs-cli/release/${RELEASE_VERSION}/"
echo "📤 Uploading release assets to $R2_PATH..."
for file in release-assets/*; do
if [[ -f "$file" ]]; then
echo "Uploading: $file"
aws s3 cp "$file" "$R2_PATH" --endpoint-url "$R2_ENDPOINT" --only-show-errors
echo "✅ Uploaded: $(basename "$file")"
fi
done
echo "✅ Release asset R2 upload completed successfully"
- name: Upload to GitHub Release
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
tag_name: ${{ needs.build-check.outputs.version }}
name: Release ${{ needs.build-check.outputs.version }}
files: release-assets/*
draft: true
prerelease: false
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Build distribution packages from the exact artifacts produced above.
package-deb-rpm:
name: Package DEB/RPM
needs: [build-check, upload-release-assets]
if: needs.build-check.outputs.is_release == 'true'
uses: ./.github/workflows/package.yml
with:
tag: ${{ needs.build-check.outputs.version }}
build_run_id: ${{ github.run_id }}
head_sha: ${{ needs.build-check.outputs.source_sha }}
secrets: inherit
permissions:
contents: write
actions: read
# Publish to crates.io
publish-crates:
name: Publish to crates.io
needs: [build-check, verify, build]
runs-on: ubuntu-latest
if: needs.build-check.outputs.is_release == 'true' && needs.build-check.outputs.has_token == 'true'
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ needs.build-check.outputs.source_ref }}
- name: Setup Rust
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
- name: Publish rc-core
run: cargo publish --locked --package rc-core
- name: Wait for crates.io
run: sleep 30
- name: Publish rc-s3
run: cargo publish --locked --package rc-s3
- name: Wait for crates.io
run: sleep 30
- name: Publish rustfs-cli
run: cargo publish --locked --package rustfs-cli