Release Build #257
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: Release Build | |
| on: | |
| push: | |
| tags: | |
| - '*.*.*' | |
| - '!*-*' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version number (e.g. 1.0.0). Leave empty for auto-increment." | |
| required: false | |
| default: "" | |
| type: string | |
| platforms: | |
| description: "Platforms to build" | |
| required: true | |
| default: "all" | |
| type: choice | |
| options: | |
| - all | |
| - android | |
| - windows | |
| - linux | |
| - macos | |
| - ios | |
| create_release: | |
| description: "Create a GitHub Release with the artifacts?" | |
| required: false | |
| default: true | |
| type: boolean | |
| keystore_base64: | |
| description: "Base64-encoded keystore (from SAVE-THESE-SECRETS.txt). Overrides ANDROID_KEYSTORE_BASE64 secret." | |
| required: false | |
| default: "" | |
| type: string | |
| key_alias: | |
| description: "Signing key alias (from SAVE-THESE-SECRETS.txt). Overrides ANDROID_SIGNING_KEY_ALIAS secret." | |
| required: false | |
| default: "" | |
| type: string | |
| key_password: | |
| description: "Signing key password (from SAVE-THESE-SECRETS.txt). Overrides ANDROID_SIGNING_KEY_PASSWORD secret." | |
| required: false | |
| default: "" | |
| type: string | |
| permissions: {} | |
| jobs: | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| # Determine version (shared across all platform jobs) | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| prepare: | |
| name: Prepare | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| tag: ${{ steps.version.outputs.tag }} | |
| build_android: ${{ steps.platforms.outputs.android }} | |
| build_windows: ${{ steps.platforms.outputs.windows }} | |
| build_linux: ${{ steps.platforms.outputs.linux }} | |
| build_macos: ${{ steps.platforms.outputs.macos }} | |
| build_ios: ${{ steps.platforms.outputs.ios }} | |
| steps: | |
| - name: Determine version | |
| id: version | |
| run: | | |
| INPUT_VERSION="${{ inputs.version }}" | |
| REF="${{ github.ref_name }}" | |
| if [[ -n "$INPUT_VERSION" && "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| VERSION="$INPUT_VERSION" | |
| echo "Using manually specified version: $VERSION" | |
| elif [[ "$REF" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| VERSION="$REF" | |
| echo "Using version from tag: $VERSION" | |
| else | |
| VERSION="$(date -u +%Y).$(date -u +%-m%d).${{ github.run_number }}" | |
| echo "Auto-generated version: $VERSION" | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Determine platforms | |
| id: platforms | |
| run: | | |
| PLATFORM="${{ inputs.platforms || 'all' }}" | |
| if [ "$PLATFORM" = "all" ] || [ "$PLATFORM" = "android" ]; then echo "android=true" >> "$GITHUB_OUTPUT"; else echo "android=false" >> "$GITHUB_OUTPUT"; fi | |
| if [ "$PLATFORM" = "all" ] || [ "$PLATFORM" = "windows" ]; then echo "windows=true" >> "$GITHUB_OUTPUT"; else echo "windows=false" >> "$GITHUB_OUTPUT"; fi | |
| if [ "$PLATFORM" = "all" ] || [ "$PLATFORM" = "linux" ]; then echo "linux=true" >> "$GITHUB_OUTPUT"; else echo "linux=false" >> "$GITHUB_OUTPUT"; fi | |
| if [ "$PLATFORM" = "all" ] || [ "$PLATFORM" = "macos" ]; then echo "macos=true" >> "$GITHUB_OUTPUT"; else echo "macos=false" >> "$GITHUB_OUTPUT"; fi | |
| if [ "$PLATFORM" = "all" ] || [ "$PLATFORM" = "ios" ]; then echo "ios=true" >> "$GITHUB_OUTPUT"; else echo "ios=false" >> "$GITHUB_OUTPUT"; fi | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| # Android Build | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| build-android: | |
| name: Build Android APK | |
| needs: prepare | |
| if: needs.prepare.outputs.build_android == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: write | |
| packages: read | |
| outputs: | |
| apk_artifact: ${{ steps.find_apk.outputs.artifact_name }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup JDK 17 | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: 17 | |
| - name: Install .NET 10.0.x | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Install .NET Android workload | |
| run: dotnet workload install android | |
| - name: Authenticate to GitHub Packages | |
| run: dotnet nuget update source winnerspiros-github --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text | |
| - name: Ensure Android NDK and CMake are available | |
| run: | | |
| SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" | |
| yes | "$SDKMANAGER" --licenses > /dev/null 2>&1 || true | |
| "$SDKMANAGER" --install "ndk;29.0.14206865" "cmake;3.22.1" > /dev/null 2>&1 | |
| - name: Build native library (libosu_native.so) | |
| run: | | |
| NDK_HOME="$ANDROID_HOME/ndk/29.0.14206865" | |
| CMAKE_BIN="$ANDROID_HOME/cmake/3.22.1/bin/cmake" | |
| for ABI in arm64-v8a; do | |
| echo "::group::Building osu_native for $ABI" | |
| CONFIGURE_ATTEMPTS=5 | |
| for attempt in $(seq 1 $CONFIGURE_ATTEMPTS); do | |
| rm -rf "build-native/$ABI" | |
| if "$CMAKE_BIN" -B "build-native/$ABI" -S osu.Android/Native \ | |
| -DCMAKE_TOOLCHAIN_FILE="$NDK_HOME/build/cmake/android.toolchain.cmake" \ | |
| -DANDROID_ABI="$ABI" \ | |
| -DANDROID_PLATFORM=android-33 \ | |
| -DCMAKE_BUILD_TYPE=Release; then | |
| break | |
| fi | |
| if [ "$attempt" -eq "$CONFIGURE_ATTEMPTS" ]; then | |
| echo "::error::CMake configure failed after $CONFIGURE_ATTEMPTS attempts" | |
| exit 1 | |
| fi | |
| echo "::warning::CMake configure attempt $attempt failed; retrying in $((attempt * 10))s" | |
| sleep $((attempt * 10)) | |
| done | |
| "$CMAKE_BIN" --build "build-native/$ABI" --config Release -j "$(nproc)" | |
| mkdir -p "osu.Android/libs/$ABI" | |
| cp "build-native/$ABI/libosu_native.so" "osu.Android/libs/$ABI/" | |
| echo "::endgroup::" | |
| done | |
| echo "Native libraries built successfully:" | |
| find osu.Android/libs -name "*.so" -exec ls -lh {} \; | |
| - name: Decode or generate keystore | |
| id: keystore | |
| run: | | |
| KS_PATH="${{ github.workspace }}/osu.Android/osu.keystore" | |
| EFFECTIVE_KS="${INPUT_KEYSTORE_BASE64:-$KEYSTORE_BASE64}" | |
| EFFECTIVE_ALIAS="${INPUT_KEY_ALIAS:-$KEY_ALIAS_SECRET}" | |
| EFFECTIVE_PASS="${INPUT_KEY_PASSWORD:-$KEY_PASS_SECRET}" | |
| EFFECTIVE_STORE_PASS="${INPUT_KEY_PASSWORD:-${STORE_PASS_SECRET:-$EFFECTIVE_PASS}}" | |
| if [ -n "$EFFECTIVE_PASS" ]; then echo "::add-mask::$EFFECTIVE_PASS"; fi | |
| if [ -n "$EFFECTIVE_STORE_PASS" ]; then echo "::add-mask::$EFFECTIVE_STORE_PASS"; fi | |
| if [ -n "$EFFECTIVE_KS" ]; then | |
| echo "$EFFECTIVE_KS" | base64 --decode > "$KS_PATH" | |
| echo "has_keystore=true" >> "$GITHUB_OUTPUT" | |
| echo "generated=false" >> "$GITHUB_OUTPUT" | |
| echo "key_alias=${EFFECTIVE_ALIAS:-osu-release}" >> "$GITHUB_OUTPUT" | |
| echo "key_pass=${EFFECTIVE_PASS}" >> "$GITHUB_OUTPUT" | |
| echo "store_pass=${EFFECTIVE_STORE_PASS}" >> "$GITHUB_OUTPUT" | |
| else | |
| AUTO_PASS="osu-$(openssl rand -hex 12)" | |
| echo "::add-mask::$AUTO_PASS" | |
| keytool -genkeypair \ | |
| -keystore "$KS_PATH" \ | |
| -storepass "$AUTO_PASS" \ | |
| -keypass "$AUTO_PASS" \ | |
| -alias osu-release \ | |
| -keyalg RSA -keysize 2048 -validity 10000 \ | |
| -dname "CN=osu! Android,O=osu,C=US" 2>/dev/null | |
| echo "has_keystore=true" >> "$GITHUB_OUTPUT" | |
| echo "generated=true" >> "$GITHUB_OUTPUT" | |
| echo "key_alias=osu-release" >> "$GITHUB_OUTPUT" | |
| echo "key_pass=$AUTO_PASS" >> "$GITHUB_OUTPUT" | |
| echo "store_pass=$AUTO_PASS" >> "$GITHUB_OUTPUT" | |
| { | |
| echo "=== osu! Android Signing Keystore ===" | |
| echo "" | |
| echo "ANDROID_KEYSTORE_BASE64 value (copy everything on the next line):" | |
| base64 -w 0 "$KS_PATH" | |
| echo "" | |
| echo "" | |
| echo "ANDROID_SIGNING_KEY_ALIAS value:" | |
| echo "osu-release" | |
| echo "" | |
| echo "ANDROID_SIGNING_KEY_PASSWORD value:" | |
| echo "$AUTO_PASS" | |
| echo "" | |
| echo "ANDROID_SIGNING_STORE_PASSWORD value:" | |
| echo "$AUTO_PASS" | |
| } > "${{ github.workspace }}/SAVE-THESE-SECRETS.txt" | |
| echo "::warning::No signing keystore secret found — auto-generated one for this build." | |
| fi | |
| env: | |
| KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| KEY_ALIAS_SECRET: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }} | |
| KEY_PASS_SECRET: ${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }} | |
| STORE_PASS_SECRET: ${{ secrets.ANDROID_SIGNING_STORE_PASSWORD }} | |
| INPUT_KEYSTORE_BASE64: ${{ inputs.keystore_base64 }} | |
| INPUT_KEY_ALIAS: ${{ inputs.key_alias }} | |
| INPUT_KEY_PASSWORD: ${{ inputs.key_password }} | |
| - name: Install ffmpeg | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y ffmpeg | |
| - name: Clone and optimize all osu-resources from GitHub | |
| run: | | |
| git clone --depth=1 https://github.com/ppy/osu-resources /tmp/osu-resources | |
| sed -i \ | |
| 's|<PackageLicenseUrl>[^<]*</PackageLicenseUrl>|<PackageLicenseExpression>MIT</PackageLicenseExpression>|g' \ | |
| /tmp/osu-resources/osu.Game.Resources/osu.Game.Resources.csproj | |
| python3 scripts/optimize_resource_overrides.py \ | |
| --root /tmp/osu-resources/osu.Game.Resources \ | |
| --config .github/resource-optimizer/osu-resources-config.json \ | |
| --report-file resource-budget/osu-resources-optimizer-report.json \ | |
| --summary-file resource-budget/osu-resources-optimizer-summary.md | |
| RESOURCES_VERSION="$(date -u +%Y).$(date -u +%-m%d).${{ github.run_number }}" | |
| if dotnet pack /tmp/osu-resources/osu.Game.Resources/osu.Game.Resources.csproj \ | |
| -c Release \ | |
| -o ./local-packages/ \ | |
| -p:Version="${RESOURCES_VERSION}"; then | |
| sed -i \ | |
| "s|ppy.osu.Game.Resources\" Version=\"[^\"]*\"|ppy.osu.Game.Resources\" Version=\"${RESOURCES_VERSION}\"|" \ | |
| osu.Game/osu.Game.csproj | |
| echo "✅ Using optimized osu.Game.Resources ${RESOURCES_VERSION} (local pack)" | |
| else | |
| echo "⚠️ dotnet pack failed; falling back to upstream ppy.osu.Game.Resources from NuGet" | |
| fi | |
| - name: Optimize local override media | |
| run: | | |
| python3 scripts/optimize_resource_overrides.py \ | |
| --root osu.Game/Resources \ | |
| --config .github/resource-optimizer/config.json \ | |
| --report-file resource-budget/optimizer-report.json \ | |
| --summary-file resource-budget/optimizer-summary.md | |
| - name: Build Android APK | |
| run: > | |
| dotnet publish -c Release | |
| osu.Android/osu.Android.csproj | |
| -f net10.0-android | |
| -p:Version="${{ needs.prepare.outputs.version }}" | |
| -p:ApplicationDisplayVersion="${{ needs.prepare.outputs.version }}" | |
| -p:ApplicationVersion="${{ github.run_number }}" | |
| -p:AndroidKeyStore=true | |
| -p:AndroidSigningKeyStore="${{ github.workspace }}/osu.Android/osu.keystore" | |
| -p:AndroidSigningKeyAlias="${{ steps.keystore.outputs.key_alias }}" | |
| -p:AndroidSigningKeyPass="${{ steps.keystore.outputs.key_pass }}" | |
| -p:AndroidSigningStorePass="${{ steps.keystore.outputs.store_pass }}" | |
| - name: Find and rename APK | |
| id: find_apk | |
| run: | | |
| APK=$(find "osu.Android/bin/Release/net10.0-android/publish" -maxdepth 1 -name "*-Signed.apk" 2>/dev/null | head -1) | |
| if [ -z "$APK" ]; then | |
| APK=$(find "osu.Android/bin/Release" -name "*-Signed.apk" | head -1) | |
| fi | |
| if [ -z "$APK" ]; then | |
| echo "::error::Failed to locate signed APK." | |
| find osu.Android/bin -name "*.apk" -o -name "*.aab" 2>/dev/null || true | |
| exit 1 | |
| fi | |
| VERSION="${{ needs.prepare.outputs.version }}" | |
| FINAL_APK="$(dirname "$APK")/osu-lazer-$VERSION.apk" | |
| cp "$APK" "$FINAL_APK" | |
| APK_SIZE=$(stat -c %s "$FINAL_APK" 2>/dev/null || stat -f %z "$FINAL_APK" 2>/dev/null || wc -c < "$FINAL_APK") | |
| APK_SIZE_MB=$((APK_SIZE / 1048576)) | |
| echo "Found APK: $FINAL_APK ($APK_SIZE_MB MB)" | |
| echo "apk_path=$FINAL_APK" >> "$GITHUB_OUTPUT" | |
| echo "artifact_name=osu-android-$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Verify APK signature | |
| run: | | |
| APK="${{ steps.find_apk.outputs.apk_path }}" | |
| APKSIGNER="$ANDROID_HOME/build-tools/$(ls "$ANDROID_HOME/build-tools" | sort -V | tail -1)/apksigner" | |
| "$APKSIGNER" verify --print-certs "$APK" | |
| - name: Verify native libraries in APK | |
| run: | | |
| APK="${{ steps.find_apk.outputs.apk_path }}" | |
| NATIVE_LIBS=$(unzip -l "$APK" | grep "lib/arm64-v8a/.*\.so" || true) | |
| MISSING=0 | |
| for LIB in libbass.so libbass_fx.so libbassmix.so; do | |
| if echo "$NATIVE_LIBS" | grep -q "$LIB"; then | |
| echo "✅ $LIB found" | |
| else | |
| echo "::error::$LIB is MISSING from the APK." | |
| MISSING=1 | |
| fi | |
| done | |
| [ "$MISSING" -eq 0 ] || exit 1 | |
| # Verify not Linux glibc ELFs | |
| TMPDIR=$(mktemp -d) | |
| trap 'rm -rf "$TMPDIR"' EXIT | |
| BAD=0 | |
| for LIB in libbass.so libbass_fx.so libbassmix.so; do | |
| unzip -p "$APK" "lib/arm64-v8a/$LIB" > "$TMPDIR/$LIB" | |
| if strings "$TMPDIR/$LIB" | grep -q "^GLIBC_"; then | |
| echo "::error::$LIB references GLIBC_ — this is a Linux ELF, not Android." | |
| BAD=1 | |
| fi | |
| done | |
| [ "$BAD" -eq 0 ] || exit 1 | |
| echo "All native libraries valid ✓" | |
| - name: Upload APK artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: osu-android-${{ needs.prepare.outputs.version }} | |
| path: ${{ steps.find_apk.outputs.apk_path }} | |
| if-no-files-found: error | |
| - name: Upload generated keystore | |
| if: steps.keystore.outputs.generated == 'true' | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: osu-signing-keystore | |
| path: | | |
| ${{ github.workspace }}/osu.Android/osu.keystore | |
| ${{ github.workspace }}/SAVE-THESE-SECRETS.txt | |
| retention-days: 7 | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| # Windows Build | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| build-windows: | |
| name: Build Windows | |
| needs: prepare | |
| if: needs.prepare.outputs.build_windows == 'true' | |
| runs-on: windows-latest | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: write | |
| packages: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install .NET 10.0.x | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Authenticate to GitHub Packages | |
| run: dotnet nuget update source winnerspiros-github --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text | |
| - name: Publish Windows x64 | |
| run: > | |
| dotnet publish -c Release | |
| osu.Desktop/osu.Desktop.csproj | |
| -r win-x64 | |
| --self-contained | |
| -p:Version="${{ needs.prepare.outputs.version }}" | |
| -p:PublishSingleFile=true | |
| -p:PublishTrimmed=true | |
| -p:TrimMode=partial | |
| -p:EnableCompressionInSingleFile=true | |
| -p:IncludeNativeLibrariesForSelfExtract=true | |
| -p:DebugType=none | |
| -p:DebugSymbols=false | |
| -p:ServerGarbageCollection=true | |
| -p:ConcurrentGarbageCollection=true | |
| -o artifacts/win-x64 | |
| - name: Package Windows build | |
| run: | | |
| $version = "${{ needs.prepare.outputs.version }}" | |
| Compress-Archive -Path artifacts/win-x64/* -DestinationPath "artifacts/osu-lazer-win-x64-$version.zip" | |
| - name: Upload Windows artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: osu-windows-${{ needs.prepare.outputs.version }} | |
| path: artifacts/osu-lazer-win-x64-*.zip | |
| if-no-files-found: error | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| # Linux Build | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| build-linux: | |
| name: Build Linux | |
| needs: prepare | |
| if: needs.prepare.outputs.build_linux == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: write | |
| packages: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install .NET 10.0.x | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Authenticate to GitHub Packages | |
| run: dotnet nuget update source winnerspiros-github --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text | |
| - name: Publish Linux x64 | |
| run: > | |
| dotnet publish -c Release | |
| osu.Desktop/osu.Desktop.csproj | |
| -r linux-x64 | |
| --self-contained | |
| -p:Version="${{ needs.prepare.outputs.version }}" | |
| -p:PublishSingleFile=true | |
| -p:PublishTrimmed=true | |
| -p:TrimMode=partial | |
| -p:EnableCompressionInSingleFile=true | |
| -p:IncludeNativeLibrariesForSelfExtract=true | |
| -p:DebugType=none | |
| -p:DebugSymbols=false | |
| -p:ServerGarbageCollection=true | |
| -p:ConcurrentGarbageCollection=true | |
| -o artifacts/linux-x64 | |
| - name: Package Linux build | |
| run: | | |
| VERSION="${{ needs.prepare.outputs.version }}" | |
| chmod +x artifacts/linux-x64/osu! | |
| tar -czf "artifacts/osu-lazer-linux-x64-$VERSION.tar.gz" -C artifacts/linux-x64 . | |
| - name: Upload Linux artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: osu-linux-${{ needs.prepare.outputs.version }} | |
| path: artifacts/osu-lazer-linux-x64-*.tar.gz | |
| if-no-files-found: error | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| # macOS Build | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| build-macos: | |
| name: Build macOS | |
| needs: prepare | |
| if: needs.prepare.outputs.build_macos == 'true' | |
| runs-on: macos-latest | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: write | |
| packages: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install .NET 10.0.x | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Authenticate to GitHub Packages | |
| run: dotnet nuget update source winnerspiros-github --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text | |
| - name: Publish macOS arm64 | |
| run: > | |
| dotnet publish -c Release | |
| osu.Desktop/osu.Desktop.csproj | |
| -r osx-arm64 | |
| --self-contained | |
| -p:Version="${{ needs.prepare.outputs.version }}" | |
| -p:PublishSingleFile=true | |
| -p:PublishTrimmed=true | |
| -p:TrimMode=partial | |
| -p:EnableCompressionInSingleFile=true | |
| -p:IncludeNativeLibrariesForSelfExtract=true | |
| -p:DebugType=none | |
| -p:DebugSymbols=false | |
| -p:ServerGarbageCollection=true | |
| -p:ConcurrentGarbageCollection=true | |
| -o artifacts/osx-arm64 | |
| - name: Publish macOS x64 | |
| run: > | |
| dotnet publish -c Release | |
| osu.Desktop/osu.Desktop.csproj | |
| -r osx-x64 | |
| --self-contained | |
| -p:Version="${{ needs.prepare.outputs.version }}" | |
| -p:PublishSingleFile=true | |
| -p:PublishTrimmed=true | |
| -p:TrimMode=partial | |
| -p:EnableCompressionInSingleFile=true | |
| -p:IncludeNativeLibrariesForSelfExtract=true | |
| -p:DebugType=none | |
| -p:DebugSymbols=false | |
| -p:ServerGarbageCollection=true | |
| -p:ConcurrentGarbageCollection=true | |
| -o artifacts/osx-x64 | |
| - name: Package macOS builds | |
| run: | | |
| VERSION="${{ needs.prepare.outputs.version }}" | |
| chmod +x "artifacts/osx-arm64/osu!" | |
| chmod +x "artifacts/osx-x64/osu!" | |
| tar -czf "artifacts/osu-lazer-macos-arm64-$VERSION.tar.gz" -C artifacts/osx-arm64 . | |
| tar -czf "artifacts/osu-lazer-macos-x64-$VERSION.tar.gz" -C artifacts/osx-x64 . | |
| - name: Upload macOS artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: osu-macos-${{ needs.prepare.outputs.version }} | |
| path: artifacts/osu-lazer-macos-*.tar.gz | |
| if-no-files-found: error | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| # iOS Build | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| build-ios: | |
| name: Build iOS | |
| needs: prepare | |
| if: needs.prepare.outputs.build_ios == 'true' | |
| runs-on: macos-26 | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: write | |
| packages: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install .NET 10.0.x | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: "10.0.x" | |
| - name: Install .NET iOS workload | |
| run: dotnet workload install ios --skip-manifest-update | |
| - name: Authenticate to GitHub Packages | |
| run: dotnet nuget update source winnerspiros-github --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text | |
| - name: Build iOS (unsigned .app) | |
| run: > | |
| dotnet publish -c Release | |
| osu.iOS/osu.iOS.csproj | |
| -f net10.0-ios | |
| -p:Version="${{ needs.prepare.outputs.version }}" | |
| -p:ApplicationDisplayVersion="${{ needs.prepare.outputs.version }}" | |
| -p:ApplicationVersion="${{ github.run_number }}" | |
| -p:ArchiveOnBuild=false | |
| -p:BuildIpa=false | |
| -p:CodesignKey="" | |
| -p:CodesignProvision="" | |
| - name: Package iOS build | |
| run: | | |
| VERSION="${{ needs.prepare.outputs.version }}" | |
| mkdir -p artifacts | |
| APP_PATH=$(find osu.iOS/bin/Release -name "*.app" -type d | head -1) | |
| if [ -z "$APP_PATH" ]; then | |
| echo "::error::Could not find .app bundle" | |
| exit 1 | |
| fi | |
| tar -czf "artifacts/osu-lazer-ios-$VERSION.tar.gz" -C "$(dirname "$APP_PATH")" "$(basename "$APP_PATH")" | |
| - name: Upload iOS artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: osu-ios-${{ needs.prepare.outputs.version }} | |
| path: artifacts/osu-lazer-ios-*.tar.gz | |
| if-no-files-found: error | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| # Create GitHub Release | |
| # ═══════════════════════════════════════════════════════════════════════════════ | |
| release: | |
| name: Create Release | |
| needs: [prepare, build-android, build-windows, build-linux, build-macos, build-ios] | |
| if: | | |
| always() && | |
| (inputs.create_release == true || github.event_name == 'push') && | |
| !contains(needs.*.result, 'failure') && | |
| !contains(needs.*.result, 'cancelled') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: release-artifacts | |
| pattern: osu-*-${{ needs.prepare.outputs.version }} | |
| merge-multiple: true | |
| continue-on-error: true | |
| - name: Verify artifacts exist | |
| run: | | |
| if [ ! -d release-artifacts ] || [ -z "$(ls -A release-artifacts 2>/dev/null)" ]; then | |
| echo "::error::No release artifacts found" | |
| exit 1 | |
| fi | |
| echo "Release artifacts:" | |
| find release-artifacts -type f | sort | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.tag }} | |
| name: "osu! ${{ needs.prepare.outputs.version }}" | |
| files: release-artifacts/* | |
| generate_release_notes: true | |
| body: | | |
| ## osu! v${{ needs.prepare.outputs.version }} | |
| ### Downloads | |
| | Platform | File | | |
| |---|---| | |
| | Android (arm64) | `osu-lazer-*.apk` | | |
| | Windows (x64) | `osu-lazer-win-x64-*.zip` | | |
| | Linux (x64) | `osu-lazer-linux-x64-*.tar.gz` | | |
| | macOS (Apple Silicon) | `osu-lazer-macos-arm64-*.tar.gz` | | |
| | macOS (Intel) | `osu-lazer-macos-x64-*.tar.gz` | | |
| | iOS (unsigned) | `osu-lazer-ios-*.tar.gz` | | |
| > **Android:** Requires Android 13+ (arm64). Enable "Install from unknown sources" if prompted. | |
| > **iOS:** Unsigned build — requires sideloading via AltStore, Sideloadly, or Xcode. | |
| > **Desktop:** Self-contained, no .NET runtime needed. | |
| make_latest: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |