feat(phase-2): release v0.1.35 #71
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |