-
Notifications
You must be signed in to change notification settings - Fork 3
Add GitHub Actions CI/CD for automated APK building and release attachment #2
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # CI/CD Workflows | ||
|
|
||
| This repository includes GitHub Actions workflows for automated building and releasing of the QR Code Scanner app. | ||
|
|
||
| ## Workflows | ||
|
|
||
| ### 1. Build Test (`build.yml`) | ||
| - **Triggers**: Pull requests and pushes to main/master branches | ||
| - **Purpose**: Validates that the code builds successfully and runs tests | ||
| - **Outputs**: Debug APK as an artifact | ||
|
|
||
| ### 2. Build and Release APK (`release.yml`) | ||
| - **Triggers**: | ||
| - When a release is published on GitHub | ||
| - When a tag starting with 'v' is pushed (e.g., `v1.0.0`) | ||
| - **Purpose**: Builds the release APK and attaches it to the GitHub release | ||
| - **Outputs**: | ||
| - Release APK attached to the GitHub release | ||
| - Release APK as an artifact for tag pushes | ||
|
|
||
| ## Creating a Release | ||
|
|
||
| ### Option 1: Using the Release Script (Recommended) | ||
|
|
||
| The repository includes a helper script to streamline the release process: | ||
|
|
||
| ```bash | ||
| ./scripts/release.sh v1.0.0-alpha-06 | ||
| ``` | ||
|
|
||
| This script will: | ||
| - Validate the version format | ||
| - Check if you're on the main/master branch | ||
| - Optionally update the version in `app/build.gradle.kts` | ||
| - Create and push the git tag | ||
| - Provide links to track the build progress | ||
|
|
||
| ### Option 2: Manual Process | ||
|
|
||
| To create a new release manually: | ||
|
|
||
| 1. **Create and push a tag**: | ||
| ```bash | ||
| git tag v1.0.0-alpha-06 | ||
| git push origin v1.0.0-alpha-06 | ||
| ``` | ||
|
|
||
| 2. **Create a GitHub release**: | ||
| - Go to the repository's Releases page | ||
| - Click "Create a new release" | ||
| - Select the tag you just created | ||
| - Fill in the release title and description | ||
| - Click "Publish release" | ||
|
|
||
| 3. **Automatic APK build**: | ||
| - The workflow will automatically trigger | ||
| - Build the release APK | ||
| - Attach the APK to the release | ||
|
|
||
| ## APK Naming | ||
|
|
||
| The generated APK will be named: `qr-code-scanner-{version}.apk` | ||
|
|
||
| Where `{version}` is extracted from either: | ||
| - The git tag (if triggered by tag push) | ||
| - The `versionName` from `app/build.gradle.kts` | ||
|
|
||
| ## Build Environment | ||
|
|
||
| The workflows use: | ||
| - Ubuntu latest | ||
| - Java 17 (Temurin distribution) | ||
| - Android SDK (via android-actions/setup-android) | ||
| - Gradle caching for faster builds |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| name: Build Test | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [ main, master ] | ||
| push: | ||
| branches: [ main, master ] | ||
|
|
||
| env: | ||
| GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4096m -Dorg.gradle.daemon=false" | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up JDK 17 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: '17' | ||
| distribution: 'temurin' | ||
|
|
||
| - name: Setup Android SDK | ||
| uses: android-actions/setup-android@v3 | ||
|
|
||
| - name: Cache Gradle dependencies | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| ~/.gradle/caches | ||
| ~/.gradle/wrapper | ||
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-gradle- | ||
|
|
||
| - name: Make gradlew executable | ||
| run: chmod +x ./gradlew | ||
|
|
||
| - name: Run tests | ||
| run: ./gradlew test | ||
|
|
||
| - name: Build debug APK | ||
| run: ./gradlew assembleDebug | ||
|
|
||
| - name: Upload debug APK | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: debug-apk | ||
| path: app/build/outputs/apk/debug/*.apk |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| name: Build and Release APK | ||
|
|
||
| on: | ||
| release: | ||
| types: [published] | ||
| push: | ||
| tags: | ||
| - 'v*' | ||
|
|
||
| env: | ||
| GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4096m -Dorg.gradle.daemon=false" | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up JDK 17 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: '17' | ||
| distribution: 'temurin' | ||
|
|
||
| - name: Setup Android SDK | ||
| uses: android-actions/setup-android@v3 | ||
|
|
||
| - name: Cache Gradle dependencies | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| ~/.gradle/caches | ||
| ~/.gradle/wrapper | ||
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-gradle- | ||
|
|
||
| - name: Make gradlew executable | ||
| run: chmod +x ./gradlew | ||
|
|
||
| - name: Clean and Build Release APK | ||
| run: | | ||
| ./gradlew clean | ||
| ./gradlew assembleRelease | ||
|
|
||
| - name: Get APK info | ||
| id: apk-info | ||
| run: | | ||
| APK_PATH=$(find app/build/outputs/apk/release -name "*.apk" | head -1) | ||
| if [ -z "$APK_PATH" ]; then | ||
| echo "APK not found!" | ||
| exit 1 | ||
| fi | ||
| echo "apk_path=$APK_PATH" >> $GITHUB_OUTPUT | ||
|
|
||
| # Extract version info from the APK path or use git tag | ||
| if [[ "${{ github.ref }}" == refs/tags/* ]]; then | ||
| VERSION=${GITHUB_REF#refs/tags/} | ||
| else | ||
| VERSION=$(grep 'versionName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/') | ||
| fi | ||
|
|
||
| APK_NAME="qr-code-scanner-${VERSION}.apk" | ||
| echo "apk_name=$APK_NAME" >> $GITHUB_OUTPUT | ||
| echo "version=$VERSION" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Rename APK | ||
| run: | | ||
| mv "${{ steps.apk-info.outputs.apk_path }}" "app/build/outputs/apk/release/${{ steps.apk-info.outputs.apk_name }}" | ||
|
|
||
| - name: Upload APK to Release | ||
| if: github.event_name == 'release' | ||
| uses: softprops/action-gh-release@v1 | ||
| with: | ||
| files: app/build/outputs/apk/release/${{ steps.apk-info.outputs.apk_name }} | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Upload APK as Artifact (for tag pushes) | ||
| if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: qr-code-scanner-${{ steps.apk-info.outputs.version }} | ||
| path: app/build/outputs/apk/release/${{ steps.apk-info.outputs.apk_name }} | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,80 @@ | ||||||||||||||||||||||||||||||||||
| #!/bin/bash | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Release script for QR Code Scanner | ||||||||||||||||||||||||||||||||||
| # Usage: ./scripts/release.sh <version> | ||||||||||||||||||||||||||||||||||
| # Example: ./scripts/release.sh v1.0.0-alpha-06 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| set -e | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if [ $# -eq 0 ]; then | ||||||||||||||||||||||||||||||||||
| echo "Usage: $0 <version>" | ||||||||||||||||||||||||||||||||||
| echo "Example: $0 v1.0.0-alpha-06" | ||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| VERSION=$1 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Validate version format | ||||||||||||||||||||||||||||||||||
| if [[ ! $VERSION =~ ^v[0-9]+\.[0-9]+\.[0-9]+.*$ ]]; then | ||||||||||||||||||||||||||||||||||
| echo "Error: Version must start with 'v' and follow semantic versioning (e.g., v1.0.0-alpha-06)" | ||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| echo "Creating release for version: $VERSION" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Check if tag already exists | ||||||||||||||||||||||||||||||||||
| if git rev-parse "$VERSION" >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||
| echo "Error: Tag $VERSION already exists" | ||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Get current branch | ||||||||||||||||||||||||||||||||||
| CURRENT_BRANCH=$(git branch --show-current) | ||||||||||||||||||||||||||||||||||
| echo "Current branch: $CURRENT_BRANCH" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Ensure we're on main/master branch | ||||||||||||||||||||||||||||||||||
| if [[ "$CURRENT_BRANCH" != "main" && "$CURRENT_BRANCH" != "master" ]]; then | ||||||||||||||||||||||||||||||||||
| echo "Warning: You're not on main/master branch. Continue? (y/N)" | ||||||||||||||||||||||||||||||||||
| read -n 1 -r | ||||||||||||||||||||||||||||||||||
| echo | ||||||||||||||||||||||||||||||||||
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | ||||||||||||||||||||||||||||||||||
| echo "Aborted" | ||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Ensure working directory is clean | ||||||||||||||||||||||||||||||||||
| if ! git diff-index --quiet HEAD --; then | ||||||||||||||||||||||||||||||||||
| echo "Error: Working directory is not clean. Please commit your changes first." | ||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Update version in build.gradle.kts if needed | ||||||||||||||||||||||||||||||||||
| echo "Current version in build.gradle.kts:" | ||||||||||||||||||||||||||||||||||
| grep 'versionName' app/build.gradle.kts || echo "Version not found" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| echo "Do you want to update the version in build.gradle.kts? (y/N)" | ||||||||||||||||||||||||||||||||||
| read -n 1 -r | ||||||||||||||||||||||||||||||||||
| echo | ||||||||||||||||||||||||||||||||||
| if [[ $REPLY =~ ^[Yy]$ ]]; then | ||||||||||||||||||||||||||||||||||
| # Extract version without 'v' prefix | ||||||||||||||||||||||||||||||||||
| VERSION_NUMBER=${VERSION#v} | ||||||||||||||||||||||||||||||||||
| sed -i.bak "s/versionName = \".*\"/versionName = \"$VERSION_NUMBER\"/" app/build.gradle.kts | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
| sed -i.bak "s/versionName = \".*\"/versionName = \"$VERSION_NUMBER\"/" app/build.gradle.kts | |
| sed -i "" "s/versionName = \".*\"/versionName = \"$VERSION_NUMBER\"/" app/build.gradle.kts |
Copilot
AI
Sep 2, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The complex sed expression for extracting the repository path is fragile and may not handle all URL formats correctly. Consider using a more robust approach like gh repo view --json url if GitHub CLI is available, or splitting this into multiple simpler operations.
| echo "🔗 https://github.com/$(git config --get remote.origin.url | sed 's/.*github.com[:/]\(.*\)\.git/\1/')/releases" | |
| # Extract repository path (owner/repo) robustly | |
| if command -v gh >/dev/null 2>&1; then | |
| REPO_PATH=$(gh repo view --json nameWithOwner -q .nameWithOwner) | |
| else | |
| REMOTE_URL=$(git config --get remote.origin.url) | |
| # Remove .git suffix if present | |
| REMOTE_URL=${REMOTE_URL%.git} | |
| # Extract owner/repo from SSH or HTTPS URL | |
| if [[ "$REMOTE_URL" =~ github\.com[:/](.+/.+) ]]; then | |
| REPO_PATH="${BASH_REMATCH[1]}" | |
| else | |
| REPO_PATH="unknown/unknown" | |
| fi | |
| fi | |
| echo "🔗 https://github.com/$REPO_PATH/releases" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable
GITHUB_REFshould be referenced as${{ github.ref }}instead of$GITHUB_REFfor consistency with the condition on line 59.