Как и зачем собирать Android приложение в docker контейнере
Добрый день, уважаемые читатели.
Я — Владимир, меня зовут девопс. Говорят, что девопс — это болезнь и я это вам сегодня докажу.
Ответа на вопрос «зачем?» вы тут не найдете, это кликбейт, я и сам не знаю. Все происходящее мотивируется девизом «бекоз ай кен».
Если без шуток, то можно прогонять процесс сборки, создавать изолированные тест-кейсы, прогонять через автотесты, 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Там будет про вывалить версию в версию. Короче, интрига.