Merge pull request #17 from HereLiesAz/fix/missing-import-properties-… #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Merged Build & Release | |
| on: | |
| push: | |
| branches: [ "**" ] | |
| pull_request: | |
| branches: [ "main" ] | |
| workflow_dispatch: | |
| # ⚡ Concurrency: Cancels the entire job if a new push comes in. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build-and-release: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| checks: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Inject Google Services | |
| env: | |
| GOOGLE_SERVICES_API_KEY: ${{ secrets.GOOGLE_SERVICES_API_KEY }} | |
| PROJECT_ID: ${{ secrets.PROJECT_ID }} | |
| CLIENT_ID: ${{ secrets.CLIENT_ID }} | |
| ARCORE_API_KEY: ${{ secrets.ARCORE_API_KEY }} | |
| run: | | |
| if [ -f "app/google-services.json.template" ]; then | |
| echo "Template found. Injecting secrets..." | |
| sed -e 's/{{\([^}]*\)}}/${\1}/g' app/google-services.json.template | envsubst > app/google-services.json | |
| else | |
| echo "Warning: google-services.json.template not found. Skipping injection." | |
| fi | |
| # Inject ARCore API Key into local.properties for build.gradle.kts to pick up | |
| echo "ARCORE_API_KEY=$ARCORE_API_KEY" >> local.properties | |
| - name: Generate Keystore from Secrets | |
| env: | |
| KEYSTORE_PRIVATE: ${{ secrets.KEYSTORE_PRIVATE }} | |
| KEYSTORE_CHAIN: ${{ secrets.KEYSTORE_CHAIN }} | |
| KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.KEY_ALIAS }} | |
| run: | | |
| echo "Generating keystore from certificate chain and private key..." | |
| # Write the private key and certificate chain to temporary files | |
| echo "$KEYSTORE_PRIVATE" > private_key.pem | |
| echo "$KEYSTORE_CHAIN" > certificate_chain.crt | |
| # Create a PKCS12 file from the key and certificate chain | |
| openssl pkcs12 -export -in certificate_chain.crt -inkey private_key.pem \ | |
| -name "$KEY_ALIAS" -out keystore.p12 \ | |
| -password pass:"$KEYSTORE_PASSWORD" | |
| # Convert the PKCS12 file to a JKS keystore | |
| keytool -importkeystore -srckeystore keystore.p12 -srcstoretype PKCS12 \ | |
| -destkeystore app/keystore.jks -deststoretype JKS \ | |
| -srcstorepass "$KEYSTORE_PASSWORD" \ | |
| -deststorepass "$KEYSTORE_PASSWORD" \ | |
| -alias "$KEY_ALIAS" -noprompt | |
| echo "Keystore generated successfully at app/keystore.jks" | |
| # Clean up temporary files | |
| rm private_key.pem certificate_chain.crt keystore.p12 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '17' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v4 | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| with: | |
| cache-read-only: false | |
| dependency-graph: disabled | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| - name: Build with Gradle | |
| id: gradle-build | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_TOKEN: ${{ secrets.GH_TOKEN }} | |
| KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.KEY_ALIAS }} | |
| KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} | |
| run: | | |
| set +e | |
| export BUILD_NUMBER=$(git rev-list --count HEAD) | |
| # Build the APK | |
| ./gradlew assembleDebug -PversionBuild=$BUILD_NUMBER --build-cache \ | |
| -Pandroid.injected.signing.store.file=$(pwd)/app/keystore.jks \ | |
| -Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD \ | |
| -Pandroid.injected.signing.key.alias=$KEY_ALIAS \ | |
| -Pandroid.injected.signing.key.password=$KEY_PASSWORD > build.log 2>&1 | |
| EXIT_CODE=$? | |
| echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT | |
| cat build.log | |
| exit $EXIT_CODE | |
| # ------------------------------------------------------------------ | |
| # FAILURE HANDLING | |
| # ------------------------------------------------------------------ | |
| - name: Report Failure to Jules | |
| if: failure() && steps.gradle-build.outcome == 'failure' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| let logTail = 'Log file not found.'; | |
| try { logTail = fs.readFileSync('build.log', 'utf8').slice(-4000); } catch (e) {} | |
| const assignee = 'gemini-code-assist'; | |
| try { | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: `Build Failure on ${context.eventName === 'pull_request' ? 'PR #' + context.issue.number : context.ref}`, | |
| body: `@${assignee} /jules debug this build error\n\n**Log:**\n\`\`\`\n${logTail}\n\`\`\``, | |
| labels: ['jules', 'bug'], | |
| assignees: [assignee] | |
| }); | |
| } catch (error) { | |
| console.log("Failed to create issue with assignee. Retrying without assignee."); | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: `Build Failure on ${context.eventName === 'pull_request' ? 'PR #' + context.issue.number : context.ref}`, | |
| body: `@${assignee} /jules debug this build error\n\n**Log:**\n\`\`\`\n${logTail}\n\`\`\``, | |
| labels: ['jules', 'bug'] | |
| }); | |
| } | |
| # ------------------------------------------------------------------ | |
| # RELEASE STEPS (Only run on PUSH, not Pull Requests) | |
| # ------------------------------------------------------------------ | |
| - name: Prepare Release Variables | |
| if: success() && github.event_name == 'push' | |
| id: release_vars | |
| run: | | |
| export BUILD_NUMBER=$(git rev-list --count HEAD) | |
| # Extract version info from properties file | |
| MAJOR=$(grep 'versionMajor=' version.properties | cut -d'=' -f2 | tr -d '\r ') | |
| MINOR=$(grep 'versionMinor=' version.properties | cut -d'=' -f2 | tr -d '\r ') | |
| # Calculate Patch version programmatically based on commits since Minor update | |
| MINOR_COMMIT=$(git blame -L '/versionMinor=/',+1 version.properties | awk '{print $1}' | tr -d '^') | |
| if [ -n "$MINOR_COMMIT" ]; then | |
| PATCH=$(git rev-list --count $MINOR_COMMIT..HEAD) | |
| else | |
| PATCH=$(grep 'versionPatch=' version.properties | cut -d'=' -f2 | tr -d '\r ') | |
| fi | |
| VERSION_NAME="$MAJOR.$MINOR.$PATCH.$BUILD_NUMBER" | |
| TAG_NAME="latest-debug-v${MAJOR}.${MINOR}" | |
| # Dynamic App Name from settings.gradle | |
| if [ -f "settings.gradle.kts" ]; then | |
| APP_NAME=$(grep 'rootProject.name' settings.gradle.kts | sed -E "s/.*=.*[\"'](.*)[\"']/\1/") | |
| elif [ -f "settings.gradle" ]; then | |
| APP_NAME=$(grep 'rootProject.name' settings.gradle | sed -E "s/.*=.*[\"'](.*)[\"']/\1/") | |
| else | |
| APP_NAME="App" | |
| fi | |
| # Locate built APK | |
| APK_ORIGINAL=$(find app/build/outputs/apk/debug -name "*.apk" | head -n 1) | |
| if [ -z "$APK_ORIGINAL" ]; then | |
| echo "Error: No APK found to release!" | |
| exit 1 | |
| fi | |
| TARGET_NAME="${APP_NAME}-${VERSION_NAME}-debug.apk" | |
| # Rename for release | |
| mv "$APK_ORIGINAL" "$TARGET_NAME" | |
| echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV | |
| echo "APK_FILE=$TARGET_NAME" >> $GITHUB_ENV | |
| # Package Build Tools (Optional) | |
| mkdir -p build_tools_package/tools build_tools_package/native | |
| [ -d "app/src/main/assets/tools" ] && cp -r app/src/main/assets/tools/* build_tools_package/tools/ || true | |
| [ -d "app/src/main/jniLibs/arm64-v8a" ] && cp -r app/src/main/jniLibs/arm64-v8a/* build_tools_package/native/ || true | |
| cd build_tools_package && zip -r ../build-tools.zip . && cd .. | |
| - name: Publish Release | |
| if: success() && github.event_name == 'push' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # Force update tag to point to current commit | |
| git tag -fa $TAG_NAME -m "Latest Debug Build" | |
| git push origin $TAG_NAME --force | |
| BUILD_TOOLS="build-tools.zip" | |
| # Check if release exists | |
| if gh release view $TAG_NAME > /dev/null 2>&1; then | |
| echo "Updating existing release..." | |
| # Clean up ONLY the build-tools asset to avoid conflicts | |
| gh release delete-asset "$TAG_NAME" "$BUILD_TOOLS" -y || true | |
| gh release edit $TAG_NAME --prerelease \ | |
| --title "Latest Debug Build ($TAG_NAME)" \ | |
| --notes "Auto-build from commit $GITHUB_SHA" \ | |
| --target $GITHUB_SHA | |
| # Clobber ensures that if we re-run the workflow on the exact same commit, it replaces the APK instead of failing. | |
| gh release upload $TAG_NAME "$APK_FILE" --clobber | |
| [ -f "$BUILD_TOOLS" ] && gh release upload $TAG_NAME "$BUILD_TOOLS" --clobber | |
| else | |
| echo "Creating new release..." | |
| gh release create $TAG_NAME "$APK_FILE" --prerelease \ | |
| --title "Latest Debug Build ($TAG_NAME)" \ | |
| --notes "Auto-build from commit $GITHUB_SHA" \ | |
| --target $GITHUB_SHA | |
| [ -f "$BUILD_TOOLS" ] && gh release upload $TAG_NAME "$BUILD_TOOLS" --clobber | |
| fi |