Skip to content

Cherry Pick CI (Pre-release Feature Backport) #3

Cherry Pick CI (Pre-release Feature Backport)

Cherry Pick CI (Pre-release Feature Backport) #3

name: Cherry Pick CI (Pre-release Feature Backport)
on:
workflow_dispatch:
inputs:
upstream_repo:
description: 'Upstream repository (format: owner/repo)'
required: true
default: 'nightscout/AndroidAPS'
cherry_pick:
description: 'Commit hash(es) to cherry-pick (e.g. abc123 def456)'
required: true
buildVariant:
description: 'Select Build Variant'
required: true
default: 'fullRelease'
type: choice
options:
- fullRelease
- fullDebug
- aapsclientRelease
- aapsclientDebug
- aapsclient2Release
- aapsclient2Debug
- pumpcontrolRelease
- pumpcontrolDebug
jobs:
build:
name: Build AAPS
runs-on: ubuntu-latest
steps:
- name: Checkout current branch
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch all remotes and tags
run: |
echo "πŸ”— Adding upstream: https://github.com/${{ github.event.inputs.upstream_repo }}.git"
git remote add upstream https://github.com/${{ github.event.inputs.upstream_repo }}.git
git fetch --all
- name: Cherry-pick commits
run: |
echo "➑️ Cherry-picking commits: ${{ github.event.inputs.cherry_pick }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if ! git cherry-pick ${{ github.event.inputs.cherry_pick }}; then
echo "❌ Cherry-pick failed. Exiting."
exit 1
fi
echo "βœ… All commits cherry-picked."
- name: Decode Secrets Keystore Set and Oauth2 to Env
run: |
if [ -n "${{ secrets.KEYSTORE_SET }}" ]; then
echo "πŸ” Decoding KEYSTORE_SET..."
DECODED=$(echo "${{ secrets.KEYSTORE_SET }}" | base64 -d)
KEYSTORE_BASE64=$(echo "$DECODED" | cut -d'|' -f1)
KEYSTORE_PASSWORD=$(echo "$DECODED" | cut -d'|' -f2)
KEY_ALIAS=$(echo "$DECODED" | cut -d'|' -f3)
KEY_PASSWORD=$(echo "$DECODED" | cut -d'|' -f4)
echo "KEYSTORE_BASE64=$KEYSTORE_BASE64" >> $GITHUB_ENV
echo "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" >> $GITHUB_ENV
echo "KEY_ALIAS=$KEY_ALIAS" >> $GITHUB_ENV
echo "KEY_PASSWORD=$KEY_PASSWORD" >> $GITHUB_ENV
echo "::add-mask::$KEYSTORE_BASE64"
echo "::add-mask::$KEYSTORE_PASSWORD"
echo "::add-mask::$KEY_ALIAS"
echo "::add-mask::$KEY_PASSWORD"
echo "βœ… Keystore parameters extracted from KEYSTORE_SET"
else
echo "ℹ️ KEYSTORE_SET not provided, using separate secrets."
echo "KEYSTORE_BASE64=${{ secrets.KEYSTORE_BASE64 }}" >> $GITHUB_ENV
echo "KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}" >> $GITHUB_ENV
echo "KEY_ALIAS=${{ secrets.KEY_ALIAS }}" >> $GITHUB_ENV
echo "KEY_PASSWORD=${{ secrets.KEY_PASSWORD }}" >> $GITHUB_ENV
fi
echo "GDRIVE_OAUTH2=${{ secrets.GDRIVE_OAUTH2 }}" >> $GITHUB_ENV
- name: Check Secrets
run: |
echo "πŸ” Checking required secrets..."
MISSING=0
check_secret() {
if [ -z "$1" ]; then
echo "❌ Missing secret: $2"
MISSING=1
fi
}
# Check secrets
check_secret "$GDRIVE_OAUTH2" "GDRIVE_OAUTH2"
check_secret "$KEYSTORE_BASE64" "KEYSTORE_BASE64"
check_secret "$KEYSTORE_PASSWORD" "KEYSTORE_PASSWORD"
check_secret "$KEY_ALIAS" "KEY_ALIAS"
check_secret "$KEY_PASSWORD" "KEY_PASSWORD"
if [ "$MISSING" -eq 1 ]; then
echo "πŸ›‘ Missing required secrets. Stopping build."
exit 1
fi
echo "βœ… All required secrets are present."
- name: Decode keystore file
run: |
mkdir -p "$RUNNER_TEMP/keystore"
echo "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/keystore/keystore.jks"
- name: Validating keystore, alias and password
run: |
set -x
echo "πŸ” Validating keystore, alias and password"
# Create a dummy JAR file (quick method using zip)
echo "test" > dummy.txt
zip -q dummy.jar dummy.txt
rm dummy.txt
# Attempt to validate using jarsigner
JARSIGNER_LOG=$(mktemp)
if ! jarsigner \
-keystore "$RUNNER_TEMP/keystore/keystore.jks" \
-storepass "$KEYSTORE_PASSWORD" \
-keypass "$KEY_PASSWORD" \
dummy.jar "$KEY_ALIAS" > "$JARSIGNER_LOG" 2>&1; then
echo "❌ Either KEYSTORE_BASE64, KEYSTORE_PASSWORD, KEY_PASSWORD, or KEY_ALIAS is incorrect"
echo "πŸ” jarsigner error output:"
cat "$JARSIGNER_LOG"
rm -f "$JARSIGNER_LOG" dummy.jar
exit 1
fi
rm -f "$JARSIGNER_LOG" dummy.jar
echo "βœ… Keystore, alias, and key password are valid."
rm -f "$KEYTOOL_LOG"
echo "βœ… Keystore and credentials validated."
- name: Decode GDrive OAuth2 secrets
run: |
echo "πŸ” Decoding GDRIVE_OAUTH2..."
DECODED=$(echo "${{ secrets.GDRIVE_OAUTH2 }}" | base64 -d)
GDRIVE_CLIENT_ID=$(echo "$DECODED" | cut -d'|' -f1)
GDRIVE_REFRESH_TOKEN=$(echo "$DECODED" | cut -d'|' -f2)
echo "::add-mask::$GDRIVE_CLIENT_ID"
echo "::add-mask::$GDRIVE_REFRESH_TOKEN"
echo "GDRIVE_CLIENT_ID=$GDRIVE_CLIENT_ID" >> $GITHUB_ENV
echo "GDRIVE_REFRESH_TOKEN=$GDRIVE_REFRESH_TOKEN" >> $GITHUB_ENV
echo "βœ… GDRIVE_CLIENT_ID and GDRIVE_REFRESH_TOKEN extracted from GDRIVE_OAUTH2"
- name: Retrieving Google Drive access token
run: |
echo "πŸ” Getting Google OAuth2 access token..."
TOKEN_RESPONSE=$(curl -s -X POST https://oauth2.googleapis.com/token \
-d client_id="$GDRIVE_CLIENT_ID" \
-d refresh_token="$GDRIVE_REFRESH_TOKEN" \
-d grant_type=refresh_token)
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r .access_token)
echo "::add-mask::$ACCESS_TOKEN"
if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
echo "❌ Failed to get access token."
echo "$TOKEN_RESPONSE"
exit 1
fi
echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> $GITHUB_ENV
echo "βœ… Access token obtained."
- name: Set BUILD_VARIANT
run: |
BUILD_VARIANT="${{ github.event.inputs.buildVariant }}"
echo "BUILD_VARIANT=$BUILD_VARIANT" >> $GITHUB_ENV
VARIANT_FLAVOR=$(echo "$BUILD_VARIANT" | sed -E 's/(Release|Debug)$//' | tr '[:upper:]' '[:lower:]')
VARIANT_TYPE=$(echo "$BUILD_VARIANT" | grep -oE '(Release|Debug)$' | tr '[:upper:]' '[:lower:]')
echo "VARIANT_FLAVOR=$VARIANT_FLAVOR" >> $GITHUB_ENV
echo "VARIANT_TYPE=$VARIANT_TYPE" >> $GITHUB_ENV
VERSION_SUFFIX=""
if [[ "$VARIANT_FLAVOR" != "full" ]]; then VERSION_SUFFIX="$VARIANT_FLAVOR"; fi
if [[ "$VARIANT_TYPE" == "debug" ]]; then VERSION_SUFFIX="$VERSION_SUFFIX-debug"; fi
if [[ -n "$VERSION_SUFFIX" && "$VERSION_SUFFIX" != -* ]]; then VERSION_SUFFIX="-$VERSION_SUFFIX"; fi
echo "VERSION_SUFFIX=$VERSION_SUFFIX" >> $GITHUB_ENV
- name: Extract VERSION
run: |
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
if echo "$BRANCH_NAME" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$'; then
VERSION="$BRANCH_NAME"
else
VERSION=$(grep 'val appVersion' buildSrc/src/main/kotlin/Versions.kt | awk -F '"' '{print $2}')
fi
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Set up JDK
uses: actions/setup-java@v4
with:
# When upgrading the JDK, please update this section accordingly as well.
java-version: 21
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build APKs
run: |
./gradlew :app:assemble${{ env.BUILD_VARIANT }} :wear:assemble${{ env.BUILD_VARIANT }} \
-Dorg.gradle.jvmargs="-Xmx8g -XX:+UseParallelGC -Xss1024m" \
-Dkotlin.daemon.jvm.options="-Xmx2g" \
-Dkotlin.compiler.execution.strategy="in-process" \
-Dorg.gradle.daemon=true \
-Dorg.gradle.workers.max=8 \
-Dorg.gradle.caching=true \
-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: Rename APKs with version
run: |
mv app/build/outputs/apk/${{ env.VARIANT_FLAVOR }}/${{ env.VARIANT_TYPE }}/*.apk aaps-${{ env.VERSION }}${{ env.VERSION_SUFFIX }}.apk
mv wear/build/outputs/apk/${{ env.VARIANT_FLAVOR }}/${{ env.VARIANT_TYPE }}/*.apk aaps-wear-${{ env.VERSION }}${{ env.VERSION_SUFFIX }}.apk
- name: Upload APKs to Google Drive
run: |
set -e
echo "πŸ” Start uploading APKs to Google Drive..."
echo "πŸ“ Checking or creating AAPS folder"
AAPS_FOLDER_ID=$(curl -s -X GET \
-H "Authorization: Bearer $ACCESS_TOKEN" \
"https://www.googleapis.com/drive/v3/files?q=name='AAPS'+and+mimeType='application/vnd.google-apps.folder'+and+trashed=false" \
| jq -r '.files[0].id')
if [ "$AAPS_FOLDER_ID" == "null" ] || [ -z "$AAPS_FOLDER_ID" ]; then
AAPS_FOLDER_ID=$(curl -s -X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "AAPS", "mimeType": "application/vnd.google-apps.folder"}' \
"https://www.googleapis.com/drive/v3/files" | jq -r '.id')
echo "πŸ“‚ Created AAPS folder: $AAPS_FOLDER_ID"
else
echo "πŸ“‚ Found AAPS folder: $AAPS_FOLDER_ID"
fi
echo "πŸ“ Checking or creating version folder: $VERSION"
VERSION_FOLDER_ID=$(curl -s -X GET \
-H "Authorization: Bearer $ACCESS_TOKEN" \
"https://www.googleapis.com/drive/v3/files?q=name='${VERSION}'+and+mimeType='application/vnd.google-apps.folder'+and+'$AAPS_FOLDER_ID'+in+parents+and+trashed=false" \
| jq -r '.files[0].id')
if [ "$VERSION_FOLDER_ID" == "null" ] || [ -z "$VERSION_FOLDER_ID" ]; then
VERSION_FOLDER_ID=$(curl -s -X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\": \"${VERSION}\", \"mimeType\": \"application/vnd.google-apps.folder\", \"parents\": [\"$AAPS_FOLDER_ID\"]}" \
"https://www.googleapis.com/drive/v3/files" | jq -r '.id')
echo "πŸ“‚ Created version folder: $VERSION_FOLDER_ID"
else
echo "πŸ“‚ Found version folder: $VERSION_FOLDER_ID"
fi
upload_to_gdrive() {
FILE=$1
NAME=$2
if [ ! -f "$FILE" ]; then
echo "❌ File not found: $FILE"
exit 26
fi
echo "πŸ“„ Checking if file $NAME already exists in Google Drive..."
QUERY="name='${NAME}' and '${VERSION_FOLDER_ID}' in parents and trashed=false"
ENCODED_QUERY=$(python3 -c "import urllib.parse; print(urllib.parse.quote('''$QUERY'''))")
FILE_ID=$(curl -s \
-H "Authorization: Bearer $ACCESS_TOKEN" \
"https://www.googleapis.com/drive/v3/files?q=${ENCODED_QUERY}&fields=files(id)" \
| jq -r '.files[0].id')
if [[ -n "$FILE_ID" && "$FILE_ID" != "null" ]]; then
echo "πŸ—‘οΈ Deleting existing file with ID: $FILE_ID"
curl -s -X DELETE \
-H "Authorization: Bearer $ACCESS_TOKEN" \
"https://www.googleapis.com/drive/v3/files/${FILE_ID}"
fi
echo "⬆️ Uploading $FILE as $NAME to Google Drive..."
RESPONSE=$(curl -s -w "%{http_code}" -o /tmp/gdrive_response.json \
-X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-F "metadata={\"name\":\"$NAME\", \"parents\":[\"$VERSION_FOLDER_ID\"]};type=application/json;charset=UTF-8" \
-F "file=@$FILE;type=application/vnd.android.package-archive" \
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart")
HTTP_CODE="${RESPONSE: -3}"
if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" ]]; then
echo "❌ Upload failed with HTTP status: $HTTP_CODE"
cat /tmp/gdrive_response.json
exit 1
fi
echo "βœ… Uploaded: $NAME"
}
# Sanitize cherry-pick input to create a safe filename suffix.
# Reason: ${{ github.event.inputs.cherry_pick }} may contain special characters,
# spaces, or symbols that are invalid or risky in filenames.
# This ensures the final name only includes letters, numbers, dash (-), and underscore (_).
CHERRY_PICK_RAW="${{ github.event.inputs.cherry_pick }}"
CHERRY_PICK_SAFE=$(echo "$CHERRY_PICK_RAW" | sed 's/ /_/g')
CHERRY_PICK_SAFE=$(echo "$CHERRY_PICK_SAFE" | sed 's/[^a-zA-Z0-9_-]/_/g')
echo "CHERRY_PICK_SAFE=$CHERRY_PICK_SAFE" >> $GITHUB_ENV
upload_to_gdrive "aaps-${VERSION}${VERSION_SUFFIX}.apk" "aaps-${VERSION}${VERSION_SUFFIX}_CP-${CHERRY_PICK_SAFE}.apk"
upload_to_gdrive "aaps-wear-${VERSION}${VERSION_SUFFIX}.apk" "aaps-wear-${VERSION}${VERSION_SUFFIX}_CP-${CHERRY_PICK_SAFE}.apk"
echo "πŸŽ‰ APKs successfully uploaded to Google Drive!"