-
Notifications
You must be signed in to change notification settings - Fork 0
Add branch protections and CI/CD workflows #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
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 1cc962b
Add Google Play Console upload and fix tag verification
iAmVishal16 a7b9767
Fix Codex review issues: inputs context, unsigned APK cleanup, check …
iAmVishal16 5c8d7d1
Remove configure_branch_protection.sh script and update docs
iAmVishal16 14c9fff
Fix Android SDK version: use API 35 instead of 36
iAmVishal16 f5ea223
Install Android API 36 manually via sdkmanager
iAmVishal16 cc32b80
Fix Android SDK setup: remove cmdline-tools from packages list
iAmVishal16 e78ffa4
Fix Android SDK package installation: install packages separately
iAmVishal16 79965d9
Enable automatic Play Console upload on main branch pushes
iAmVishal16 fb7205e
Fix workflow validation: remove secret checks from if conditions
iAmVishal16 262de0e
Temporarily trigger release workflow on ci-cd-setup branch
iAmVishal16 52f5333
Switch release workflow to upload Android App Bundle
iAmVishal16 98c4a59
Replace APK signing with AAB signing using jarsigner
iAmVishal16 6a7d4e7
Add auto-increment version code from Google Play Console
iAmVishal16 87f718d
Improve version code auto-increment with better API querying
iAmVishal16 0dc59b4
Fix Python syntax error: use sys.exit() instead of exit
iAmVishal16 7ed5013
Remove temporary ci-cd-setup branch from workflow triggers
iAmVishal16 317ebc9
Extract Python script to separate file
iAmVishal16 72c8def
Fail workflow if signing secrets are missing
iAmVishal16 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| 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 | ||
|
|
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
| 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" | ||
| 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 | ||
|
|
||
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
| 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.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.