How and why to build an Android application in a docker container

Good afternoon, dear readers.

I’m Vladimir, my name is devops. They say that devops is a disease and I will prove it to you today.

The answer to the question “why?” you won’t find it here, it’s clickbait, I don’t know myself. Everything that happens is motivated by the motto “bekoz ai ken”.

Joking aside, you can run the build process, create isolated test cases, run through autotests, SonarQube and other SASTs

So. Given. One crazy devops, one unfortunate android developer building apk locally. Unfortunately, the situation is widespread.

Part 1. Dockerfile. How to assemble

How do we decide. We write Dockerfile for business and Jenkinsfile for show-off, today we are only talking about Dockerfile.

For a successful process, you need lubricate pull out from the developer the environment in which all this joy is collected. we are interested in gradle, jdk, android sdk versions

It is most convenient to assemble in a ready-made official way https://hub.docker.com/_/gradle

When choosing an image, pay attention to the version of gradle and java, gradle has debian roots, so apt-get java in case there is no version ready. Create a Dockerfile at the root of the turnip and start filling it out:

Immediately check if the versions of gradle and java converge

FROM gradle:6.1.1-jdk11
RUN java -version
RUN gradle -v

because Dockerfile is at the root of the repository, copy the contents to the container

RUN mkdir /opt/project
COPY .  /opt/project
WORKDIR /opt/project

Two of the three are – gradle and jdk are installed, install andriodsdk.

There is one nuance. For andriodsdk, you need to pull out the archiver with it. You can smoke the latest version, or you can use pens watch here.

As a devops – I wouldn’t like to go back to updating the environment by hand, however, there may be unpleasant surprises with new versions of andriodsdk. And there is not always a need to use latest. So just put the name of the archive in a variable

ARG ANDROID_SDK_VERSION=8092744
ENV ANDROID_SDK_ROOT /opt/android-sdk

download. put. we clean the trash.

RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools && \
    wget https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_VERSION}_latest.zip && \
    unzip *tools*linux*.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools && \
    mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/tools && \
    rm *tools*linux*.zip

That feeling when you didn’t have time to start, but almost everything is ready.

Specify PATH for androidsdk, where gradle will go

ENV PATH ${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin:${ANDROID_SDK_ROOT}/platform-tools:${ANDROID_SDK_ROOT}/emulator

Pay attention to the second line, where the androidsdk packages are listed separately
In earlier versions, it was enough to install androidsdk, but now you need to install packages \ platforms \ samovars \ harlots separately. Nobody bothers to put all the stuffing at once, butoo …

But the feeling of beauty says that you need only what you need. How to determine which sobsna packages are needed? about it below about the nuances

RUN yes | sdkmanager --licenses && \
	sdkmanager "platforms;android-29" "platforms;android-30" "platforms;android-31" "build-tools;29.0.2" && \
	cd /opt/project

Last but not least, let’s do some magic.

RUN gradle build 

Entirely great Dockerfile

Dockerfile
FROM gradle:6.1.1-jdk11

RUN mkdir /opt/project
COPY .  /opt/project
WORKDIR /opt/project

ARG ANDROID_SDK_VERSION=8092744
ENV ANDROID_SDK_ROOT /opt/android-sdk
RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools && \
    wget https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_VERSION}_latest.zip && \
    unzip *tools*linux*.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools && \
    mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/tools && \
    rm *tools*linux*.zip
    
ENV PATH ${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin:${ANDROID_SDK_ROOT}/platform-tools:${ANDROID_SDK_ROOT}/emulator
RUN yes | sdkmanager --licenses && \
		sdkmanager "platforms;android-29" "platforms;android-30" "platforms;android-31" "build-tools;29.0.2" && \
		cd /opt/project
RUN gradle build 

Now about the nuances.

  • 1. How to determine the sdkmanager packages required for installation?

    You can see in the studio \ ask the developer. most likely there will be a lot of unnecessary, so the variant is rooted in a ruthless ru-development.

    sequentially run gradle build without sdkmanager-a packages, look at what it swears at – set (i.e. add packages to line 17), repeat until complete self-satisfaction.

  • 2. How to pull out the final apk?

    You can add a user and a volume to the Dockerfile, the most obvious.

    But in my opinion there is an elegant solution in the form of a Jenkinsfile, because it is a little more than completely composed of sh, you can do the same, but with your hands:

    The meaning is that
    – create an image (!) from a Dockerfile
    docker build .
    – the container finished working and closed
    status = exited
    – we start the container with a command that can continue its work.
    docker start build_apk sleep infinity
    – we pull out all this joy with a docker and remove the tails
    docker cp build_apk:/path/to/apk/in/docker/container/*.apk /path/to/home/dir && /
    docker stop build_apk && docker rm build_apk

docker build . -t build_apk
docker run -d build_apk sleep infinity
docker cp build_apk:/path/to/apk/in/docker/container/*.apk /path/to/home/dir
docker stop build_apk && docker rm build_apk

Someone will say “fu, crutch” and will be right. But this decision does not claim to be the ultimate truth. This is a simple and easy-to-learn option that solves a tactical problem.

  • 3. What if we are behind a proxy?

    for wget add key “–no-check-certificate”
    for sdk manager add keys “–proxy=http –proxy_host= –proxy_port=

RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools && \
    wget https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_VERSION}_latest.zip --no-check-certificate && \
    unzip *tools*linux*.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools && \
    mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/tools && \
    rm *tools*linux*.zip
    
RUN yes | sdkmanager --licenses --proxy=http --proxy_host=<ip> --proxy_port=<port>&& \
		sdkmanager --proxy=http --proxy_host=<ip> --proxy_port=<port> && \
		cd /opt/project

Don’t forget to set the proxy in gradle.properies

systemProp.http.proxyHost=<ip>
systemProp.http.proxyPort=<port>

systemProp.https.proxyHost=<ip>
systemProp.https.proxyPort=<port>

#сходу бонус
#чтобы сборка не обжирались и не падала с ошибкой "плак плак, хатю память"
#выставим ограничение
org.gradle.jvmargs=-Xmx2048m -Dkotlin.daemon.jvm.options\="-Xmx2048M" -XX:MaxPermSize=4096m

If we are pulling from a corporate repo, then do not forget to specify it in build.gradle in two places !!

    repositories {
        mavenCentral { url "https://corp.repo.example.ru/repository/maven-central/" }
        jcenter { url "https://corp.repo.example.ru/repository/jcenter/" }
        google { url "https://corp.repo.example.ru/repository/dl.google.com-android/" }
    }
    
allprojects {
        repositories {
        mavenCentral { url "https://corp.repo.example.ru/repository/maven-central/" }
        jcenter { url "https://corp.repo.example.ru/repository/jcenter/" }
        google { url "https://corp.repo.example.ru/repository/dl.google.com-android/" }
    }
}

PS Intrigue

We are here about devops, and therefore we want smoothies and women automation. and therefore we want the assembly to start after the commit to the turnip and the final apk to fall into some kind of general access. How to do it beautifully – in the next article) about the simplest nginx and Jenkins There will be about dumping version into version. In short, intrigue.

Similar Posts

Leave a Reply