Build App #10
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 App | |
| # Run it manually from the Actions tab ("Run workflow") and choose the target platform: | |
| # android - debug + release APKs (Jetpack Compose UI in source-code/) | |
| # ios - unsigned .ipa + simulator build (SwiftUI UI in ios-ui/; Compose is not available on iOS) | |
| # both - both jobs | |
| # Pushes/PRs that touch the Android source keep building the APKs automatically, as before. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| platform: | |
| description: "Platform to build" | |
| type: choice | |
| required: true | |
| default: android | |
| options: | |
| - android | |
| - ios | |
| - both | |
| push: | |
| branches: [ main ] | |
| paths: | |
| - "source-code/**" | |
| - "ios-ui/**" | |
| - ".github/workflows/build.yml" | |
| pull_request: | |
| branches: [ main ] | |
| paths: | |
| - "source-code/**" | |
| - "ios-ui/**" | |
| - ".github/workflows/build.yml" | |
| jobs: | |
| android: | |
| name: Android - debug + release APKs | |
| # Manual runs honour the chosen platform; push/PR runs always build Android. | |
| if: github.event_name != 'workflow_dispatch' || inputs.platform == 'android' || inputs.platform == 'both' | |
| runs-on: ubuntu-latest | |
| # Hard safety net: this job must never sit "waiting" indefinitely. If anything ever hangs | |
| # again, the run fails loudly after 30 minutes instead of stalling silently. | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| # NOTE: intentionally NOT using a third-party "setup-android" action here. | |
| # GitHub's ubuntu-latest runners already ship a fully installed, license-accepted Android | |
| # SDK at $ANDROID_HOME/$ANDROID_SDK_ROOT. A separate SDK-setup action was previously used | |
| # here and is what caused the workflow to hang "waiting" - it re-installs SDK components | |
| # and can block on interactive license prompts. The Android Gradle Plugin can already | |
| # auto-download any additional platform/build-tools it needs during the build itself, | |
| # using the runner's pre-accepted licenses, so no extra action is required. | |
| - name: Verify preinstalled Android SDK | |
| run: | | |
| echo "ANDROID_HOME=$ANDROID_HOME" | |
| echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" | |
| ls "$ANDROID_HOME" || true | |
| # Generate local.properties for this run instead of relying on one committed to the repo. | |
| # (A hardcoded developer machine path in a committed local.properties previously shadowed | |
| # the CI SDK entirely and broke every build - local.properties is now gitignored.) | |
| - name: Write local.properties | |
| working-directory: source-code | |
| run: echo "sdk.dir=$ANDROID_HOME" > local.properties | |
| - name: Grant execute permission for gradlew | |
| working-directory: source-code | |
| run: chmod +x ./gradlew | |
| - name: Cache Gradle packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: gradle-${{ runner.os }}-${{ hashFiles('source-code/**/*.gradle*', 'source-code/gradle/wrapper/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| gradle-${{ runner.os }}- | |
| - name: Build debug APK | |
| working-directory: source-code | |
| run: ./gradlew assembleDebug --stacktrace --no-daemon | |
| - name: Build release APK | |
| working-directory: source-code | |
| run: ./gradlew assembleRelease --stacktrace --no-daemon | |
| - name: Collect APKs into outputs/ | |
| run: | | |
| mkdir -p outputs | |
| find source-code/app/build/outputs/apk/debug -name "*.apk" -exec cp {} outputs/ \; | |
| find source-code/app/build/outputs/apk/release -name "*.apk" -exec cp {} outputs/ \; | |
| ls -la outputs | |
| - name: Read app version | |
| id: version | |
| run: | | |
| VERSION=$(grep -m1 'versionName' source-code/app/build.gradle.kts | sed -E 's/.*"([^"]+)".*/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Zip APKs | |
| run: | | |
| cd outputs | |
| zip -r "HackerOS-App-v${{ steps.version.outputs.version }}-apks.zip" *.apk | |
| rm -f *.apk | |
| ls -la | |
| - name: Upload outputs artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-outputs | |
| path: outputs/*.zip | |
| if-no-files-found: error | |
| ios: | |
| name: iOS - SwiftUI build (unsigned .ipa) | |
| # Only for manual runs that picked ios/both. Needs no Apple account: the .ipa is unsigned | |
| # (re-sign it with your own certificate, e.g. via Xcode/AltStore/Sideloadly, to install it). | |
| if: github.event_name == 'workflow_dispatch' && (inputs.platform == 'ios' || inputs.platform == 'both') | |
| runs-on: macos-14 | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Select Xcode | |
| # macos-14 runners ship several Xcode versions; pin one explicitly instead of relying on | |
| # whatever happens to be the default, so this build doesn't silently change behaviour. | |
| run: | | |
| sudo xcode-select -s /Applications/Xcode_15.4.app | |
| xcodebuild -version | |
| - name: Install XcodeGen | |
| run: brew install xcodegen | |
| - name: Generate Xcode project | |
| working-directory: ios-ui | |
| run: xcodegen generate | |
| - name: Build for iOS Simulator (compile check) | |
| working-directory: ios-ui | |
| run: | | |
| xcodebuild -project HackerOS.xcodeproj -scheme HackerOS \ | |
| -sdk iphonesimulator -configuration Debug \ | |
| -destination 'generic/platform=iOS Simulator' \ | |
| CODE_SIGNING_ALLOWED=NO build | |
| - name: Build for iOS device (unsigned) | |
| working-directory: ios-ui | |
| run: | | |
| xcodebuild -project HackerOS.xcodeproj -scheme HackerOS \ | |
| -sdk iphoneos -configuration Release -derivedDataPath build \ | |
| CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO build | |
| - name: Package unsigned .ipa | |
| run: | | |
| VERSION=$(grep -m1 'MARKETING_VERSION' ios-ui/project.yml | sed -E 's/.*"([^"]+)".*/\1/') | |
| mkdir -p outputs/Payload | |
| cp -R ios-ui/build/Build/Products/Release-iphoneos/HackerOS.app outputs/Payload/ | |
| cd outputs | |
| zip -qr "HackerOS-App-v${VERSION}-unsigned.ipa" Payload | |
| rm -rf Payload | |
| ls -la | |
| - name: Upload outputs artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ios-outputs | |
| path: outputs/*.ipa | |
| if-no-files-found: error |