From 0b89f48e224dee85bbbf8c72060e902556c928ae Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 21:58:03 +0800 Subject: [PATCH 01/19] test: isolate download job for testing Google Play API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove job dependencies to test download independently - Hardcode v2.3.3 version values for testing existing APK - This is a temporary test configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a37e4a83..465875b2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -385,9 +385,7 @@ jobs: download-signed-apk: name: Download Google Play Signed APK - needs: [prepare, play-store-upload] runs-on: ubuntu-latest - if: success() steps: - name: Checkout code @@ -419,8 +417,8 @@ jobs: env: PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} run: | - VERSION_NAME="${{ needs.prepare.outputs.version }}" - VERSION_CODE="${{ needs.prepare.outputs.version_code }}" + VERSION_NAME="v2.3.3" + VERSION_CODE="233" PACKAGE_NAME="me.ghui.v2er" # Create Python script to download signed universal APK @@ -572,8 +570,8 @@ jobs: if: steps.download-apk.outputs.found == 'true' id: play-link run: | - VERSION_NAME="${{ needs.prepare.outputs.version }}" - VERSION_CODE="${{ needs.prepare.outputs.version_code }}" + VERSION_NAME="v2.3.3" + VERSION_CODE="233" # Create info file about Google Play signing cat > "v2er-${VERSION_NAME}_google_play_signed_info.txt" << EOF @@ -601,7 +599,7 @@ jobs: if: steps.download-apk.outputs.found == 'true' uses: softprops/action-gh-release@v2 with: - tag_name: ${{ needs.prepare.outputs.version }} + tag_name: v2.3.3 files: | ${{ steps.download-apk.outputs.apk_path }} ${{ steps.play-link.outputs.info_path }} @@ -616,9 +614,9 @@ jobs: if [ "${{ steps.download-apk.outputs.found }}" = "true" ]; then echo "✅ **Universal APK generated successfully**" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Version**: ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "- **Version Code**: ${{ needs.prepare.outputs.version_code }}" >> $GITHUB_STEP_SUMMARY - echo "- **File**: v2er-${{ needs.prepare.outputs.version }}_google_play_signed.apk" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: v2.3.3" >> $GITHUB_STEP_SUMMARY + echo "- **Version Code**: 233" >> $GITHUB_STEP_SUMMARY + echo "- **File**: v2er-v2.3.3_google_play_signed.apk" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Notes" >> $GITHUB_STEP_SUMMARY echo "- This APK is generated from the AAB uploaded to Google Play" >> $GITHUB_STEP_SUMMARY From 12bdf8efa40eea76fe2dfcc07ac193258ba88757 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 22:06:06 +0800 Subject: [PATCH 02/19] fix: remove AAB dependency for Google Play signed APK download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove AAB artifact download step (not needed) - Remove fallback bundletool generation (focus only on signed APK) - Simplify workflow to only download actual signed APK from Google Play 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 48 +++-------------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 465875b2..09ef1499 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -405,12 +405,7 @@ jobs: echo "Waiting for Google Play to process and sign the APK..." sleep 120 # Wait 2 minutes for Google Play to process - - name: Download AAB artifact - uses: actions/download-artifact@v4 - with: - name: release-bundle - path: bundle-artifacts/ - continue-on-error: true + # AAB artifact not needed for Google Play signed APK download - name: Download Google Play Signed APK id: download-apk @@ -524,46 +519,9 @@ jobs: echo "found=false" >> $GITHUB_OUTPUT fi else - echo "Failed to download Google Play signed APK, falling back to universal APK generation" + echo "Failed to download Google Play signed APK" cat download_output.txt - - # Fallback: Generate universal APK from AAB as before - AAB_PATH=$(find bundle-artifacts -name "*.aab" 2>/dev/null | head -1) - if [ -z "$AAB_PATH" ]; then - echo "No AAB found for fallback, skipping" - echo "found=false" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "Generating universal APK from AAB as fallback..." - - # Download bundletool - curl -L -o bundletool-all.jar https://github.com/google/bundletool/releases/latest/download/bundletool-all.jar - - # Create dummy keystore - keytool -genkey -v -keystore debug.keystore -alias androiddebugkey \ - -keyalg RSA -keysize 2048 -validity 10000 \ - -dname "CN=Android Debug,O=Android,C=US" \ - -storepass android -keypass android - - # Generate universal APK - java -jar bundletool-all.jar build-apks \ - --bundle="$AAB_PATH" \ - --output=apks.apks \ - --mode=universal \ - --ks=debug.keystore \ - --ks-pass=pass:android \ - --ks-key-alias=androiddebugkey \ - --key-pass=pass:android - - # Extract APK - unzip -q apks.apks universal.apk - OUTPUT_FILE="v2er-${VERSION_NAME}_google_play_signed.apk" - mv universal.apk "$OUTPUT_FILE" - - echo "Generated fallback APK: $OUTPUT_FILE" - echo "apk_path=$OUTPUT_FILE" >> $GITHUB_OUTPUT - echo "found=true" >> $GITHUB_OUTPUT + echo "found=false" >> $GITHUB_OUTPUT fi - name: Create Google Play link info From 3f09f0d236dc91f3eb2f9c6a0ecfbe73c4a2aaaf Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 22:19:55 +0800 Subject: [PATCH 03/19] feat: use correct Google Play API endpoints for APK download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use generatedapks.list to get downloadId from APK manifest - Use generatedapks.download to fetch binary APK with downloadId - Improve universal APK detection with better targeting logic - Add detailed logging for debugging APK selection Based on correct API documentation: - GET /applications/{packageName}/generatedApks/{versionCode} - GET /applications/{packageName}/generatedApks/{versionCode}/downloads/{downloadId}:download 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 49 +++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 09ef1499..a3af7687 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -442,8 +442,8 @@ jobs: print(f"Attempting to download signed APK for {package_name} version {version_code}") - # Get the signed universal APK download URL - # Note: This requires the app to be released and processed by Google Play + # Step 1: Get the generated APKs list to find downloadId + print("Getting generated APKs list...") result = service.generatedapks().list( packageName=package_name, versionCode=version_code @@ -453,32 +453,53 @@ jobs: print("No generated APKs found. App may not be processed yet by Google Play.") return False - # Find universal APK + print(f"Found {len(result['generatedApks'])} generated APKs") + + # Find universal APK and get downloadId universal_apk = None for apk in result['generatedApks']: - if apk.get('targetingInfo', {}).get('abiTargeting') is None: - # This should be the universal APK + targeting_info = apk.get('targetingInfo', {}) + # Universal APK typically has no targeting info or minimal targeting + if (not targeting_info.get('abiTargeting') and + not targeting_info.get('screenDensityTargeting') and + not targeting_info.get('languageTargeting')): universal_apk = apk break + if not universal_apk: + # Fallback: try to find any APK that looks universal + for apk in result['generatedApks']: + if not apk.get('targetingInfo', {}).get('abiTargeting'): + universal_apk = apk + break + if not universal_apk: print("Universal APK not found in generated APKs") + print("Available APKs:") + for i, apk in enumerate(result['generatedApks']): + print(f" APK {i}: {apk.get('targetingInfo', 'No targeting info')}") return False - # Download the APK - download_url = universal_apk.get('downloadUrl') - if not download_url: - print("Download URL not available for universal APK") + download_id = universal_apk.get('downloadId') + if not download_id: + print("Download ID not available for universal APK") return False - print(f"Downloading APK from: {download_url}") - response = requests.get(download_url, stream=True) - response.raise_for_status() + print(f"Found universal APK with downloadId: {download_id}") + + # Step 2: Download the APK using the downloadId + print("Downloading APK binary...") + download_request = service.generatedapks().download( + packageName=package_name, + versionCode=version_code, + downloadId=download_id + ) output_filename = f"v2er-{os.environ['VERSION_NAME']}_google_play_signed.apk" + + # Execute the download request and save to file with open(output_filename, 'wb') as f: - for chunk in response.iter_content(chunk_size=8192): - f.write(chunk) + download_request.execute(f) print(f"Successfully downloaded: {output_filename}") print(f"apk_path={output_filename}") From 6919b9dc85cac5ffb0c4a8084c3a4e0da9102c82 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 22:23:59 +0800 Subject: [PATCH 04/19] debug: add APK object structure logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Print complete APK object structure to understand available fields - This will help identify the correct field name for downloadId 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a3af7687..6a4da406 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -455,6 +455,13 @@ jobs: print(f"Found {len(result['generatedApks'])} generated APKs") + # Debug: Print all APK structures + for i, apk in enumerate(result['generatedApks']): + print(f"APK {i} structure:") + for key, value in apk.items(): + print(f" {key}: {value}") + print() + # Find universal APK and get downloadId universal_apk = None for apk in result['generatedApks']: From 306caff2328c233ff065f21bbdb020f08c216d10 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 22:30:36 +0800 Subject: [PATCH 05/19] fix: update APK download logic to use correct generatedSplitApks structure - Changed from looking for generatedUniversalApK field to generatedSplitApks array - Look for base module with variantId=1 and no splitId to identify universal APK - Added better debugging output to understand API response structure - Fixed duplicate download_id validation --- .github/workflows/release.yml | 46 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a4da406..89a46419 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -462,34 +462,34 @@ jobs: print(f" {key}: {value}") print() - # Find universal APK and get downloadId + # Find universal APK in generatedSplitApks (based on API debug output) + download_id = None universal_apk = None - for apk in result['generatedApks']: - targeting_info = apk.get('targetingInfo', {}) - # Universal APK typically has no targeting info or minimal targeting - if (not targeting_info.get('abiTargeting') and - not targeting_info.get('screenDensityTargeting') and - not targeting_info.get('languageTargeting')): - universal_apk = apk - break - if not universal_apk: - # Fallback: try to find any APK that looks universal - for apk in result['generatedApks']: - if not apk.get('targetingInfo', {}).get('abiTargeting'): - universal_apk = apk + for apk in result['generatedApks']: + if 'generatedSplitApks' in apk: + split_apks = apk['generatedSplitApks'] + print(f"Found {len(split_apks)} split APKs") + + # Look for the base/universal APK - typically variantId=1, moduleName='base' + for split_apk in split_apks: + print(f"Split APK: variantId={split_apk.get('variantId')}, moduleName={split_apk.get('moduleName')}") + # Universal APK is typically the base module without splitId + if (split_apk.get('moduleName') == 'base' and + split_apk.get('variantId') == 1 and + 'splitId' not in split_apk): + download_id = split_apk.get('downloadId') + universal_apk = split_apk + print(f"Found universal APK: {universal_apk}") + break + + if download_id: break - if not universal_apk: - print("Universal APK not found in generated APKs") - print("Available APKs:") - for i, apk in enumerate(result['generatedApks']): - print(f" APK {i}: {apk.get('targetingInfo', 'No targeting info')}") - return False - - download_id = universal_apk.get('downloadId') if not download_id: - print("Download ID not available for universal APK") + print("Universal APK not found in generatedSplitApks") + print("Available APK structure:") + print(json.dumps(result['generatedApks'], indent=2)) return False print(f"Found universal APK with downloadId: {download_id}") From 058d1abfb6a021f25411c0b4e37bf4ba45281c19 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 22:36:51 +0800 Subject: [PATCH 06/19] fix: use MediaIoBaseDownload for Google Play API binary downloads - Replace direct execute() call with proper Google API media download - Add progress tracking during download - Use BytesIO buffer for binary data handling --- .github/workflows/release.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 89a46419..92c3b711 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -505,8 +505,22 @@ jobs: output_filename = f"v2er-{os.environ['VERSION_NAME']}_google_play_signed.apk" # Execute the download request and save to file + # Use media download with googleapiclient.http to handle binary content + import io + from googleapiclient.http import MediaIoBaseDownload + + file_io = io.BytesIO() + downloader = MediaIoBaseDownload(file_io, download_request) + + done = False + while done is False: + status, done = downloader.next_chunk() + if status: + print(f"Download progress: {int(status.progress() * 100)}%") + + # Write to file with open(output_filename, 'wb') as f: - download_request.execute(f) + f.write(file_io.getvalue()) print(f"Successfully downloaded: {output_filename}") print(f"apk_path={output_filename}") From 75eea7c5b29da510a0152009a0c2285e825d3f01 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 22:41:38 +0800 Subject: [PATCH 07/19] fix: improve universal APK detection logic - Remove hard-coded variantId=1 requirement - Accept any variant ID for base module without splitId - Add better debugging output to show splitId values --- .github/workflows/release.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 92c3b711..7db9769b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -471,12 +471,11 @@ jobs: split_apks = apk['generatedSplitApks'] print(f"Found {len(split_apks)} split APKs") - # Look for the base/universal APK - typically variantId=1, moduleName='base' + # Look for the base/universal APK - try different variant IDs + # Universal APK is the base module without splitId - could be any variantId for split_apk in split_apks: - print(f"Split APK: variantId={split_apk.get('variantId')}, moduleName={split_apk.get('moduleName')}") - # Universal APK is typically the base module without splitId + print(f"Split APK: variantId={split_apk.get('variantId')}, moduleName={split_apk.get('moduleName')}, splitId={split_apk.get('splitId', 'None')}") if (split_apk.get('moduleName') == 'base' and - split_apk.get('variantId') == 1 and 'splitId' not in split_apk): download_id = split_apk.get('downloadId') universal_apk = split_apk From 38a66f9a91587cec7c6f1b097817d33ff40b8162 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 22:47:02 +0800 Subject: [PATCH 08/19] fix: add multiple fallback strategies for APK variant selection - Try variantId=1 without splitId first (original approach) - Fall back to variantId=2 without splitId - Fall back to variantId=3 without splitId - Finally try first base module even with splitId - Add detailed logging for each strategy attempt --- .github/workflows/release.yml | 48 +++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7db9769b..a985a29d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -471,16 +471,48 @@ jobs: split_apks = apk['generatedSplitApks'] print(f"Found {len(split_apks)} split APKs") - # Look for the base/universal APK - try different variant IDs - # Universal APK is the base module without splitId - could be any variantId + # Look for the base/universal APK - try different strategies + # Strategy 1: Base module without splitId + base_candidates = [] for split_apk in split_apks: print(f"Split APK: variantId={split_apk.get('variantId')}, moduleName={split_apk.get('moduleName')}, splitId={split_apk.get('splitId', 'None')}") - if (split_apk.get('moduleName') == 'base' and - 'splitId' not in split_apk): - download_id = split_apk.get('downloadId') - universal_apk = split_apk - print(f"Found universal APK: {universal_apk}") - break + if split_apk.get('moduleName') == 'base': + base_candidates.append(split_apk) + if 'splitId' not in split_apk: + download_id = split_apk.get('downloadId') + universal_apk = split_apk + print(f"Found universal APK (no splitId): {universal_apk}") + break + + # Strategy 2: Try variantId=2 first (might be the actively available variant) + if not download_id: + print(f"Trying variantId=2 base module...") + for split_apk in split_apks: + if (split_apk.get('moduleName') == 'base' and + split_apk.get('variantId') == 2 and + 'splitId' not in split_apk): + download_id = split_apk.get('downloadId') + universal_apk = split_apk + print(f"Found universal APK (variantId=2): {universal_apk}") + break + + # Strategy 3: Try variantId=3 + if not download_id: + print(f"Trying variantId=3 base module...") + for split_apk in split_apks: + if (split_apk.get('moduleName') == 'base' and + split_apk.get('variantId') == 3 and + 'splitId' not in split_apk): + download_id = split_apk.get('downloadId') + universal_apk = split_apk + print(f"Found universal APK (variantId=3): {universal_apk}") + break + + # Strategy 4: If still none, try the first base module even with splitId + if not download_id and base_candidates: + universal_apk = base_candidates[0] + download_id = universal_apk.get('downloadId') + print(f"Using first base module as fallback: {universal_apk}") if download_id: break From 5597c7dcaf23f7cf8efff1f21089ed0fe56c8142 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 22:53:40 +0800 Subject: [PATCH 09/19] fix: prioritize variantId=2 over variantId=1 for APK downloads - Try variantId=2 first since variantId=1 consistently returns 404 - Use variantId=3 as secondary option - Keep variantId=1 as last resort for debugging - Add better logging for all base APK candidates --- .github/workflows/release.yml | 37 +++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a985a29d..b617f5ef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -471,44 +471,47 @@ jobs: split_apks = apk['generatedSplitApks'] print(f"Found {len(split_apks)} split APKs") - # Look for the base/universal APK - try different strategies - # Strategy 1: Base module without splitId + # Look for the base/universal APK - try variantId=2 first since variantId=1 consistently fails + # Print all base modules for debugging base_candidates = [] for split_apk in split_apks: - print(f"Split APK: variantId={split_apk.get('variantId')}, moduleName={split_apk.get('moduleName')}, splitId={split_apk.get('splitId', 'None')}") if split_apk.get('moduleName') == 'base': base_candidates.append(split_apk) - if 'splitId' not in split_apk: - download_id = split_apk.get('downloadId') - universal_apk = split_apk - print(f"Found universal APK (no splitId): {universal_apk}") - break + print(f"Base APK: variantId={split_apk.get('variantId')}, splitId={split_apk.get('splitId', 'None')}") + + # Strategy 1: Try variantId=2 first (most likely to work based on observations) + for split_apk in split_apks: + if (split_apk.get('moduleName') == 'base' and + split_apk.get('variantId') == 2 and + 'splitId' not in split_apk): + download_id = split_apk.get('downloadId') + universal_apk = split_apk + print(f"Found universal APK (variantId=2): {universal_apk}") + break - # Strategy 2: Try variantId=2 first (might be the actively available variant) + # Strategy 2: If variantId=2 not found, try variantId=3 if not download_id: - print(f"Trying variantId=2 base module...") for split_apk in split_apks: if (split_apk.get('moduleName') == 'base' and - split_apk.get('variantId') == 2 and + split_apk.get('variantId') == 3 and 'splitId' not in split_apk): download_id = split_apk.get('downloadId') universal_apk = split_apk - print(f"Found universal APK (variantId=2): {universal_apk}") + print(f"Found universal APK (variantId=3): {universal_apk}") break - # Strategy 3: Try variantId=3 + # Strategy 3: Try variantId=1 as last resort (known to fail) if not download_id: - print(f"Trying variantId=3 base module...") for split_apk in split_apks: if (split_apk.get('moduleName') == 'base' and - split_apk.get('variantId') == 3 and + split_apk.get('variantId') == 1 and 'splitId' not in split_apk): download_id = split_apk.get('downloadId') universal_apk = split_apk - print(f"Found universal APK (variantId=3): {universal_apk}") + print(f"Found universal APK (variantId=1 - last resort): {universal_apk}") break - # Strategy 4: If still none, try the first base module even with splitId + # Strategy 4: Fallback to first base module if not download_id and base_candidates: universal_apk = base_candidates[0] download_id = universal_apk.get('downloadId') From d838b78f2c8ab40f5eb7c59d2a3e555c6aa5e298 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 22:59:05 +0800 Subject: [PATCH 10/19] fix: use download_media method for Google Play API binary downloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changed from download() to download_media() method - This fixes the HTTP 204 error when downloading signed APKs - download_media() returns the actual media content instead of metadata 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b617f5ef..45c9a73c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -530,7 +530,9 @@ jobs: # Step 2: Download the APK using the downloadId print("Downloading APK binary...") - download_request = service.generatedapks().download( + + # Execute the download request to get the media content + download_request = service.generatedapks().download_media( packageName=package_name, versionCode=version_code, downloadId=download_id @@ -538,7 +540,6 @@ jobs: output_filename = f"v2er-{os.environ['VERSION_NAME']}_google_play_signed.apk" - # Execute the download request and save to file # Use media download with googleapiclient.http to handle binary content import io from googleapiclient.http import MediaIoBaseDownload From 644646489ca52f489393661bdfeb31f9ac031217 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 23:00:55 +0800 Subject: [PATCH 11/19] fix: properly look for universal APKs in Google Play API response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - First check generatedUniversalApk field as per API documentation - Fallback to base module in generatedSplitApks if needed - Simplified variant selection logic - This should resolve HTTP 204 errors by using correct APK structure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 90 ++++++++++++++++------------------- 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 45c9a73c..d10d466c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -462,66 +462,58 @@ jobs: print(f" {key}: {value}") print() - # Find universal APK in generatedSplitApks (based on API debug output) + # Find universal APK using the correct API structure download_id = None universal_apk = None + # First, try to find a universal APK in generatedUniversalApk for apk in result['generatedApks']: - if 'generatedSplitApks' in apk: - split_apks = apk['generatedSplitApks'] - print(f"Found {len(split_apks)} split APKs") - - # Look for the base/universal APK - try variantId=2 first since variantId=1 consistently fails - # Print all base modules for debugging - base_candidates = [] - for split_apk in split_apks: - if split_apk.get('moduleName') == 'base': - base_candidates.append(split_apk) - print(f"Base APK: variantId={split_apk.get('variantId')}, splitId={split_apk.get('splitId', 'None')}") - - # Strategy 1: Try variantId=2 first (most likely to work based on observations) - for split_apk in split_apks: - if (split_apk.get('moduleName') == 'base' and - split_apk.get('variantId') == 2 and - 'splitId' not in split_apk): - download_id = split_apk.get('downloadId') - universal_apk = split_apk - print(f"Found universal APK (variantId=2): {universal_apk}") - break - - # Strategy 2: If variantId=2 not found, try variantId=3 - if not download_id: + if 'generatedUniversalApk' in apk: + universal_apk = apk['generatedUniversalApk'] + download_id = universal_apk.get('downloadId') + print(f"Found universal APK: {universal_apk}") + break + + # Fallback: Look for base module in split APKs if no universal APK found + if not download_id: + print("No generatedUniversalApk found, trying split APKs...") + for apk in result['generatedApks']: + if 'generatedSplitApks' in apk: + split_apks = apk['generatedSplitApks'] + print(f"Found {len(split_apks)} split APKs") + + # Print all base modules for debugging + base_candidates = [] for split_apk in split_apks: - if (split_apk.get('moduleName') == 'base' and - split_apk.get('variantId') == 3 and - 'splitId' not in split_apk): - download_id = split_apk.get('downloadId') - universal_apk = split_apk - print(f"Found universal APK (variantId=3): {universal_apk}") - break - - # Strategy 3: Try variantId=1 as last resort (known to fail) - if not download_id: + if split_apk.get('moduleName') == 'base': + base_candidates.append(split_apk) + print(f"Base APK: variantId={split_apk.get('variantId')}, splitId={split_apk.get('splitId', 'None')}") + + # Try variantId=2 first (based on previous observations) for split_apk in split_apks: if (split_apk.get('moduleName') == 'base' and - split_apk.get('variantId') == 1 and + split_apk.get('variantId') == 2 and 'splitId' not in split_apk): download_id = split_apk.get('downloadId') universal_apk = split_apk - print(f"Found universal APK (variantId=1 - last resort): {universal_apk}") + print(f"Found base APK (variantId=2): {universal_apk}") break - - # Strategy 4: Fallback to first base module - if not download_id and base_candidates: - universal_apk = base_candidates[0] - download_id = universal_apk.get('downloadId') - print(f"Using first base module as fallback: {universal_apk}") - - if download_id: - break + + # Try other variants if variantId=2 not found + if not download_id: + for split_apk in split_apks: + if (split_apk.get('moduleName') == 'base' and + 'splitId' not in split_apk): + download_id = split_apk.get('downloadId') + universal_apk = split_apk + print(f"Found base APK (variantId={split_apk.get('variantId')}): {universal_apk}") + break + + if download_id: + break if not download_id: - print("Universal APK not found in generatedSplitApks") + print("No universal or base APK found") print("Available APK structure:") print(json.dumps(result['generatedApks'], indent=2)) return False @@ -530,9 +522,7 @@ jobs: # Step 2: Download the APK using the downloadId print("Downloading APK binary...") - - # Execute the download request to get the media content - download_request = service.generatedapks().download_media( + download_request = service.generatedapks().download( packageName=package_name, versionCode=version_code, downloadId=download_id From 78f6ab6cee9155a594d5acc070c00e9787e3616a Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 23:10:54 +0800 Subject: [PATCH 12/19] fix: add alt=media parameter to Google Play API download request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ?alt=media to download request URL to get binary content - Without alt=media, API returns HTTP 204 with metadata only - This follows Google APIs media download convention 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d10d466c..4332946b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -522,11 +522,14 @@ jobs: # Step 2: Download the APK using the downloadId print("Downloading APK binary...") + + # Use alt=media to get the actual binary content instead of metadata download_request = service.generatedapks().download( packageName=package_name, versionCode=version_code, downloadId=download_id ) + download_request.uri += '?alt=media' output_filename = f"v2er-{os.environ['VERSION_NAME']}_google_play_signed.apk" From 05d0401f314b3ec0f3cc34b8dacdb0f0de5e1085 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 23:15:24 +0800 Subject: [PATCH 13/19] fix: correctly append alt=media parameter to URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Check for existing query params and use & instead of ? - Prevents double question mark (??) in URL which caused HTTP 400 - Now properly handles URLs that already contain query parameters 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4332946b..d43eb8b5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -529,7 +529,11 @@ jobs: versionCode=version_code, downloadId=download_id ) - download_request.uri += '?alt=media' + # Add alt=media parameter correctly (URL already has query params, so use &) + if '?' in download_request.uri: + download_request.uri += '&alt=media' + else: + download_request.uri += '?alt=media' output_filename = f"v2er-{os.environ['VERSION_NAME']}_google_play_signed.apk" From 65e5dc24d275fab1413f76d919288aa0900bd1d1 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 23:22:53 +0800 Subject: [PATCH 14/19] fix: properly integrate download job with play store upload pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add dependency on play-store-upload job - Add proper execution conditions (requires signing and play store upload enabled) - Use dynamic version info from prepare job instead of hardcoded values - Increase wait time to 5 minutes for Google Play processing - Update error messages to be more accurate - Ensure download job only runs after successful Play Store upload 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d43eb8b5..d403a4c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -385,6 +385,8 @@ jobs: download-signed-apk: name: Download Google Play Signed APK + needs: [prepare, play-store-upload] + if: ${{ vars.ENABLE_PLAY_STORE_UPLOAD == 'true' && vars.ENABLE_SIGNING == 'true' }} runs-on: ubuntu-latest steps: @@ -403,7 +405,7 @@ jobs: - name: Wait for Google Play processing run: | echo "Waiting for Google Play to process and sign the APK..." - sleep 120 # Wait 2 minutes for Google Play to process + sleep 300 # Wait 5 minutes for Google Play to process and sign # AAB artifact not needed for Google Play signed APK download @@ -412,8 +414,8 @@ jobs: env: PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} run: | - VERSION_NAME="v2.3.3" - VERSION_CODE="233" + VERSION_NAME="${{ needs.prepare.outputs.version }}" + VERSION_CODE="${{ needs.prepare.outputs.version_code }}" PACKAGE_NAME="me.ghui.v2er" # Create Python script to download signed universal APK @@ -602,8 +604,8 @@ jobs: if: steps.download-apk.outputs.found == 'true' id: play-link run: | - VERSION_NAME="v2.3.3" - VERSION_CODE="233" + VERSION_NAME="${{ needs.prepare.outputs.version }}" + VERSION_CODE="${{ needs.prepare.outputs.version_code }}" # Create info file about Google Play signing cat > "v2er-${VERSION_NAME}_google_play_signed_info.txt" << EOF @@ -631,7 +633,7 @@ jobs: if: steps.download-apk.outputs.found == 'true' uses: softprops/action-gh-release@v2 with: - tag_name: v2.3.3 + tag_name: ${{ needs.prepare.outputs.version }} files: | ${{ steps.download-apk.outputs.apk_path }} ${{ steps.play-link.outputs.info_path }} @@ -646,15 +648,19 @@ jobs: if [ "${{ steps.download-apk.outputs.found }}" = "true" ]; then echo "✅ **Universal APK generated successfully**" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Version**: v2.3.3" >> $GITHUB_STEP_SUMMARY - echo "- **Version Code**: 233" >> $GITHUB_STEP_SUMMARY - echo "- **File**: v2er-v2.3.3_google_play_signed.apk" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Version Code**: ${{ needs.prepare.outputs.version_code }}" >> $GITHUB_STEP_SUMMARY + echo "- **File**: v2er-${{ needs.prepare.outputs.version }}_google_play_signed.apk" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Notes" >> $GITHUB_STEP_SUMMARY echo "- This APK is generated from the AAB uploaded to Google Play" >> $GITHUB_STEP_SUMMARY echo "- When installed from Play Store, it will use Google Play's signing certificate" >> $GITHUB_STEP_SUMMARY echo "- The APK has been uploaded to the GitHub Release" >> $GITHUB_STEP_SUMMARY else - echo "⚠️ **No AAB found in artifacts**" >> $GITHUB_STEP_SUMMARY - echo "Signed APK generation requires a release bundle (AAB)" >> $GITHUB_STEP_SUMMARY + echo "⚠️ **Google Play signed APK download failed**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "This may be because:" >> $GITHUB_STEP_SUMMARY + echo "- Google Play is still processing the upload" >> $GITHUB_STEP_SUMMARY + echo "- The version hasn't been released to any track yet" >> $GITHUB_STEP_SUMMARY + echo "- API permissions are insufficient" >> $GITHUB_STEP_SUMMARY fi \ No newline at end of file From 308c13a0331f1ad1ff45c766c5bb25b214b43f44 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 23:29:13 +0800 Subject: [PATCH 15/19] feat: add smart APK availability checking before waiting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Check if Google Play signed APK already exists before waiting - Implement periodic polling (every 30s) with 10min maximum wait - Skip unnecessary waiting if APK is already available - Provide better progress feedback during waiting - Reduce overall pipeline execution time when APK is ready 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 111 +++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d403a4c2..6ab03c93 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -402,10 +402,115 @@ jobs: run: | pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib requests - - name: Wait for Google Play processing + - name: Check and wait for Google Play processing + env: + PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} run: | - echo "Waiting for Google Play to process and sign the APK..." - sleep 300 # Wait 5 minutes for Google Play to process and sign + VERSION_CODE="${{ needs.prepare.outputs.version_code }}" + PACKAGE_NAME="me.ghui.v2er" + + # Create Python script to check if APK exists + cat > check_apk_exists.py << 'EOF' + import json + import os + import sys + import time + from google.oauth2 import service_account + from googleapiclient.discovery import build + + def check_apk_exists(): + try: + # Load service account credentials + service_account_info = json.loads(os.environ['PLAY_STORE_SERVICE_ACCOUNT_JSON']) + credentials = service_account.Credentials.from_service_account_info( + service_account_info, + scopes=['https://www.googleapis.com/auth/androidpublisher'] + ) + + # Build the service + service = build('androidpublisher', 'v3', credentials=credentials) + + package_name = os.environ['PACKAGE_NAME'] + version_code = int(os.environ['VERSION_CODE']) + + print(f"Checking if signed APK exists for {package_name} version {version_code}") + + # Try to get the generated APKs list + result = service.generatedapks().list( + packageName=package_name, + versionCode=version_code + ).execute() + + if 'generatedApks' not in result or not result['generatedApks']: + print(f"No generated APKs found for version {version_code}") + return False + + print(f"Found {len(result['generatedApks'])} generated APK groups") + + # Check if we can find a universal APK + for apk in result['generatedApks']: + if 'generatedUniversalApk' in apk: + universal_apk = apk['generatedUniversalApk'] + download_id = universal_apk.get('downloadId') + if download_id: + print(f"✅ Universal APK found with downloadId: {download_id}") + return True + + # Fallback: check split APKs for base module + if 'generatedSplitApks' in apk: + split_apks = apk['generatedSplitApks'] + for split_apk in split_apks: + if (split_apk.get('moduleName') == 'base' and + 'splitId' not in split_apk): + download_id = split_apk.get('downloadId') + if download_id: + print(f"✅ Base APK found with downloadId: {download_id}") + return True + + print("❌ No suitable APK found") + return False + + except Exception as e: + print(f"Error checking APK: {str(e)}") + return False + + if __name__ == "__main__": + exists = check_apk_exists() + sys.exit(0 if exists else 1) + EOF + + # Set environment variables for the script + export PACKAGE_NAME="$PACKAGE_NAME" + export VERSION_CODE="$VERSION_CODE" + + # Check if APK already exists + echo "Checking if Google Play signed APK is ready..." + if python3 check_apk_exists.py; then + echo "✅ APK is already available, skipping wait" + else + echo "⏳ APK not ready yet, waiting for Google Play to process..." + + # Smart waiting with periodic checks + MAX_WAIT=600 # Maximum 10 minutes + CHECK_INTERVAL=30 # Check every 30 seconds + elapsed=0 + + while [ $elapsed -lt $MAX_WAIT ]; do + sleep $CHECK_INTERVAL + elapsed=$((elapsed + CHECK_INTERVAL)) + + echo "⏱️ Waited ${elapsed}s, checking again..." + if python3 check_apk_exists.py; then + echo "✅ APK is now available after ${elapsed}s" + break + fi + + if [ $elapsed -ge $MAX_WAIT ]; then + echo "⚠️ Maximum wait time (${MAX_WAIT}s) reached" + echo "APK may still be processing, will attempt download anyway" + fi + done + fi # AAB artifact not needed for Google Play signed APK download From 50a115b214364f0dea23a2aa1647c7c45aa883eb Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 23:34:50 +0800 Subject: [PATCH 16/19] test: temporarily use version 233 to test smart APK checking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use known existing version 2.3.3 (code 233) in Google Play - This should skip waiting and go directly to download - Will revert after testing to use dynamic version 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ab03c93..6e879070 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -406,7 +406,8 @@ jobs: env: PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} run: | - VERSION_CODE="${{ needs.prepare.outputs.version_code }}" + # Temporarily test with known existing version 2.3.3 (version code 233) + VERSION_CODE="233" PACKAGE_NAME="me.ghui.v2er" # Create Python script to check if APK exists From 5c09b10e258866fa5d99aecaff8cc99251c2121b Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 23:40:54 +0800 Subject: [PATCH 17/19] test: make download job independent for testing smart check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove job dependencies temporarily - Use hardcoded version 2.3.3 (233) which exists in Google Play - This should demonstrate immediate APK detection and skip waiting - Will revert after testing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e879070..da4009af 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -263,7 +263,8 @@ jobs: - name: Create Release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ needs.prepare.outputs.version }} + # TESTING: Use hardcoded tag + tag_name: v2.3.3-test name: Release ${{ needs.prepare.outputs.version }} body_path: CHANGELOG.md draft: false @@ -385,8 +386,9 @@ jobs: download-signed-apk: name: Download Google Play Signed APK - needs: [prepare, play-store-upload] - if: ${{ vars.ENABLE_PLAY_STORE_UPLOAD == 'true' && vars.ENABLE_SIGNING == 'true' }} + # TESTING: Temporarily remove dependencies for testing + # needs: [prepare, play-store-upload] + # if: ${{ vars.ENABLE_PLAY_STORE_UPLOAD == 'true' && vars.ENABLE_SIGNING == 'true' }} runs-on: ubuntu-latest steps: @@ -520,8 +522,9 @@ jobs: env: PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} run: | - VERSION_NAME="${{ needs.prepare.outputs.version }}" - VERSION_CODE="${{ needs.prepare.outputs.version_code }}" + # TESTING: Use hardcoded version 2.3.3 which exists in Google Play + VERSION_NAME="v2.3.3" + VERSION_CODE="233" PACKAGE_NAME="me.ghui.v2er" # Create Python script to download signed universal APK @@ -710,8 +713,9 @@ jobs: if: steps.download-apk.outputs.found == 'true' id: play-link run: | - VERSION_NAME="${{ needs.prepare.outputs.version }}" - VERSION_CODE="${{ needs.prepare.outputs.version_code }}" + # TESTING: Use hardcoded version 2.3.3 + VERSION_NAME="v2.3.3" + VERSION_CODE="233" # Create info file about Google Play signing cat > "v2er-${VERSION_NAME}_google_play_signed_info.txt" << EOF @@ -739,7 +743,8 @@ jobs: if: steps.download-apk.outputs.found == 'true' uses: softprops/action-gh-release@v2 with: - tag_name: ${{ needs.prepare.outputs.version }} + # TESTING: Use hardcoded tag + tag_name: v2.3.3-test files: | ${{ steps.download-apk.outputs.apk_path }} ${{ steps.play-link.outputs.info_path }} @@ -754,9 +759,9 @@ jobs: if [ "${{ steps.download-apk.outputs.found }}" = "true" ]; then echo "✅ **Universal APK generated successfully**" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Version**: ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "- **Version Code**: ${{ needs.prepare.outputs.version_code }}" >> $GITHUB_STEP_SUMMARY - echo "- **File**: v2er-${{ needs.prepare.outputs.version }}_google_play_signed.apk" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: v2.3.3" >> $GITHUB_STEP_SUMMARY + echo "- **Version Code**: 233" >> $GITHUB_STEP_SUMMARY + echo "- **File**: v2er-v2.3.3_google_play_signed.apk" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Notes" >> $GITHUB_STEP_SUMMARY echo "- This APK is generated from the AAB uploaded to Google Play" >> $GITHUB_STEP_SUMMARY From 54dd6035ba4dc33293f9481aa5ad261acb9e385e Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 23:46:06 +0800 Subject: [PATCH 18/19] Revert "test: temporarily use version 233 to test smart APK checking" This reverts commit 50a115b214364f0dea23a2aa1647c7c45aa883eb. --- .github/workflows/release.yml | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index da4009af..6ab03c93 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -263,8 +263,7 @@ jobs: - name: Create Release uses: softprops/action-gh-release@v2 with: - # TESTING: Use hardcoded tag - tag_name: v2.3.3-test + tag_name: ${{ needs.prepare.outputs.version }} name: Release ${{ needs.prepare.outputs.version }} body_path: CHANGELOG.md draft: false @@ -386,9 +385,8 @@ jobs: download-signed-apk: name: Download Google Play Signed APK - # TESTING: Temporarily remove dependencies for testing - # needs: [prepare, play-store-upload] - # if: ${{ vars.ENABLE_PLAY_STORE_UPLOAD == 'true' && vars.ENABLE_SIGNING == 'true' }} + needs: [prepare, play-store-upload] + if: ${{ vars.ENABLE_PLAY_STORE_UPLOAD == 'true' && vars.ENABLE_SIGNING == 'true' }} runs-on: ubuntu-latest steps: @@ -408,8 +406,7 @@ jobs: env: PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} run: | - # Temporarily test with known existing version 2.3.3 (version code 233) - VERSION_CODE="233" + VERSION_CODE="${{ needs.prepare.outputs.version_code }}" PACKAGE_NAME="me.ghui.v2er" # Create Python script to check if APK exists @@ -522,9 +519,8 @@ jobs: env: PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }} run: | - # TESTING: Use hardcoded version 2.3.3 which exists in Google Play - VERSION_NAME="v2.3.3" - VERSION_CODE="233" + VERSION_NAME="${{ needs.prepare.outputs.version }}" + VERSION_CODE="${{ needs.prepare.outputs.version_code }}" PACKAGE_NAME="me.ghui.v2er" # Create Python script to download signed universal APK @@ -713,9 +709,8 @@ jobs: if: steps.download-apk.outputs.found == 'true' id: play-link run: | - # TESTING: Use hardcoded version 2.3.3 - VERSION_NAME="v2.3.3" - VERSION_CODE="233" + VERSION_NAME="${{ needs.prepare.outputs.version }}" + VERSION_CODE="${{ needs.prepare.outputs.version_code }}" # Create info file about Google Play signing cat > "v2er-${VERSION_NAME}_google_play_signed_info.txt" << EOF @@ -743,8 +738,7 @@ jobs: if: steps.download-apk.outputs.found == 'true' uses: softprops/action-gh-release@v2 with: - # TESTING: Use hardcoded tag - tag_name: v2.3.3-test + tag_name: ${{ needs.prepare.outputs.version }} files: | ${{ steps.download-apk.outputs.apk_path }} ${{ steps.play-link.outputs.info_path }} @@ -759,9 +753,9 @@ jobs: if [ "${{ steps.download-apk.outputs.found }}" = "true" ]; then echo "✅ **Universal APK generated successfully**" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Version**: v2.3.3" >> $GITHUB_STEP_SUMMARY - echo "- **Version Code**: 233" >> $GITHUB_STEP_SUMMARY - echo "- **File**: v2er-v2.3.3_google_play_signed.apk" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Version Code**: ${{ needs.prepare.outputs.version_code }}" >> $GITHUB_STEP_SUMMARY + echo "- **File**: v2er-${{ needs.prepare.outputs.version }}_google_play_signed.apk" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Notes" >> $GITHUB_STEP_SUMMARY echo "- This APK is generated from the AAB uploaded to Google Play" >> $GITHUB_STEP_SUMMARY From f325d5fc2ce8727c55ba9a8bb5ac3fcc9cabe2ee Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Tue, 9 Sep 2025 23:56:03 +0800 Subject: [PATCH 19/19] refactor: remove unnecessary split APK fallback logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Universal APK is guaranteed to exist for all releases - Removed fallback logic that looked for base modules in split APKs - Simplified code by only checking for generatedUniversalApk field - This makes the code cleaner and more maintainable 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/release.yml | 53 ++--------------------------------- 1 file changed, 2 insertions(+), 51 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ab03c93..851b35ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -455,19 +455,8 @@ jobs: if download_id: print(f"✅ Universal APK found with downloadId: {download_id}") return True - - # Fallback: check split APKs for base module - if 'generatedSplitApks' in apk: - split_apks = apk['generatedSplitApks'] - for split_apk in split_apks: - if (split_apk.get('moduleName') == 'base' and - 'splitId' not in split_apk): - download_id = split_apk.get('downloadId') - if download_id: - print(f"✅ Base APK found with downloadId: {download_id}") - return True - print("❌ No suitable APK found") + print("❌ No universal APK found") return False except Exception as e: @@ -581,46 +570,8 @@ jobs: print(f"Found universal APK: {universal_apk}") break - # Fallback: Look for base module in split APKs if no universal APK found if not download_id: - print("No generatedUniversalApk found, trying split APKs...") - for apk in result['generatedApks']: - if 'generatedSplitApks' in apk: - split_apks = apk['generatedSplitApks'] - print(f"Found {len(split_apks)} split APKs") - - # Print all base modules for debugging - base_candidates = [] - for split_apk in split_apks: - if split_apk.get('moduleName') == 'base': - base_candidates.append(split_apk) - print(f"Base APK: variantId={split_apk.get('variantId')}, splitId={split_apk.get('splitId', 'None')}") - - # Try variantId=2 first (based on previous observations) - for split_apk in split_apks: - if (split_apk.get('moduleName') == 'base' and - split_apk.get('variantId') == 2 and - 'splitId' not in split_apk): - download_id = split_apk.get('downloadId') - universal_apk = split_apk - print(f"Found base APK (variantId=2): {universal_apk}") - break - - # Try other variants if variantId=2 not found - if not download_id: - for split_apk in split_apks: - if (split_apk.get('moduleName') == 'base' and - 'splitId' not in split_apk): - download_id = split_apk.get('downloadId') - universal_apk = split_apk - print(f"Found base APK (variantId={split_apk.get('variantId')}): {universal_apk}") - break - - if download_id: - break - - if not download_id: - print("No universal or base APK found") + print("No universal APK found") print("Available APK structure:") print(json.dumps(result['generatedApks'], indent=2)) return False