Cherry Pick CI (Pre-release Feature Backport) #3
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: 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!" |