Как и зачем собирать Android приложение в docker контейнере

55911e58c9e60dd56fdeccf43d80c5a4

Добрый день, уважаемые читатели.

Я — Владимир, меня зовут девопс. Говорят, что девопс — это болезнь и я это вам сегодня докажу.

Ответа на вопрос «зачем?» вы тут не найдете, это кликбейт, я и сам не знаю. Все происходящее мотивируется девизом «бекоз ай кен».

Если без шуток, то можно прогонять процесс сборки, создавать изолированные тест-кейсы, прогонять через автотесты, SonarQube и прочие SAST-ы

Итак. Дано. Один безумный девопс, один несчастный андроид-разработчик, собирающий апк локально. Ситуация, увы, распространенная.

Часть 1. Dockerfile. Как собрать

Как решаем. Пишем Dockerfile для дела и Jenkinsfile для понтов, сегодня речь только про Dockerfile.

Для успешного процесса нужно смазать вытащить из разработчика окружение, в котором вся эта радость собирается. нас интересует версии gradle, jdk, android sdk

Удобнее всего собирать готовым официальным образом https://hub.docker.com/_/gradle

При выборе образа обратите внимание на версию gradle и java, gradle имеет debian корни, так что apt-get java на случай, если версии нет готовой. Создаем в корне репы Dockerfile и начинаем его заполнять:

Сразу проверяем, сходятся ли версии gradle и java

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

т…к. Dockerfile лежит в корне репозитория, копируем содержимое в контейнер

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

Два из трех есть — gradle и jdk установлены, ставим andriodsdk.

Есть один нюанс. Для andriodsdk нужно вытащить архивчик с оным. Можно скурлить последнюю версию, а можно ручками смотреть тут.

Как девопс — я бы не хотел возвращаться к обновлению окружения руками, однако могут быть неприятные сюрпризы с новыми версиями andriodsdk. Да и не всегда есть нужда в использовании latest. Так что просто укажем имя архива в переменной

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

То чувство, когда не успевал начать, а уже почти все готово.

Указываем PATH для androidsdk, куда полезет gradle

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

Обратите внимание на вторую строчку, где отдельно указаны пакеты androidsdk
В ранних версиях было достаточно установить androidsdk, сейчас же нужно отдельно ставить пакеты\платформы\самовары\блудниц. Никто не мешает поставить сразу весь фарш, нооо…

Но чувство прекрасного говорит, что надо только то, что надо. Как определить какие пакеты собсна нужны? об этом ниже про нюансы

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 

Целиком прекрасный 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 

Теперь про нюансы.

  • 1. Как определить пакеты sdkmanager, необходимые к установке?

    Можно посмотреть в студии\спросить разраба. скорее всего будет уйма ненужного, так что вариант корнями ведущий в беспощадный ру-девелоп.

    последовательно запускаем gradle build без пакетов sdkmanager-a, смотрим, на что ругается — ставим (т.е. добавляем пакеты в строчку 17), повторять до полного самоудовлетворения.

  • 2. Как вытащить итоговый apk?

    Можно присобачить юзера и volume в Dockerfile, самое очевидное.

    Но на мой взгляд есть изящное решение в виде Jenkinsfile, т.к. он чуть более, чем полностью состоит из sh, можно делать то же самое, но руками:

    Смысл такой, что
    — создаем образ (!) из Dockerfile
    docker build.
    — контейнер закончил работать и закрылся
    статус = exited
    — запускаем контейнер с командой, которая сможет продолжить его работу.
    docker start 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

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

Кто то скажет «фу, костыль» и будет прав. Но это решение не претендует на истину в последней инстанции. Это простой и легкий в освоении вариант, который решает тактическую задачу

  • 3. Что делать, если мы за прокси?

    для wget добавляем ключ »--no-check-certificate»
    для sdk manager добавляем ключи »--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= --proxy_port=&& \
		sdkmanager --proxy=http --proxy_host= --proxy_port= && \
		cd /opt/project

Не забываем прописать прокси в gradle.properies

systemProp.http.proxyHost=
systemProp.http.proxyPort=

systemProp.https.proxyHost=
systemProp.https.proxyPort=

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

Если тянем с либы корпоративного репо — то и не забываем указать его же в build.gradle в двух местах!

    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/" }
    }
}

P.S. Интрига

Мы ж тут про девопс, а потому мы хотим смузи и женщин автоматизацию.а потому хотим, чтобы после коммита в репу запускалась сборка и итоговая апк вываливалась в какой-никакой общий доступ.Как это сделать красиво — в следующей статье) про простейший nginx и JenkinsТам будет про вывалить версию в версию. Короче, интрига.

© Habrahabr.ru