0

I am trying to build an Android Studio project inside docker using gradle. Gradle goes through a long process of downloading my dependencies. I would like to cache this step inside docker, so that it is not repeated every time I build.

I am unhappy with the underlying snippet from my Dockerfile (full file below). It requires that I copy my resource directory. Everytime I change a string or a resource, all of my dependencies will be downloaded again! (Because docker cache will be invalidated)

COPY my-android-project/app/src/main/res app/src/main/res

This line will trigger alot of redundant dependency downloads by docker. How can I remove it from my dockerfile? I would appreciate a Dockerfile example with even a trivial "empty activity" Android Studio project...

# https://medium.com/@AndreSand/building-android-with-docker-8dbf717f54d4

FROM gradle:5.4.1-jdk8
USER root
ENV SDK_URL="https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip" \
    ANDROID_HOME="/usr/local/android-sdk" \
    ANDROID_VERSION=29 \
    ANDROID_BUILD_TOOLS_VERSION=29.0.2

# Download Android SDK
RUN mkdir "$ANDROID_HOME" .android \
    && cd "$ANDROID_HOME" \
    && curl -o sdk.zip $SDK_URL \
    && unzip sdk.zip \
    && rm sdk.zip \
    && mkdir "$ANDROID_HOME/licenses" || true \
    && echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_HOME/licenses/android-sdk-license"
#    && yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses
# Install Android Build Tool and Libraries
RUN $ANDROID_HOME/tools/bin/sdkmanager --update
RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \
    "platforms;android-${ANDROID_VERSION}" \
    "platform-tools"
# Install Build Essentials
RUN apt-get update && apt-get install build-essential -y && apt-get install file -y && apt-get install apt-utils -y

RUN mkdir my-android-project
WORKDIR my-android-project

# Fix gradle cache directory to work outside of profile (required for docker)
RUN mkdir -p /opt/gradle/.gradle
ENV GRADLE_USER_HOME=/opt/gradle/.gradle

# Cache dependencies so that they are not downloaded every time the code changes
COPY my-android-project/gradle* ./
COPY my-android-project/build.gradle .
COPY my-android-project/settings.gradle .
COPY my-android-project/gradle/ ./gradle/
COPY my-android-project/gradle/ ./gradle/
COPY my-android-project/app/build.gradle app/build.gradle
COPY my-android-project/app/src/main/AndroidManifest.xml app/src/main/AndroidManifest.xml

#
####
# I am unhappy with this, as the dependencies will be downloaded again
# If I change any strings, icons, or IDs inside this folder
# How do I prevent having to copy this folder?
###
#
COPY my-android-project/app/src/main/res app/src/main/res

RUN find
# Running gradle instead of gradlew to prevent redundant update of gradle version
# to save time: if you need another version of gradle, please install it in the docker image prioir to running this line
RUN gradle --no-daemon build

# Build the actual files
COPY my-android-project .
# Running gradle instead of gradlew to prevent redundant update of gradle version
# to save time: if you need another version of gradle, please install it in the docker image prioir to running this line
# --offline to make sure no further dependenies are being downloaded
RUN gradle --no-daemon --offline build
eshalev
  • 3,033
  • 4
  • 34
  • 48
  • Why wouldn't the same work for gradle? Call `./gradlew build` (downloads the dep's, same as `mvn package` does) `rm -r build` (the `build` folder is the equiv to the `package` folder iirc) – Blundell Nov 25 '19 at 19:30
  • It doesn't, I tried it. It downloads lot's of things twice. I think it has something to do with the daemon. I tried the ```--no-damon``` flag to no avail. I added my gradle file to the original post – eshalev Nov 25 '19 at 20:49
  • in my limited understanding, the gradle cache is in a folder called `.gradle` usually under your home directory `~`, where as your script is looking for caches inside of a specific project directory? – Blundell Nov 25 '19 at 22:01
  • @Blundell I updated the dockerfile with the correct environment variables. That solves that specific caching problem – eshalev Nov 26 '19 at 00:11
  • It would probably be better if you volume mounted your project into the container rather than copied it. That way, the build output would come out to your host machine – OneCricketeer Dec 01 '19 at 07:01
  • @cricket_007 I do volume mount it. I do it from the command line when I "docker run". I find that mounting volumes directly from within the Dockerfile is a bit dirty, since I run my dockerfiles on several different systems: ex: My laptop, My *remote* build server, etc... Each has their own separate volume configuration. If you first build your file inside the Dockerfile, and then copy the resulting binary to a volume with a "docker run" command, every build is cached properly, and you can configure different volumes for different build systems. – eshalev Dec 01 '19 at 07:06
  • You could wrap your docker run in a bash or Makefile that could use logic to mount the proper working directory – OneCricketeer Dec 01 '19 at 07:13
  • @cricket_007 Docker has an inbuilt cache system, that I successfully use with other build systems: Maven/Pip/NPM/Golang,etc... I am not looking for shortcuts, but rather how to replicate this behavior with gradle (there is nothing special about Android ). If as you suggest, I mount my external cache folder as a docker volume, Then the builds become dependent on the external operating system. example: Imagine that 2 devs are working on different branches and triggering different builds on the same build server. Their jobs will start "fighting" over the singleton volume and causing problems. – eshalev Dec 01 '19 at 13:13
  • Okay, well, there's no way to prevent downloading your dependencies in the Docker build because you must copy the project before you can run the Gradle command. Even Maven would have the same behavior as it also relies on dependencies in the .m2 folder – OneCricketeer Dec 01 '19 at 15:01
  • @cricket_007 You are wrong about maven. There is no need to copy .m2 folder. If you copy your pom file, then .m2 gets cached inside your build container. This is because docker will cache the result of any commands that have not changed *anywhere in the container's file system*. So .m2 will get cached as long as you don't change your pom file. Meaning: If you just change your .java files, nothing will get downloaded again. Please see: https://stackoverflow.com/questions/57001765/caching-maven-with-docker-and-kotlin/57003662#57003662 . No need to copy .m2 – eshalev Dec 01 '19 at 23:19
  • That's what I'm saying. Gradle and Maven do the same thing in that regard. I didn't suggest that you copy the folder, only that you could link a volume outside of the CI system (where it's local on your machine and only you would be working on the project). Can't you just copy the Gradle files and run some Gradle command to download them? Then copy your code. Like https://github.com/mdietrichstein/gradle-offline-dependencies-plugin/blob/master/README.md – OneCricketeer Dec 01 '19 at 23:43
  • Using [this multistage build](https://stackoverflow.com/a/59022743/6354805) helped me (i.e., the first stage should only copy over the Gradle files such as `build.gradle` so that unrelated code changes don't trigger a fresh rebuild). – Neel Kamath Dec 02 '19 at 06:05

0 Answers0