Skip to content

Build App

Build App #10

Workflow file for this run

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