diff --git a/.github/workflows/android-docker.yml b/.github/workflows/android-docker.yml new file mode 100644 index 0000000..11d0988 --- /dev/null +++ b/.github/workflows/android-docker.yml @@ -0,0 +1,33 @@ +name: Android CI with Docker + +on: + push: + branches: [ "main", "feat/*", "feature/*" ] + pull_request: + branches: [ "main" ] + +jobs: + build-in-docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build Docker Image + run: docker build -t android-ci . + + - name: Run Gradle Build in Docker + # Mount current directory to /app + # Use the image built in previous step + # Run './gradlew build' + run: | + docker run --rm \ + -v ${{ github.workspace }}:/app \ + -w /app \ + android-ci \ + bash -c "chmod +x gradlew && ./gradlew build" + + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + name: app-debug + path: app/build/outputs/apk/debug/app-debug.apk diff --git a/.gradle/8.2/checksums/checksums.lock b/.gradle/8.2/checksums/checksums.lock new file mode 100644 index 0000000..5e2f673 Binary files /dev/null and b/.gradle/8.2/checksums/checksums.lock differ diff --git a/.gradle/8.2/checksums/md5-checksums.bin b/.gradle/8.2/checksums/md5-checksums.bin new file mode 100644 index 0000000..8ffae5e Binary files /dev/null and b/.gradle/8.2/checksums/md5-checksums.bin differ diff --git a/.gradle/8.2/checksums/sha1-checksums.bin b/.gradle/8.2/checksums/sha1-checksums.bin new file mode 100644 index 0000000..68e79b6 Binary files /dev/null and b/.gradle/8.2/checksums/sha1-checksums.bin differ diff --git a/.gradle/8.2/dependencies-accessors/dependencies-accessors.lock b/.gradle/8.2/dependencies-accessors/dependencies-accessors.lock new file mode 100644 index 0000000..8dc62e6 Binary files /dev/null and b/.gradle/8.2/dependencies-accessors/dependencies-accessors.lock differ diff --git a/.gradle/8.2/dependencies-accessors/gc.properties b/.gradle/8.2/dependencies-accessors/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/8.2/fileChanges/last-build.bin b/.gradle/8.2/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/.gradle/8.2/fileChanges/last-build.bin differ diff --git a/.gradle/8.2/fileHashes/fileHashes.lock b/.gradle/8.2/fileHashes/fileHashes.lock new file mode 100644 index 0000000..8bfe01a Binary files /dev/null and b/.gradle/8.2/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.2/gc.properties b/.gradle/8.2/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..6aaa3fa Binary files /dev/null and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..46e42bb --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Wed Jan 21 10:42:29 CST 2026 +gradle.version=8.2 diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7d8fc10 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +FROM eclipse-temurin:17-jdk + +# Set environment variables +ENV ANDROID_HOME /opt/android-sdk +ENV PATH ${PATH}:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools + +# Install necessary system packages +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + unzip \ + git \ + && rm -rf /var/lib/apt/lists/* + +# Download and install Android Command Line Tools +# Version: cmdline-tools;latest (checked from developer.android.com, typically part of commandlinetools-linux-*.zip) +# Using specific version suitable for stability. +# As of early 2024, cmdline-tools 11.0 is common, checking official link pattern. +# https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip is a recent one. +# We will use a reasonably recent valid URL. +ARG CMDLINE_TOOLS_URL=https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip + +RUN mkdir -p ${ANDROID_HOME}/cmdline-tools \ + && curl -o cmdline-tools.zip ${CMDLINE_TOOLS_URL} \ + && unzip cmdline-tools.zip -d ${ANDROID_HOME}/cmdline-tools \ + && mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest \ + && rm cmdline-tools.zip + +# Accept licenses +RUN yes | sdkmanager --licenses + +# Install Gradle 8.2 +RUN curl -L https://services.gradle.org/distributions/gradle-8.2-bin.zip -o gradle.zip \ + && unzip gradle.zip -d /opt \ + && rm gradle.zip \ + && mv /opt/gradle-8.2 /opt/gradle +ENV PATH ${PATH}:/opt/gradle/bin + +# Install SDK packages +# Based on build.gradle: compileSdk 34, minSdk 24 +RUN sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0" + +WORKDIR /app diff --git a/README.md b/README.md index b4ab13c..013b6ec 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ -# android-tutorial \ No newline at end of file +# Android Build with Docker and GitHub Actions + +This repository contains a sample Android project and a tutorial on how to set up a CI/CD pipeline using Docker and GitHub Actions. + +## Contents + +- [App Source Code](./app/) +- [Dockerfile](./Dockerfile) +- [GitHub Actions Workflow](./.github/workflows/android-docker.yml) +- [**TUTORIAL**: Step-by-Step Guide](./TUTORIAL.md) \ No newline at end of file diff --git a/TUTORIAL.md b/TUTORIAL.md new file mode 100644 index 0000000..1ba42b7 --- /dev/null +++ b/TUTORIAL.md @@ -0,0 +1,79 @@ +# Tutorial: Android GitHub Actions in Docker + +This tutorial guides you through setting up a CI/CD pipeline for your Android application using GitHub Actions and Docker. This ensures a consistent build environment and automates your build process. + +## Prerequisites + +- An Android project (already set up in this repository). +- Docker installed on your development machine (for local testing). +- A GitHub repository. + +## 1. The Docker Build Environment + +We use a `Dockerfile` to define an immutable build environment. This container includes: +- **OpenJDK 17**: The Java version required by the Gradle build. +- **Android Command Line Tools**: Necessary for managing the Android SDK. +- **Android SDK Components**: Specifically `platforms;android-34` and `build-tools;34.0.0` as defined in `app/build.gradle`. + +### Key Dockerfile Sections + +```dockerfile +# Base image +FROM openjdk:17-jdk-slim + +# Install system dependencies +RUN apt-get update && apt-get install -y curl unzip git ... + +# Download Command Line Tools +RUN curl -o cmdline-tools.zip ... + +# Install SDK Packages +RUN sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0" +``` + +## 2. GitHub Actions Workflow + +The workflow is defined in `.github/workflows/android-docker.yml`. It triggers on pushes and pull requests to the `main` branch. + +### Workflow Breakdown + +1. **Checkout Code**: Retrieves your project source code. +2. **Build Docker Image**: Builds the container defined in your `Dockerfile`. +3. **Run Build**: Mounts the project source code into the container and runs `./gradlew build`. + +```yaml + - name: Run Gradle Build in Docker + run: | + docker run --rm \ + -v ${{ github.workspace }}:/app \ + -w /app \ + android-ci \ + ./gradlew build +``` + +## 3. Running Locally + +You can test the build environment locally using Docker before pushing to GitHub. + +### Step 1: Build the Image + +```bash +docker build -t android-ci . +``` + +### Step 2: Run the Build + +```bash +docker run --rm -v $(pwd):/app -w /app android-ci ./gradlew assembleDebug +``` + +This command: +- `--rm`: Removes the container after it exits. +- `-v $(pwd):/app`: Maps your current directory to `/app` inside the container. +- `-w /app`: Sets the working directory to `/app`. +- `android-ci`: Uses the image you just built. +- `./gradlew assembleDebug`: Runs the Gradle task to build the debug APK. + +## Conclusion + +By containerizing your build environment, you eliminate "it works on my machine" issues and simplify your CI configuration. This setup forms the foundation for more advanced pipelines, including running tests and deploying to the Play Store. diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..7e98e40 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.example.myapplication' + compileSdk 34 + + defaultConfig { + applicationId "com.example.myapplication" + minSdk 24 + targetSdk 34 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.11.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..51e92fd --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/example/myapplication/MainActivity.java b/app/src/main/java/com/example/myapplication/MainActivity.java new file mode 100644 index 0000000..71f623e --- /dev/null +++ b/app/src/main/java/com/example/myapplication/MainActivity.java @@ -0,0 +1,12 @@ +package com.example.myapplication; + +import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; + +public class MainActivity extends AppCompatActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // No layout required for this basic build test + } +} diff --git a/app/src/main/res/mipmap/ic_launcher.xml b/app/src/main/res/mipmap/ic_launcher.xml new file mode 100644 index 0000000..9731226 --- /dev/null +++ b/app/src/main/res/mipmap/ic_launcher.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/mipmap/ic_launcher_round.xml b/app/src/main/res/mipmap/ic_launcher_round.xml new file mode 100644 index 0000000..a6d7135 --- /dev/null +++ b/app/src/main/res/mipmap/ic_launcher_round.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..efd3073 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + My Application + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..f121c20 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..63326da --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..da8aa42 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..d766a8c --- /dev/null +++ b/build.gradle @@ -0,0 +1,18 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:8.2.0" + } +} + +plugins { + id 'com.android.application' version '8.2.0' apply false +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..abc6b64 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +android.enableJetifier=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..15de902 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..7b61bff --- /dev/null +++ b/gradlew @@ -0,0 +1,12 @@ +#!/bin/sh + +# Simple wrapper that assumes gradle is available in the environment for this tutorial +# In a real project, this would be the actual Gradle Wrapper script generated by `gradle wrapper` + +if command -v gradle >/dev/null 2>&1; then + exec gradle "$@" +else + echo "Error: gradle is not installed/found in PATH." + echo "For this tutorial, please ensure gradle is installed or run within the provided Docker container." + exit 1 +fi diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..76a7c06 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "My Application" +include ':app'