Build and Release #28
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: | |
| - "*.*.*" | |
| 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 | |
| jobs: | |
| # Build strategy check - determine build type and version | |
| build-check: | |
| name: Build Strategy Check | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| should_build: ${{ steps.check.outputs.should_build }} | |
| 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: | | |
| should_build=true | |
| 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 "should_build=$should_build" >> $GITHUB_OUTPUT | |
| echo "version=$version" >> $GITHUB_OUTPUT | |
| echo "is_release=$is_release" >> $GITHUB_OUTPUT | |
| echo "📊 Build Summary:" | |
| echo " - Should build: $should_build" | |
| echo " - Version: $version" | |
| echo " - Is release: $is_release" | |
| build: | |
| name: Build | |
| needs: build-check | |
| if: needs.build-check.outputs.should_build == 'true' | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # - platform: "macos-latest" | |
| # rust_target: "aarch64-apple-darwin" | |
| # os_name: "macos" | |
| # arch: "aarch64" | |
| # - platform: "macos-13" | |
| # rust_target: "x86_64-apple-darwin" | |
| # os_name: "macos" | |
| # arch: "x86_64" | |
| - platform: "windows-latest" | |
| rust_target: "x86_64-pc-windows-msvc" | |
| os_name: "windows" | |
| arch: "x86_64" | |
| runs-on: ${{ matrix.platform }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.rust_target }} | |
| - name: Install WebAssembly target | |
| run: rustup target add wasm32-unknown-unknown | |
| - name: Setup Rust cache | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src-tauri | |
| - name: Install dependencies (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| brew install trunk | |
| cargo install tauri-cli | |
| - name: Install dependencies (Windows) | |
| if: runner.os == 'Windows' | |
| run: | | |
| cargo install trunk tauri-cli | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| - name: Download RustFS binary (macOS aarch64) | |
| if: matrix.platform == 'macos-latest' | |
| run: | | |
| mkdir -p src-tauri/binaries | |
| curl -L -o rustfs-macos-aarch64.zip https://dl.rustfs.com/artifacts/rustfs/release/rustfs-macos-aarch64-latest.zip | |
| unzip -q rustfs-macos-aarch64.zip -d rustfs-temp | |
| cp rustfs-temp/rustfs src-tauri/binaries/rustfs-macos-aarch64 | |
| chmod +x src-tauri/binaries/rustfs-macos-aarch64 | |
| rm -rf rustfs-temp rustfs-macos-aarch64.zip | |
| - name: Download RustFS binary (macOS x86_64) | |
| if: matrix.platform == 'macos-13' | |
| run: | | |
| mkdir -p src-tauri/binaries | |
| curl -L -o rustfs-macos-x86_64.zip https://dl.rustfs.com/artifacts/rustfs/release/rustfs-macos-x86_64-latest.zip | |
| unzip -q rustfs-macos-x86_64.zip -d rustfs-temp | |
| cp rustfs-temp/rustfs src-tauri/binaries/rustfs-macos-x86_64 | |
| chmod +x src-tauri/binaries/rustfs-macos-x86_64 | |
| rm -rf rustfs-temp rustfs-macos-x86_64.zip | |
| - name: Download RustFS binary (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path src-tauri\binaries | |
| Invoke-WebRequest -Uri "https://dl.rustfs.com/artifacts/rustfs/release/rustfs-windows-x86_64-latest.zip" -OutFile "rustfs-windows-x86_64.zip" | |
| Expand-Archive -Path "rustfs-windows-x86_64.zip" -DestinationPath "rustfs-temp" -Force | |
| Copy-Item "rustfs-temp\rustfs.exe" -Destination "src-tauri\binaries\rustfs-windows-x86_64.exe" | |
| Remove-Item -Recurse -Force rustfs-temp, rustfs-windows-x86_64.zip | |
| - name: Build Tauri application | |
| run: cargo tauri build --target ${{ matrix.rust_target }} | |
| - name: Prepare and rename artifacts (macOS) | |
| if: runner.os == 'macOS' | |
| env: | |
| VERSION: ${{ needs.build-check.outputs.version }} | |
| run: | | |
| mkdir -p artifacts | |
| # 允许空 glob 返回空数组,避免 zip 在找不到文件时直接失败 | |
| shopt -s nullglob | |
| # Format: rustfs-launcher-{os}-{arch}-{version} | |
| ARTIFACT_PREFIX="rustfs-launcher-${{ matrix.os_name }}-${{ matrix.arch }}-${VERSION}" | |
| # 在 workspace 内搜索打包输出,不依赖固定路径 | |
| APP_DIR=$(find src-tauri target -path "*bundle/macos/*.app" -type d -print -quit 2>/dev/null || true) | |
| DMG_FILE=$(find src-tauri target -path "*bundle/dmg/*.dmg" -type f -print -quit 2>/dev/null || true) | |
| echo "发现的 .app 目录:${APP_DIR:-无}" | |
| echo "发现的 .dmg 文件:${DMG_FILE:-无}" | |
| # Copy and rename DMG | |
| if [ -n "$DMG_FILE" ]; then | |
| cp "$DMG_FILE" "artifacts/${ARTIFACT_PREFIX}.dmg" | |
| echo "✅ Created: ${ARTIFACT_PREFIX}.dmg" | |
| fi | |
| # Create and rename app.zip | |
| if [ -n "$APP_DIR" ]; then | |
| APP_PARENT="$(dirname "$APP_DIR")" | |
| APP_NAME="$(basename "$APP_DIR")" | |
| cd "$APP_PARENT" | |
| zip -r "$GITHUB_WORKSPACE/artifacts/${ARTIFACT_PREFIX}.app.zip" "$APP_NAME" | |
| echo "✅ Created: ${ARTIFACT_PREFIX}.app.zip" | |
| else | |
| echo "❌ 未找到 .app 产物" | |
| exit 1 | |
| fi | |
| # Create latest version files | |
| cd "$GITHUB_WORKSPACE/artifacts" | |
| LATEST_PREFIX="rustfs-launcher-${{ matrix.os_name }}-${{ matrix.arch }}-latest" | |
| for file in rustfs-launcher-*-${VERSION}.*; do | |
| if [ -f "$file" ]; then | |
| ext="${file##*.}" | |
| cp "$file" "${LATEST_PREFIX}.${ext}" | |
| echo "✅ Created latest: ${LATEST_PREFIX}.${ext}" | |
| fi | |
| done | |
| # 确保确实生成了产物 | |
| if [ $(ls -1 | wc -l) -eq 0 ]; then | |
| echo "❌ 未生成任何 artifacts" | |
| exit 1 | |
| fi | |
| ls -la "$GITHUB_WORKSPACE/artifacts" | |
| - name: Prepare and rename 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-launcher-{os}-{arch}-{version} | |
| $ARTIFACT_PREFIX = "rustfs-launcher-${{ matrix.os_name }}-${{ matrix.arch }}-$env:VERSION" | |
| # 调试:列出可能的构建输出目录 | |
| $targetPath = "target/${{ matrix.rust_target }}/release/bundle" | |
| if (-not (Test-Path $targetPath)) { | |
| $targetPath = "src-tauri/target/${{ matrix.rust_target }}/release/bundle" | |
| } | |
| Write-Host "🔍 搜索构建产物目录: $targetPath" | |
| if (Test-Path $targetPath) { | |
| Write-Host "📁 Bundle 目录存在,内容:" | |
| Get-ChildItem -Path $targetPath -Recurse -File | Select-Object FullName, Length | Format-Table -AutoSize | |
| } else { | |
| Write-Host "❌ Bundle 目录不存在: $targetPath" | |
| } | |
| $artifactCount = 0 | |
| # Copy and rename MSI | |
| # $msiPath = "$targetPath/msi" | |
| # Write-Host "🔍 搜索 MSI 文件: $msiPath" | |
| # $msiFiles = Get-ChildItem -Path $msiPath -Filter "*.msi" -ErrorAction SilentlyContinue | |
| # if ($msiFiles) { | |
| # $destPath = "artifacts/$ARTIFACT_PREFIX.msi" | |
| # Copy-Item $msiFiles[0].FullName -Destination $destPath | |
| # Write-Host "✅ Created: $destPath" | |
| # $artifactCount++ | |
| # } else { | |
| # Write-Host "⚠️ 未找到 MSI 文件在: $msiPath" | |
| # } | |
| # Copy and rename EXE (NSIS installer) | |
| $nsisPath = "$targetPath/nsis" | |
| Write-Host "🔍 搜索 NSIS EXE 文件: $nsisPath" | |
| $exeFiles = Get-ChildItem -Path $nsisPath -Filter "*.exe" -ErrorAction SilentlyContinue | |
| if ($exeFiles) { | |
| $destPath = "artifacts/$ARTIFACT_PREFIX-setup.exe" | |
| Copy-Item $exeFiles[0].FullName -Destination $destPath | |
| Write-Host "✅ Created: $destPath" | |
| $artifactCount++ | |
| } else { | |
| Write-Host "⚠️ 未找到 NSIS EXE 文件在: $nsisPath" | |
| } | |
| # 检查是否至少生成了一个产物 | |
| if ($artifactCount -eq 0) { | |
| Write-Host "❌ 错误:未找到任何构建产物(MSI 或 EXE)" | |
| Write-Host "请检查构建是否成功完成" | |
| exit 1 | |
| } | |
| # Create latest version files | |
| $LATEST_PREFIX = "rustfs-launcher-${{ matrix.os_name }}-${{ matrix.arch }}-latest" | |
| Get-ChildItem -Path "artifacts" -Filter "rustfs-launcher-*-$env:VERSION.*" | ForEach-Object { | |
| $ext = $_.Extension | |
| if ($_.Name -like "*-setup.exe") { | |
| # Create the -setup.exe version | |
| $setupName = "$LATEST_PREFIX-setup.exe" | |
| Copy-Item $_.FullName -Destination "artifacts/$setupName" | |
| Write-Host "✅ Created latest: $setupName" | |
| # Create the plain .exe version (as requested) | |
| $plainName = "$LATEST_PREFIX.exe" | |
| Copy-Item $_.FullName -Destination "artifacts/$plainName" | |
| Write-Host "✅ Created latest: $plainName" | |
| } else { | |
| $latestName = "$LATEST_PREFIX$ext" | |
| Copy-Item $_.FullName -Destination "artifacts/$latestName" | |
| Write-Host "✅ Created latest: $latestName" | |
| } | |
| } | |
| # 只保留 NSIS EXE 文件,删除 MSI 文件 | |
| Get-ChildItem -Path "artifacts" -Filter "*.msi" | Remove-Item -Force | |
| Write-Host "🗑️ 已删除 MSI 文件,只保留 EXE 安装包" | |
| Write-Host "📦 最终 artifacts 列表:" | |
| Get-ChildItem -Path artifacts | Format-Table Name, Length -AutoSize | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: rustfs-launcher-${{ matrix.os_name }}-${{ matrix.arch }} | |
| path: artifacts/* | |
| if-no-files-found: error | |
| - 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 -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 | |
| ;; | |
| windows) | |
| OSSUTIL_ZIP="ossutil-${OSSUTIL_VERSION}-windows-amd64.zip" | |
| OSSUTIL_DIR="ossutil-${OSSUTIL_VERSION}-windows-amd64" | |
| curl -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}" | |
| unzip "$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-launcher/release/" | |
| # Upload all artifacts to OSS | |
| echo "📤 Uploading artifacts to $OSS_PATH..." | |
| for file in artifacts/*; do | |
| if [[ -f "$file" ]]; then | |
| echo "Uploading: $file to $OSS_PATH..." | |
| $OSSUTIL_BIN cp "$file" "$OSS_PATH" --force | |
| echo "✅ Uploaded: $file" | |
| fi | |
| done | |
| echo "✅ OSS upload completed successfully" | |
| # Upload release assets | |
| upload-release-assets: | |
| name: Upload Release Assets | |
| needs: [build-check, build] | |
| 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-launcher-* | |
| merge-multiple: true | |
| - name: Display structure of downloaded files | |
| run: ls -R artifacts | |
| - name: Prepare release assets | |
| run: | | |
| mkdir -p release-assets | |
| # Copy all artifacts | |
| if [ -d "artifacts" ] && [ "$(ls -A artifacts)" ]; then | |
| cp artifacts/* release-assets/ | |
| echo "✅ Artifacts copied to release-assets" | |
| else | |
| echo "⚠️ No artifacts found to copy" | |
| fi | |
| # Generate checksums | |
| cd release-assets | |
| if ls *.dmg *.zip *.msi *.exe 2>/dev/null; then | |
| sha256sum * > SHA256SUMS | |
| echo "✅ SHA256SUMS generated" | |
| else | |
| echo "⚠️ No files found for checksum generation" | |
| fi | |
| echo "📦 Release assets:" | |
| ls -la | |
| - name: Upload to GitHub Release (release event) | |
| if: github.event_name == 'release' | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ github.event.release.tag_name }} | |
| files: release-assets/* | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload to GitHub Release (tag push) | |
| if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ needs.build-check.outputs.version }} | |
| files: release-assets/* | |
| draft: false | |
| prerelease: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |