diff --git a/.circleci/config.yml b/.circleci/config.yml index 4593de71fc5..e8c8e147450 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,38 +1,71 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. + # Use the latest 2.1 version of CircleCI pipeline process engine. # See: https://circleci.com/docs/2.0/configuration-reference version: 2.1 # Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects. orbs: - android: circleci/android@2.3.0 + android: circleci/android@2.4.0 codecov: codecov/codecov@3.3.0 jobs: # Below is the definition of your job to build and test your app, you can rename and customize it as you want. build-and-test: - # These next lines define the Android machine image executor: https://circleci.com/docs/2.0/executor-types/ - executor: - name: android/android-machine - resource-class: large - tag: 2023.11.1 + machine: true + resource_class: nightscout/android steps: - checkout - - android/change-java-version: - java-version: 17 + - run: + name: Create avd + command: | + echo "no" | /usr/lib/android-sdk/cmdline-tools/13.0/bin/avdmanager --verbose create avd -n citest -k "system-images;android-31;google_apis_playstore;x86_64" --force - - android/start-emulator-and-run-tests: - system-image: system-images;android-29;google_apis;x86 - # Compile while the emulator starts to use the time. - post-emulator-launch-assemble-command: ./gradlew compileFullDebugUnitTestSources compileFullDebugAndroidTestSources - test-command: ./gradlew connectedFullDebugAndroidTest + - run: + name: Launch emulator + command: | + export ANDROID_SDK_ROOT=/usr/lib/android-sdk + export ANDROID_HOME=/usr/lib/android-sdk + emulator -avd citest -delay-adb -verbose -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim + background: true - - android/run-tests: - test-command: ./gradlew testFullDebugUnitTest + - run: + name: Run connectedFullDebugAndroidTest + command: | + export ANDROID_SDK_ROOT=/usr/lib/android-sdk + export ANDROID_HOME=/usr/lib/android-sdk + env + ./gradlew \ + -Dorg.gradle.jvmargs="-Xmx8g -XX:+UseParallelGC -Xss1024m" \ + -Dkotlin.daemon.jvm.options="-Xmx2g" \ + -Dkotlin.compiler.execution.strategy="in-process" \ + -Dorg.gradle.daemon=true \ + connectedFullDebugAndroidTest + + - run: + name: Kill emulators + command: | + echo "Killing emulators" + adb devices | grep emulator | cut -f1 | while read -r line; do adb -s $line emu kill; done + + - run: + name: Run testFullDebugUnitTest + command: | + export ANDROID_SDK_ROOT=/usr/lib/android-sdk + export ANDROID_HOME=/usr/lib/android-sdk + ./gradlew \ + -Dorg.gradle.jvmargs="-Xmx8g -XX:+UseParallelGC -Xss1024m" \ + -Dkotlin.daemon.jvm.options="-Xmx2g" \ + -Dkotlin.compiler.execution.strategy="in-process" \ + -Dorg.gradle.daemon=true \ + testFullDebugUnitTest - - android/run-tests: - test-command: ./gradlew --stacktrace jacocoAllDebugReport + - run: + run: Run jacocoAllDebugReport + command: | + export ANDROID_SDK_ROOT=/usr/lib/android-sdk + export ANDROID_HOME=/usr/lib/android-sdk + ./gradlew --stacktrace jacocoAllDebugReport - run: name: Save test results @@ -50,6 +83,12 @@ jobs: - codecov/upload: file: './build/reports/jacoco/jacocoAllDebugReport/jacocoAllDebugReport.xml' + - run: + name: Kill java processes + command: | + killall java + when: always + workflows: # Below is the definition of your workflow. # Inside the workflow, you provide the jobs you want to run, e.g this workflow runs the build-and-test job above. diff --git a/.circleci/config.yml.cloud b/.circleci/config.yml.cloud new file mode 100644 index 00000000000..4593de71fc5 --- /dev/null +++ b/.circleci/config.yml.cloud @@ -0,0 +1,60 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/2.0/configuration-reference +version: 2.1 + +# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects. +orbs: + android: circleci/android@2.3.0 + codecov: codecov/codecov@3.3.0 + +jobs: + # Below is the definition of your job to build and test your app, you can rename and customize it as you want. + build-and-test: + # These next lines define the Android machine image executor: https://circleci.com/docs/2.0/executor-types/ + executor: + name: android/android-machine + resource-class: large + tag: 2023.11.1 + + steps: + - checkout + + - android/change-java-version: + java-version: 17 + + - android/start-emulator-and-run-tests: + system-image: system-images;android-29;google_apis;x86 + # Compile while the emulator starts to use the time. + post-emulator-launch-assemble-command: ./gradlew compileFullDebugUnitTestSources compileFullDebugAndroidTestSources + test-command: ./gradlew connectedFullDebugAndroidTest + + - android/run-tests: + test-command: ./gradlew testFullDebugUnitTest + + - android/run-tests: + test-command: ./gradlew --stacktrace jacocoAllDebugReport + + - run: + name: Save test results + command: | + mkdir -p ~/test-results/junit/ + find . -type f -regex ".*/build/outputs/androidTest-results/.*xml" -exec cp {} ~/test-results/junit/ \; + when: always + + - store_test_results: + path: ~/test-results + + - store_artifacts: + path: ~/test-results/junit + + - codecov/upload: + file: './build/reports/jacoco/jacocoAllDebugReport/jacocoAllDebugReport.xml' + +workflows: + # Below is the definition of your workflow. + # Inside the workflow, you provide the jobs you want to run, e.g this workflow runs the build-and-test job above. + # CircleCI will run this workflow on every commit. + # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows + dotests: + jobs: + - build-and-test diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md index 9f30207094c..3e085d67abe 100644 --- a/.github/ISSUE_TEMPLATE/custom.md +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -15,5 +15,5 @@ Reporting bugs upper-right corner). - Obtain the app's log files, which can be found on the phone in _/storage/emulated/0/Android/data/info.nightscout.androidaps/_ - See https://wiki.aaps.app/en/latest/Usage/Accessing-logfiles.html + See https://wiki.aaps.app/en/latest/GettingHelp/AccessingLogFiles.html - Open an issue at https://github.com/nightscout/AndroidAPS/issues/new diff --git a/.github/workflows/aaps-ci.yml b/.github/workflows/aaps-ci.yml new file mode 100644 index 00000000000..7f233e092c0 --- /dev/null +++ b/.github/workflows/aaps-ci.yml @@ -0,0 +1,291 @@ +name: AAPS CI + +on: + workflow_dispatch: + inputs: + 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: 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: Checkout source code + uses: actions/checkout@v4 + + - 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 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" + } + + upload_to_gdrive "aaps-${VERSION}${VERSION_SUFFIX}.apk" "aaps-${VERSION}${VERSION_SUFFIX}.apk" + upload_to_gdrive "aaps-wear-${VERSION}${VERSION_SUFFIX}.apk" "aaps-wear-${VERSION}${VERSION_SUFFIX}.apk" + + echo "🎉 APKs successfully uploaded to Google Drive!" \ No newline at end of file diff --git a/.github/workflows/cherry-pick-ci.yml b/.github/workflows/cherry-pick-ci.yml new file mode 100644 index 00000000000..e0e262e2b12 --- /dev/null +++ b/.github/workflows/cherry-pick-ci.yml @@ -0,0 +1,330 @@ +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 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!" \ No newline at end of file diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml new file mode 100644 index 00000000000..ae567730812 --- /dev/null +++ b/.github/workflows/pr-ci.yml @@ -0,0 +1,327 @@ +name: Pull Request CI(Test) + +on: + workflow_dispatch: + inputs: + repo: + description: 'Target repository (format: owner/repo)' + required: true + default: 'nightscout/AndroidAPS' + + pr_number: + description: 'PR number to checkout (e.g. 123)' + required: true + + pr_ref_type: + description: 'Select PR reference type to checkout' + required: true + default: 'head' + type: choice + options: + - head + - merge + + 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: 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: Determine PR reference + id: pr_ref + run: | + PR_NUMBER=${{ github.event.inputs.pr_number }} + PR_REF_TYPE=${{ github.event.inputs.pr_ref_type }} + + full_ref="refs/pull/${{ github.event.inputs.pr_number }}/$PR_REF_TYPE" + + echo "Resolved PR ref: $full_ref" + echo "ref_name=$full_ref" >> $GITHUB_OUTPUT + + echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV + echo "PR_REF_TYPE=$PR_REF_TYPE" >> $GITHUB_ENV + + - name: Checkout pull/${{ github.event.inputs.pr_number }}/${{ github.event.inputs.pr_ref_type }} + uses: actions/checkout@v4 + with: + repository: ${{ github.event.inputs.repo }} + ref: ${{ steps.pr_ref.outputs.ref_name }} + fetch-depth: 1 + + - 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 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" + } + + upload_to_gdrive "aaps-${VERSION}${VERSION_SUFFIX}.apk" "aaps-${VERSION}${VERSION_SUFFIX}_pull-${PR_NUMBER}-${PR_REF_TYPE}.apk" + upload_to_gdrive "aaps-wear-${VERSION}${VERSION_SUFFIX}.apk" "aaps-wear-${VERSION}${VERSION_SUFFIX}_pull-${PR_NUMBER}-${PR_REF_TYPE}.apk" + + echo "🎉 APKs successfully uploaded to Google Drive!" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 369da9db4c6..989046f5e22 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ */output-metadata.json /build /captures +/java_pid* *.apk build/ !.idea/dictionaries/project-dictionary.xml @@ -26,3 +27,9 @@ app/pumpcontrol/* wear/aapsclient/* wear/aapsclient2/* wear/pumpcontrol/* +.kotlin/* +/*.log +*.preferences_pb + +# This is temporary ignore. This file will for now contain pin of Tandem Pump and should be never checked in +pump/tandem/src/main/kotlin/app/aaps/pump/tandem/common/driver/config/TandemPumpConfig.kt \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index bf41126f8b9..680e62e6652 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,7 +1,7 @@