# Copyright 2024 RustFS Team # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Build and Release on: push: tags: ["*"] branches: [main] paths-ignore: - "**.md" - "**.txt" - ".github/**" - "docs/**" - "deploy/**" - "scripts/dev_*.sh" - "LICENSE*" - "README*" - "**/*.png" - "**/*.jpg" - "**/*.svg" - ".gitignore" - ".dockerignore" pull_request: branches: [main] paths-ignore: - "**.md" - "**.txt" - ".github/**" - "docs/**" - "deploy/**" - "scripts/dev_*.sh" - "LICENSE*" - "README*" - "**/*.png" - "**/*.jpg" - "**/*.svg" - ".gitignore" - ".dockerignore" schedule: - cron: "0 0 * * 0" # Weekly on Sunday at midnight UTC workflow_dispatch: inputs: force_build: description: "Force build even without changes" required: false default: false type: boolean env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 # Optimize build performance CARGO_INCREMENTAL: 0 jobs: # Second layer: Business logic level checks (handling build strategy) build-check: name: Build Strategy Check runs-on: ubuntu-latest outputs: should_build: ${{ steps.check.outputs.should_build }} build_type: ${{ steps.check.outputs.build_type }} steps: - name: Determine build strategy id: check run: | should_build=false build_type="none" # Business logic: when we need to build if [[ "${{ github.event_name }}" == "schedule" ]] || \ [[ "${{ github.event_name }}" == "workflow_dispatch" ]] || \ [[ "${{ github.event.inputs.force_build }}" == "true" ]] || \ [[ "${{ contains(github.event.head_commit.message, '--build') }}" == "true" ]]; then should_build=true build_type="development" fi # Always build for tag pushes (version releases) if [[ "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]]; then should_build=true build_type="release" echo "🏷️ Tag detected: forcing release build" fi echo "should_build=$should_build" >> $GITHUB_OUTPUT echo "build_type=$build_type" >> $GITHUB_OUTPUT echo "Build needed: $should_build (type: $build_type)" # Build RustFS binaries build-rustfs: name: Build RustFS needs: [build-check] if: needs.build-check.outputs.should_build == 'true' runs-on: ${{ matrix.os }} timeout-minutes: 60 env: RUSTFLAGS: ${{ matrix.cross == 'false' && '-C target-cpu=native' || '' }} strategy: fail-fast: false matrix: include: # Linux builds - os: ubuntu-latest target: x86_64-unknown-linux-musl cross: false platform: linux - os: ubuntu-latest target: aarch64-unknown-linux-musl cross: true platform: linux # macOS builds - os: macos-latest target: aarch64-apple-darwin cross: false platform: macos - os: macos-latest target: x86_64-apple-darwin cross: false platform: macos # # Windows builds (temporarily disabled) # - os: windows-latest # target: x86_64-pc-windows-msvc # cross: false # platform: windows # - os: windows-latest # target: aarch64-pc-windows-msvc # cross: true # platform: windows steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Rust environment uses: ./.github/actions/setup with: rust-version: stable target: ${{ matrix.target }} cache-shared-key: build-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} github-token: ${{ secrets.GITHUB_TOKEN }} cache-save-if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }} install-cross-tools: ${{ matrix.cross }} - name: Download static console assets run: | mkdir -p ./rustfs/static if [[ "${{ matrix.platform }}" == "windows" ]]; then curl.exe -L "https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip" -o console.zip --retry 3 --retry-delay 5 --max-time 300 if [[ $? -eq 0 ]]; then unzip -o console.zip -d ./rustfs/static rm console.zip else echo "Warning: Failed to download console assets, continuing without them" echo "// Static assets not available" > ./rustfs/static/empty.txt fi else curl -L "https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip" \ -o console.zip --retry 3 --retry-delay 5 --max-time 300 if [[ $? -eq 0 ]]; then unzip -o console.zip -d ./rustfs/static rm console.zip else echo "Warning: Failed to download console assets, continuing without them" echo "// Static assets not available" > ./rustfs/static/empty.txt fi fi - name: Build RustFS run: | # Force rebuild by touching build.rs touch rustfs/build.rs if [[ "${{ matrix.cross }}" == "true" ]]; then if [[ "${{ matrix.platform }}" == "windows" ]]; then # Use cross for Windows ARM64 cargo install cross --git https://github.com/cross-rs/cross cross build --release --target ${{ matrix.target }} -p rustfs --bins else # Use zigbuild for Linux ARM64 cargo zigbuild --release --target ${{ matrix.target }} -p rustfs --bins fi else cargo build --release --target ${{ matrix.target }} -p rustfs --bins fi - name: Create release package id: package shell: bash run: | PACKAGE_NAME="rustfs-${{ matrix.target }}" # Create zip packages for all platforms # Ensure zip is available if ! command -v zip &> /dev/null; then if [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then sudo apt-get update && sudo apt-get install -y zip fi fi cd target/${{ matrix.target }}/release zip "../../../${PACKAGE_NAME}.zip" rustfs cd ../../.. echo "package_name=${PACKAGE_NAME}" >> $GITHUB_OUTPUT echo "package_file=${PACKAGE_NAME}.zip" >> $GITHUB_OUTPUT echo "Package created: ${PACKAGE_NAME}.zip" - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: ${{ steps.package.outputs.package_name }} path: ${{ steps.package.outputs.package_file }} retention-days: ${{ startsWith(github.ref, 'refs/tags/') && 30 || 7 }} - name: Upload to Aliyun OSS if: needs.build-check.outputs.build_type == 'release' && env.OSS_ACCESS_KEY_ID != '' 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 run: | # Install ossutil (platform-specific) OSSUTIL_VERSION="2.1.1" case "${{ matrix.platform }}" in linux) if [[ "$(uname -m)" == "arm64" ]]; then ARCH="arm64" else ARCH="amd64" fi OSSUTIL_ZIP="ossutil-${OSSUTIL_VERSION}-linux-${ARCH}.zip" OSSUTIL_DIR="ossutil-${OSSUTIL_VERSION}-linux-${ARCH}" curl -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}" unzip "$OSSUTIL_ZIP" mv "${OSSUTIL_DIR}/ossutil" /usr/local/bin/ rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP" chmod +x /usr/local/bin/ossutil OSSUTIL_BIN=ossutil ;; 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 -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}" unzip "$OSSUTIL_ZIP" mv "${OSSUTIL_DIR}/ossutil" /usr/local/bin/ rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP" chmod +x /usr/local/bin/ossutil OSSUTIL_BIN=ossutil ;; esac # Upload the package file directly to OSS echo "Uploading ${{ steps.package.outputs.package_file }} to OSS..." $OSSUTIL_BIN cp "${{ steps.package.outputs.package_file }}" oss://rustfs-artifacts/artifacts/rustfs/ --force # Create latest.json (only for the first Linux build to avoid duplication) if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-musl" ]]; then VERSION="${GITHUB_REF#refs/tags/v}" echo "{\"version\":\"${VERSION}\",\"release_date\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > latest.json $OSSUTIL_BIN cp latest.json oss://rustfs-version/latest.json --force fi # Release management release: name: GitHub Release needs: [build-check, build-rustfs] if: always() && needs.build-check.outputs.build_type == 'release' runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download all artifacts uses: actions/download-artifact@v4 with: path: ./release-artifacts - name: Prepare release assets id: release_prep run: | VERSION="${GITHUB_REF#refs/tags/}" VERSION_CLEAN="${VERSION#v}" echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "version_clean=${VERSION_CLEAN}" >> $GITHUB_OUTPUT # Organize artifacts mkdir -p ./release-files # Copy all artifacts (.zip files) find ./release-artifacts -name "*.zip" -exec cp {} ./release-files/ \; # Generate checksums for all files cd ./release-files if ls *.zip >/dev/null 2>&1; then sha256sum *.zip >> SHA256SUMS sha512sum *.zip >> SHA512SUMS fi cd .. # Display what we're releasing echo "=== Release Files ===" ls -la ./release-files/ - name: Create GitHub Release env: GH_TOKEN: ${{ github.token }} run: | VERSION="${{ steps.release_prep.outputs.version }}" VERSION_CLEAN="${{ steps.release_prep.outputs.version_clean }}" # Check if release already exists if gh release view "$VERSION" >/dev/null 2>&1; then echo "Release $VERSION already exists, skipping creation" else # Get release notes from tag message RELEASE_NOTES=$(git tag -l --format='%(contents)' "${VERSION}") if [[ -z "$RELEASE_NOTES" || "$RELEASE_NOTES" =~ ^[[:space:]]*$ ]]; then RELEASE_NOTES="Release ${VERSION_CLEAN}" fi # Determine if this is a prerelease PRERELEASE_FLAG="" if [[ "$VERSION" == *"alpha"* ]] || [[ "$VERSION" == *"beta"* ]] || [[ "$VERSION" == *"rc"* ]]; then PRERELEASE_FLAG="--prerelease" fi # Create the release only if it doesn't exist gh release create "$VERSION" \ --title "RustFS $VERSION_CLEAN" \ --notes "$RELEASE_NOTES" \ $PRERELEASE_FLAG fi - name: Upload release assets env: GH_TOKEN: ${{ github.token }} run: | VERSION="${{ steps.release_prep.outputs.version }}" cd ./release-files # Upload all binary files for file in *.zip; do if [[ -f "$file" ]]; then echo "Uploading $file..." gh release upload "$VERSION" "$file" --clobber fi done # Upload checksum files if [[ -f "SHA256SUMS" ]]; then echo "Uploading SHA256SUMS..." gh release upload "$VERSION" "SHA256SUMS" --clobber fi if [[ -f "SHA512SUMS" ]]; then echo "Uploading SHA512SUMS..." gh release upload "$VERSION" "SHA512SUMS" --clobber fi - name: Update release notes env: GH_TOKEN: ${{ github.token }} run: | VERSION="${{ steps.release_prep.outputs.version }}" VERSION_CLEAN="${{ steps.release_prep.outputs.version_clean }}" # Check if release already has custom notes (not auto-generated) EXISTING_NOTES=$(gh release view "$VERSION" --json body --jq '.body' 2>/dev/null || echo "") # Only update if release notes are empty or auto-generated if [[ -z "$EXISTING_NOTES" ]] || [[ "$EXISTING_NOTES" == *"Release ${VERSION_CLEAN}"* ]]; then echo "Updating release notes for $VERSION" # Get original release notes from tag ORIGINAL_NOTES=$(git tag -l --format='%(contents)' "${VERSION}") if [[ -z "$ORIGINAL_NOTES" || "$ORIGINAL_NOTES" =~ ^[[:space:]]*$ ]]; then ORIGINAL_NOTES="Release ${VERSION_CLEAN}" fi # Use external template file and substitute variables sed -e "s/\${VERSION}/$VERSION/g" \ -e "s/\${VERSION_CLEAN}/$VERSION_CLEAN/g" \ -e "s/\${ORIGINAL_NOTES}/$(echo "$ORIGINAL_NOTES" | sed 's/[[\.*^$()+?{|]/\\&/g')/g" \ .github/workflows/release-notes-template.md > enhanced_notes.md # Update the release with enhanced notes gh release edit "$VERSION" --notes-file enhanced_notes.md else echo "Release $VERSION already has custom notes, skipping update to preserve manual edits" fi