name: Build AAPS run-name: Build AAPS ${{ inputs.version }} (${{ inputs.variant }}) # ============================================================================================ # HOW TO UPDATE YOUR COPY: copy ALL of this file (copy icon at the top right of this page) # and paste it over your own .github/workflows/build.yml. # This file: https://github.com/dio99/aaps-builder/blob/main/.github/workflows/build.yml # Plain text (phone): https://raw.githubusercontent.com/dio99/aaps-builder/main/.github/workflows/build.yml # Step-by-step guide: https://github.com/dio99/aaps-builder#-updating-your-copy # # Version 2: automatic weekly builds were removed at the request of the AAPS developers. # You always start a build yourself: Actions -> Build AAPS -> Run workflow. # ============================================================================================ # # Everything this repository does is in this file. Nothing is run from anywhere else. on: workflow_dispatch: inputs: version: description: 'AAPS version ("latest" = newest release)' required: true default: 'latest' type: string variant: description: 'Build variant' required: true default: 'fullRelease' type: choice options: - fullRelease - aapsclientRelease - aapsclient2Release - pumpcontrolRelease - fullDebug permissions: contents: read env: # Version of this file. Bump it (and the VERSION file) when publishing a new aaps-builder release. BUILDER_VERSION: 2 BUILDER_REPO: dio99/aaps-builder IN_VERSION: ${{ inputs.version || 'latest' }} IN_VARIANT: ${{ inputs.variant || 'fullRelease' }} jobs: check: name: Check runs-on: ubuntu-latest permissions: contents: read outputs: build: ${{ steps.check.outputs.build }} version: ${{ steps.check.outputs.version }} private: ${{ steps.check.outputs.private }} env: UPSTREAM: https://github.com/nightscout/AndroidAPS.git HAS_GDRIVE: ${{ secrets.GDRIVE_OAUTH2 != '' }} HAS_KEYSTORE: ${{ secrets.KEYSTORE_SET != '' }} GH_TOKEN: ${{ github.token }} steps: - name: Check repository, version and keystore id: check run: | stop() { echo "::error::$1"; exit 1; } api() { curl -s -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" -w '\n%{http_code}' "https://api.github.com/repos/$GITHUB_REPOSITORY$1"; } REPO_JSON=$(api "" | head -n -1) IS_TEMPLATE=$(echo "$REPO_JSON" | jq -r .is_template) PRIVATE=$(echo "$REPO_JSON" | jq -r .private) echo "private=$PRIVATE" >> "$GITHUB_OUTPUT" [ "$IS_TEMPLATE" = true ] && stop "This is the template. Create your own private copy with the setup page: https://${BUILDER_REPO%%/*}.github.io/${BUILDER_REPO#*/}/" if [ "$PRIVATE" != true ] && [ "$HAS_GDRIVE" != true ]; then stop "This repository is PUBLIC, anyone could download your APK. Make it private: Settings -> General -> Danger Zone -> Change visibility -> Private" fi [ "$HAS_KEYSTORE" = true ] || stop "Secret KEYSTORE_SET is missing. Create it with the setup page and add it under Settings -> Secrets and variables -> Actions." VERSION="$IN_VERSION" VERSION="${VERSION// /}" if [ -z "$VERSION" ] || [ "$VERSION" = "latest" ]; then VERSION=$(git ls-remote --tags --refs "$UPSTREAM" \ | awk -F/ '{print $3}' \ | grep -E '^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$' \ | sort -V | tail -1) fi git ls-remote --exit-code --tags "$UPSTREAM" "refs/tags/$VERSION" > /dev/null \ || stop "Version $VERSION does not exist in nightscout/AndroidAPS." echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "build=true" >> "$GITHUB_OUTPUT" build: name: Build AAPS ${{ needs.check.outputs.version }} needs: check if: ${{ needs.check.outputs.build == 'true' }} runs-on: ubuntu-latest timeout-minutes: 150 permissions: contents: read env: UPSTREAM: https://github.com/nightscout/AndroidAPS.git HAS_GDRIVE: ${{ secrets.GDRIVE_OAUTH2 != '' }} VERSION: ${{ needs.check.outputs.version }} steps: - name: Read keystore env: KEYSTORE_SET: ${{ secrets.KEYSTORE_SET }} run: | # Note: GitHub hides the secret's value in the log as ***, so these messages # must not contain text that could be the value itself (like the secret's name). HINT="Open Settings -> Secrets and variables -> Actions, edit the secret and paste the LONG code from 'Copy long code' on the setup page (step 3)." VALUE=$(printf '%s' "$KEYSTORE_SET" | tr -d ' \r\n') if [ ${#VALUE} -lt 200 ]; then echo "::error::The secret is too short (${#VALUE} characters). It must be a very long code, not the secret's name. $HINT" exit 1 fi DECODED=$(printf '%s' "$VALUE" | base64 -d 2>/dev/null) || { echo "::error::The secret is not a valid code (incomplete or wrong text was pasted). $HINT"; exit 1; } KEYSTORE_BASE64=$(printf '%s' "$DECODED" | cut -d'|' -f1) KEYSTORE_PASSWORD=$(printf '%s' "$DECODED" | cut -d'|' -f2) KEY_ALIAS=$(printf '%s' "$DECODED" | cut -d'|' -f3) KEY_PASSWORD=$(printf '%s' "$DECODED" | cut -d'|' -f4) if [ "$(printf '%s' "$DECODED" | tr -cd '|' | wc -c)" -ne 3 ] || [ -z "$KEY_ALIAS" ] || [ -z "$KEY_PASSWORD" ]; then echo "::error::The secret does not contain a keystore with passwords and alias. $HINT" exit 1 fi echo "::add-mask::$KEYSTORE_PASSWORD" echo "::add-mask::$KEY_PASSWORD" mkdir -p "$RUNNER_TEMP/keystore" printf '%s' "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/keystore/keystore.jks" { echo "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" echo "KEY_ALIAS=$KEY_ALIAS" echo "KEY_PASSWORD=$KEY_PASSWORD" } >> "$GITHUB_ENV" - name: Validate keystore and passwords run: | echo test > dummy.txt && zip -q dummy.jar dummy.txt if ! jarsigner -keystore "$RUNNER_TEMP/keystore/keystore.jks" \ -storepass "$KEYSTORE_PASSWORD" -keypass "$KEY_PASSWORD" \ dummy.jar "$KEY_ALIAS" > jarsigner.log 2>&1; then echo "::error::Keystore, password or alias is wrong. Create KEYSTORE_SET again with the setup page." cat jarsigner.log exit 1 fi rm -f dummy.txt dummy.jar jarsigner.log echo "✅ Keystore OK" - name: Prepare build settings run: | case "$VERSION" in 2.*|3.0.*) JAVA_VERSION=11 ;; 3.1.*|3.2.*) JAVA_VERSION=17 ;; *) JAVA_VERSION=21 ;; esac VARIANT="$IN_VARIANT" FLAVOR=$(echo "$VARIANT" | sed -E 's/(Release|Debug)$//') TYPE=$(echo "$VARIANT" | grep -oE '(Release|Debug)$' | tr '[:upper:]' '[:lower:]') SUFFIX="" [ "$FLAVOR" != "full" ] && SUFFIX="-$FLAVOR" [ "$TYPE" = "debug" ] && SUFFIX="$SUFFIX-debug" { echo "JAVA_VERSION=$JAVA_VERSION" echo "VARIANT=$VARIANT" echo "FLAVOR=$FLAVOR" echo "TYPE=$TYPE" echo "SUFFIX=$SUFFIX" } >> "$GITHUB_ENV" echo "### Building AAPS $VERSION ($VARIANT)" >> "$GITHUB_STEP_SUMMARY" - name: Download AAPS source run: git clone --depth 1 --branch "$VERSION" "$UPSTREAM" aaps - name: Set up JDK uses: actions/setup-java@v5 with: java-version: ${{ env.JAVA_VERSION }} distribution: temurin cache: gradle cache-dependency-path: | aaps/**/*.gradle* aaps/**/gradle-wrapper.properties # Private repositories get a small runner (2 CPU, ~7 GB RAM). Without extra swap # the machine runs out of memory when R8 optimizes the app and the job is killed. - name: Add swap run: | DIR=/mnt; [ -w "$DIR" ] || sudo test -d "$DIR" || DIR=/ FREE_GB=$(df -BG --output=avail "$DIR" | tail -1 | tr -dc 0-9) SIZE=$(( FREE_GB > 24 ? 12 : 6 )) sudo fallocate -l ${SIZE}G "$DIR/aaps.swap" sudo chmod 600 "$DIR/aaps.swap" sudo mkswap "$DIR/aaps.swap" > /dev/null sudo swapon "$DIR/aaps.swap" free -h - name: Build APKs working-directory: aaps run: | chmod +x gradlew ./gradlew ":app:assemble$VARIANT" ":wear:assemble$VARIANT" \ -Dorg.gradle.jvmargs="-Xmx5g -XX:MaxMetaspaceSize=1g -XX:+UseParallelGC -Xss1024m" \ -Dkotlin.compiler.execution.strategy=in-process \ -Dorg.gradle.parallel=false \ -Dorg.gradle.workers.max=2 \ -Pandroid.injected.signing.store.file="$RUNNER_TEMP/keystore/keystore.jks" \ -Pandroid.injected.signing.store.password="$KEYSTORE_PASSWORD" \ -Pandroid.injected.signing.key.alias="$KEY_ALIAS" \ -Pandroid.injected.signing.key.password="$KEY_PASSWORD" - name: Collect APKs run: | mkdir out mv aaps/app/build/outputs/apk/"$FLAVOR"/"$TYPE"/*.apk "out/aaps-$VERSION$SUFFIX.apk" mv aaps/wear/build/outputs/apk/"$FLAVOR"/"$TYPE"/*.apk "out/aaps-wear-$VERSION$SUFFIX.apk" rm -rf "$RUNNER_TEMP/keystore" ls -l out # Without Google Drive the check job only lets private repositories get this far. # archive: false uploads the APK itself (no zip), so the phone can install it directly. - name: Upload phone APK (download it from this page) id: phone if: ${{ needs.check.outputs.private == 'true' || env.HAS_GDRIVE != 'true' }} uses: actions/upload-artifact@v7 with: path: out/aaps-${{ env.VERSION }}${{ env.SUFFIX }}.apk archive: false retention-days: 14 - name: Upload watch APK id: wear if: ${{ needs.check.outputs.private == 'true' || env.HAS_GDRIVE != 'true' }} uses: actions/upload-artifact@v7 with: path: out/aaps-wear-${{ env.VERSION }}${{ env.SUFFIX }}.apk archive: false retention-days: 14 - name: Show download links if: ${{ steps.phone.outputs.artifact-url != '' }} env: PHONE_URL: ${{ steps.phone.outputs.artifact-url }} WEAR_URL: ${{ steps.wear.outputs.artifact-url }} run: | { echo "> [!WARNING]" echo "> **Before you install: back up your settings!**" echo "> 1. AAPS → Maintenance → **Export settings**" echo "> 2. Copy the exported file **off the phone** (Google Drive, Dropbox…)" echo ">" echo "> [How to export](https://androidaps.readthedocs.io/en/latest/Maintenance/ExportImportSettings.html) · [AAPS FAQ: backups](https://androidaps.readthedocs.io/en/latest/UsefulLinks/FAQ.html#how-to-organize-my-backups)" echo echo "## 📱 [Download AAPS $VERSION]($PHONE_URL)" echo "Open this page on your phone in the web browser (not the GitHub app), tap the link, then tap the downloaded file to install." echo echo "⌚ [Watch: aaps-wear-$VERSION$SUFFIX.apk]($WEAR_URL) (only for a smartwatch)" echo echo "_Available for 14 days._" } >> "$GITHUB_STEP_SUMMARY" - name: Upload APKs to Google Drive if: ${{ env.HAS_GDRIVE == 'true' }} env: GDRIVE_OAUTH2: ${{ secrets.GDRIVE_OAUTH2 }} run: | set -e DECODED=$(printf '%s' "$GDRIVE_OAUTH2" | base64 -d) CLIENT_ID=$(printf '%s' "$DECODED" | cut -d'|' -f1) REFRESH_TOKEN=$(printf '%s' "$DECODED" | cut -d'|' -f2) echo "::add-mask::$CLIENT_ID" echo "::add-mask::$REFRESH_TOKEN" TOKEN=$(curl -s -X POST https://oauth2.googleapis.com/token \ -d client_id="$CLIENT_ID" -d refresh_token="$REFRESH_TOKEN" \ -d grant_type=refresh_token | jq -r .access_token) if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then echo "::error::Google Drive access has expired or was revoked (unused for 6 months or Google password changed). Redo step 6 on the setup page and update the GDRIVE_OAUTH2 secret." exit 1 fi echo "::add-mask::$TOKEN" API=https://www.googleapis.com/drive/v3/files folder() { # name parent -> id (created if missing) local q="name='$1' and mimeType='application/vnd.google-apps.folder' and trashed=false" [ -n "$2" ] && q="$q and '$2' in parents" local id=$(curl -s -G -H "Authorization: Bearer $TOKEN" "$API" --data-urlencode "q=$q" | jq -r '.files[0].id') if [ -z "$id" ] || [ "$id" = "null" ]; then local body=$(jq -nc --arg n "$1" --arg p "$2" '{name:$n, mimeType:"application/vnd.google-apps.folder"} + (if $p=="" then {} else {parents:[$p]} end)') id=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$body" "$API" | jq -r .id) fi echo "$id" } AAPS_ID=$(folder AAPS "") VERSION_ID=$(folder "$VERSION" "$AAPS_ID") for f in out/*.apk; do NAME=$(basename "$f") OLD=$(curl -s -G -H "Authorization: Bearer $TOKEN" "$API" \ --data-urlencode "q=name='$NAME' and '$VERSION_ID' in parents and trashed=false" | jq -r '.files[0].id') [ -n "$OLD" ] && [ "$OLD" != "null" ] && curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$API/$OLD" CODE=$(curl -s -o resp.json -w '%{http_code}' -X POST -H "Authorization: Bearer $TOKEN" \ -F "metadata={\"name\":\"$NAME\",\"parents\":[\"$VERSION_ID\"]};type=application/json;charset=UTF-8" \ -F "file=@$f;type=application/vnd.android.package-archive" \ "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart") if [ "$CODE" != 200 ] && [ "$CODE" != 201 ]; then echo "::error::Upload of $NAME failed ($CODE)"; cat resp.json; exit 1 fi echo "✅ $NAME → Google Drive: AAPS/$VERSION" done echo "📁 APKs uploaded to Google Drive folder **AAPS/$VERSION**" >> "$GITHUB_STEP_SUMMARY" # Tells you (with a GitHub issue, which also sends you an e-mail) when a newer version of # aaps-builder exists. It only reads a version number; nothing is updated automatically. builder-update: name: Check for builder updates runs-on: ubuntu-latest permissions: issues: write env: GH_TOKEN: ${{ github.token }} steps: - name: Compare with the newest aaps-builder run: | LATEST=$(curl -sf "https://raw.githubusercontent.com/$BUILDER_REPO/main/VERSION" | tr -dc 0-9) [ -n "$LATEST" ] || { echo "Could not read the newest version, skipping."; exit 0; } if [ "$LATEST" -le "$BUILDER_VERSION" ]; then echo "✅ aaps-builder is up to date (version $BUILDER_VERSION)."; exit 0; fi [ "$(gh api "repos/$GITHUB_REPOSITORY" -q .is_template)" = true ] && exit 0 echo "::warning::A newer version of aaps-builder is available (version $LATEST, you have $BUILDER_VERSION). See the issue in your repository." TITLE="AAPS Builder update available (version $LATEST)" if [ "$(gh issue list -R "$GITHUB_REPOSITORY" --state all --search "\"$TITLE\" in:title" --json number -q length)" != 0 ]; then echo "Issue already exists."; exit 0 fi EDIT="https://github.com/$GITHUB_REPOSITORY/edit/${GITHUB_REF_NAME}/.github/workflows/build.yml" gh issue create -R "$GITHUB_REPOSITORY" --title "$TITLE" --body "$(cat <