Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
493f3a4
Add branch protections and CI/CD workflows
iAmVishal16 Nov 10, 2025
1cc962b
Add Google Play Console upload and fix tag verification
iAmVishal16 Nov 12, 2025
a7b9767
Fix Codex review issues: inputs context, unsigned APK cleanup, check …
iAmVishal16 Nov 12, 2025
5c8d7d1
Remove configure_branch_protection.sh script and update docs
iAmVishal16 Nov 12, 2025
14c9fff
Fix Android SDK version: use API 35 instead of 36
iAmVishal16 Nov 12, 2025
f5ea223
Install Android API 36 manually via sdkmanager
iAmVishal16 Nov 12, 2025
cc32b80
Fix Android SDK setup: remove cmdline-tools from packages list
iAmVishal16 Nov 12, 2025
e78ffa4
Fix Android SDK package installation: install packages separately
iAmVishal16 Nov 12, 2025
79965d9
Enable automatic Play Console upload on main branch pushes
iAmVishal16 Nov 12, 2025
fb7205e
Fix workflow validation: remove secret checks from if conditions
iAmVishal16 Nov 12, 2025
262de0e
Temporarily trigger release workflow on ci-cd-setup branch
iAmVishal16 Nov 12, 2025
52f5333
Switch release workflow to upload Android App Bundle
iAmVishal16 Nov 12, 2025
98c4a59
Replace APK signing with AAB signing using jarsigner
iAmVishal16 Nov 12, 2025
6a7d4e7
Add auto-increment version code from Google Play Console
iAmVishal16 Nov 12, 2025
87f718d
Improve version code auto-increment with better API querying
iAmVishal16 Nov 12, 2025
0dc59b4
Fix Python syntax error: use sys.exit() instead of exit
iAmVishal16 Nov 12, 2025
7ed5013
Remove temporary ci-cd-setup branch from workflow triggers
iAmVishal16 Nov 12, 2025
317ebc9
Extract Python script to separate file
iAmVishal16 Nov 12, 2025
72c8def
Fail workflow if signing secrets are missing
iAmVishal16 Nov 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/build-apk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Build APK

on:
push:
branches:
- main
pull_request:
branches:
- main

concurrency:
group: build-apk-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Assemble Release APK
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
cache: gradle

- name: Set up Android SDK
uses: android-actions/setup-android@v3

- name: Accept Android SDK licenses and install packages
run: |
# Find sdkmanager in common locations
if [ -f "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then
SDK_MANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
elif [ -f "$ANDROID_HOME/cmdline-tools/bin/sdkmanager" ]; then
SDK_MANAGER="$ANDROID_HOME/cmdline-tools/bin/sdkmanager"
elif [ -f "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then
SDK_MANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager"
else
echo "Error: sdkmanager not found"
exit 1
fi
echo "Using sdkmanager at: $SDK_MANAGER"

# Accept all licenses first
yes | "$SDK_MANAGER" --licenses

# Install packages separately (as per Stack Overflow solution)
# Install platform-tools first
yes | "$SDK_MANAGER" "platform-tools"
# Install Android API 35
yes | "$SDK_MANAGER" "platforms;android-35"
# Install build-tools
yes | "$SDK_MANAGER" "build-tools;35.0.0"
# Install Android API 36
yes | "$SDK_MANAGER" "platforms;android-36"

- name: Cache Gradle
uses: gradle/actions/setup-gradle@v4

- name: Grant execute permission for Gradle wrapper
run: chmod +x gradlew

- name: Assemble release APK
run: ./gradlew assembleRelease

- name: Upload release APK
uses: actions/upload-artifact@v4
with:
name: app-release-apk
path: app/build/outputs/apk/release/*.apk

196 changes: 196 additions & 0 deletions .github/workflows/release-apk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
name: Release APK

on:
workflow_dispatch:
inputs:
release_name:
description: Override release name (defaults to tag or ref name).
required: false
play_track:
description: Google Play release track (internal, alpha, beta, production).
required: false
default: internal
type: choice
options:
- internal
- alpha
- beta
- production
push:
branches:
- main

permissions:
contents: write

env:
BUILD_TOOLS_VERSION: 35.0.0
RELEASE_BUNDLE_PATH: app/build/outputs/bundle/release/app-release.aab

jobs:
release:
name: Build and Publish
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Verify ref on main (workflow_dispatch)
if: github.event_name == 'workflow_dispatch'
run: |
if [ "${GITHUB_REF}" != "refs/heads/main" ]; then
echo "Workflow must target the main branch. Ref: ${GITHUB_REF}" >&2
exit 1
fi

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
cache: gradle

- name: Set up Android SDK
uses: android-actions/setup-android@v3

- name: Accept Android SDK licenses and install packages
run: |
# Find sdkmanager in common locations
if [ -f "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then
SDK_MANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
elif [ -f "$ANDROID_HOME/cmdline-tools/bin/sdkmanager" ]; then
SDK_MANAGER="$ANDROID_HOME/cmdline-tools/bin/sdkmanager"
elif [ -f "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then
SDK_MANAGER="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager"
else
echo "Error: sdkmanager not found"
exit 1
fi
echo "Using sdkmanager at: $SDK_MANAGER"

# Accept all licenses first
yes | "$SDK_MANAGER" --licenses

# Install packages separately (as per Stack Overflow solution)
# Install platform-tools first
yes | "$SDK_MANAGER" "platform-tools"
# Install Android API 35
yes | "$SDK_MANAGER" "platforms;android-35"
# Install build-tools
yes | "$SDK_MANAGER" "build-tools;${{ env.BUILD_TOOLS_VERSION }}"
# Install Android API 36
yes | "$SDK_MANAGER" "platforms;android-36"

- name: Cache Gradle
uses: gradle/actions/setup-gradle@v4

- name: Grant execute permission for Gradle wrapper
run: chmod +x gradlew

- name: Auto-increment version code from Google Play
continue-on-error: true
env:
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
run: |
if [ -z "$GOOGLE_PLAY_SERVICE_ACCOUNT_JSON" ]; then
echo "Google Play service account not configured, skipping version code check"
Comment thread
iAmVishal16 marked this conversation as resolved.
exit 0
fi

# Write service account JSON to file
echo "$GOOGLE_PLAY_SERVICE_ACCOUNT_JSON" > /tmp/service_account.json

# Install Python dependencies
python3 -m pip install --quiet google-api-python-client google-auth-httplib2 google-auth-oauthlib

# Run the version code increment script
python3 scripts/increment_version_code.py

- name: Build release bundle
run: ./gradlew bundleRelease

- name: Decode keystore
run: |
if [ -z "${{ secrets.ANDROID_SIGNING_KEYSTORE_BASE64 }}" ]; then
echo "ERROR: ANDROID_SIGNING_KEYSTORE_BASE64 secret is missing. Signing secrets are required for release builds." >&2
exit 1
fi
echo "${{ secrets.ANDROID_SIGNING_KEYSTORE_BASE64 }}" | base64 --decode > release.keystore
if [ ! -f "release.keystore" ] || [ ! -s "release.keystore" ]; then
echo "ERROR: Failed to decode keystore. The secret may be invalid." >&2
exit 1
fi

- name: Sign AAB
env:
KEY_ALIAS: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}
STORE_PASSWORD: ${{ secrets.ANDROID_SIGNING_KEYSTORE_PASSWORD }}
run: |
# Validate all signing secrets are present
if [ -z "$KEY_ALIAS" ] || [ -z "$KEY_PASSWORD" ] || [ -z "$STORE_PASSWORD" ]; then
echo "ERROR: One or more signing secrets are missing:" >&2
echo " - ANDROID_SIGNING_KEY_ALIAS: $([ -z "$KEY_ALIAS" ] && echo 'MISSING' || echo 'present')" >&2
echo " - ANDROID_SIGNING_KEY_PASSWORD: $([ -z "$KEY_PASSWORD" ] && echo 'MISSING' || echo 'present')" >&2
echo " - ANDROID_SIGNING_KEYSTORE_PASSWORD: $([ -z "$STORE_PASSWORD" ] && echo 'MISSING' || echo 'present')" >&2
exit 1
fi

if [ ! -f "release.keystore" ]; then
echo "ERROR: Keystore file not found. Decode keystore step must have failed." >&2
exit 1
fi

AAB_PATH=$(ls app/build/outputs/bundle/release/*.aab | head -n 1)
if [ -z "$AAB_PATH" ]; then
echo "ERROR: No AAB produced at app/build/outputs/bundle/release." >&2
exit 1
fi

# Sign AAB using jarsigner
jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
-keystore release.keystore \
-storepass "$STORE_PASSWORD" \
-keypass "$KEY_PASSWORD" \
"$AAB_PATH" \
"$KEY_ALIAS"

# Verify the signature
jarsigner -verify -verbose -certs "$AAB_PATH"
if [ $? -ne 0 ]; then
echo "ERROR: Signature verification failed." >&2
exit 1
fi
echo "✓ AAB signed and verified successfully"

- name: Set release variables
id: release_vars
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "play_track=${{ github.event.inputs.play_track || 'internal' }}" >> $GITHUB_OUTPUT
echo "release_name=${{ github.event.inputs.release_name || github.ref_name }}" >> $GITHUB_OUTPUT
else
# Automatic upload to internal track for main branch pushes
echo "play_track=internal" >> $GITHUB_OUTPUT
echo "release_name=main-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
fi

- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: release-bundle
path: app/build/outputs/bundle/release/*.aab

- name: Upload to Google Play Console
continue-on-error: true
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
packageName: llc.fungee.IngrediCheck
releaseFiles: app/build/outputs/bundle/release/*.aab
track: ${{ steps.release_vars.outputs.play_track }}
status: completed

99 changes: 99 additions & 0 deletions docs/ci-cd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# CI/CD & Release Guide

This project enforces guarded changes on `main` and publishes release APKs via GitHub Actions. Use this guide to configure repository settings, maintain secrets, and operate the pipelines.

## Branch Protection

Main must remain fast-forward only and receive all changes through reviewed pull requests.

1. **Enable protection in GitHub**
- Go to `Settings → Branches → Branch protection rules → Add rule`.
- Target branch name pattern: `main`.
- Enable these options:
- Require a pull request before merging (minimum one approval, dismiss stale reviews).
- Require status checks to pass (`Build APK / Assemble Release APK`).
- Require branches to be up to date before merging.
- Include administrators (prevents accidental direct pushes).
- Disallow force pushes and deletions.

## Required GitHub Secrets

| Secret | Purpose | Notes |
| --- | --- | --- |
| `ANDROID_SIGNING_KEYSTORE_BASE64` | Base64-encoded `.jks`/`.keystore` file | Optional; omit to produce unsigned release artifacts. |
| `ANDROID_SIGNING_KEYSTORE_PASSWORD` | Password for the keystore | Required for signing. |
| `ANDROID_SIGNING_KEY_ALIAS` | Alias inside the keystore | Required for signing. |
| `ANDROID_SIGNING_KEY_PASSWORD` | Password for the key alias | Required for signing. |
| `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` | Google Play service account JSON key | Optional; omit to skip Play Console upload. See setup instructions below. |

If signing secrets are not provided, releases remain unsigned but still build successfully. If the Google Play secret is not provided, the workflow will skip Play Console upload but still produce artifacts.

### Setting up Google Play Service Account

To enable automatic uploads to Google Play Console:

1. **Create a service account in Google Cloud Console:**
- Go to [Google Cloud Console](https://console.cloud.google.com/)
- Create a new project or select an existing one
- Navigate to `IAM & Admin → Service Accounts`
- Click `Create Service Account`
- Provide a name (e.g., "github-actions-play-upload") and description
- Click `Create and Continue`

2. **Grant Play Console permissions:**
- In the service account details, note the email address (format: `name@project-id.iam.gserviceaccount.com`)
- Go to [Google Play Console](https://play.google.com/console/)
- Navigate to `Setup → API access`
- Find your service account email and click `Grant access`
- Grant the following permissions:
- `View app information and download bulk reports`
- `Manage production releases`
- `Manage testing track releases` (if using internal/alpha/beta tracks)
- Save the changes

3. **Create and download the JSON key:**
- Return to Google Cloud Console → Service Accounts
- Click on your service account
- Go to the `Keys` tab
- Click `Add Key → Create new key`
- Choose `JSON` format
- Download the JSON file

4. **Add the secret to GitHub:**
- Open the downloaded JSON file and copy its entire contents
- Go to your GitHub repository → `Settings → Secrets and variables → Actions`
- Click `New repository secret`
- Name: `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON`
- Value: Paste the entire JSON file contents
- Click `Add secret`

The workflow will now automatically upload signed APKs to the selected Play Console track when triggered.

## Workflows

### Build APK (`.github/workflows/build-apk.yml`)

- Triggers on pull requests to `main` and pushes directly to `main`.
- Builds the release variant using JDK 17 and Android SDK.
- Publishes the generated APK as an artifact (`app-release-apk`) for validation.
- Use this workflow as a required status check in the branch protection rule.

### Release APK (`.github/workflows/release-apk.yml`)

- Trigger modes:
- **Tag push (`v*`)**: Builds, optionally signs, uploads to Google Play Console (if configured), and attaches the APK to a GitHub Release created for the tag (requires branch/tag commit on `main`).
- **Manual dispatch**: Select `main` as the branch, choose a Play Console track (internal, alpha, beta, production), optionally override the release name, and obtain the artifact from the run (no release created automatically for manual runs).
- Signing is automatic when all four signing secrets are present; otherwise an unsigned artifact is uploaded.
- Google Play Console upload is automatic when `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` is configured. For manual dispatch, you can select the release track (defaults to `internal`).
- The workflow aborts if the triggering ref/tag does not point to the latest commit on `main`, guaranteeing releases originate from protected history.

## Release Checklist

1. Merge all changes into `main` via reviewed pull requests.
2. Tag the `main` commit (`git tag vX.Y.Z && git push origin vX.Y.Z`) **or** run the Release workflow manually from `main` (select the Play Console track if using manual dispatch).
3. Confirm the workflow succeeds:
- The signed APK is available as a GitHub Actions artifact
- If `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` is configured, the APK is automatically uploaded to the selected Play Console track
- For tag pushes, a GitHub Release is created with the APK attached
4. Monitor the Play Console to confirm the release is processed and available to testers/users.

Empty file modified gradlew
100644 → 100755
Empty file.
Loading