Jenkins для Android на чистой системе и без UI

в 8:32, , рубрики: android, continuous integration, Jenkins CI, Разработка под android, системы сборки

На Хабре уже есть похожие статьи на тему сборки Android приложения с помощью Jenkins. Ключевыми особенностями/дополнениями текущей будет следующее:

  1. Мы установим Jenkins на удалённую Linux машину, где отсутствует UI.
  2. Мы будем собирать приложение из приватного репозитория.
  3. Мы решим проблему сборки приложения из ветки имя которой нам не известно.
  4. После сборки .apk файлов мы отправим их в Fabric и оповестим тестировщиков.
  5. После отправления в Fabric мы опубликуем приложение на Google Play.
  6. Защитим задачи по публикации приложения от запуска тестировщиками.

Установка jenkins

Итак, вы получили доступ на сервер. UI там отсутствует, но это совсем не проблема.
Для начала давайте установим сам jenkins сервер.

$ wget -q -O - https://pkg.jenkins.io/debian/jenkins-ci.org.key | sudo apt-key add -
$ sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
$ sudo apt-get update
$ sudo apt-get install jenkins

Более подробно о том, что делает установка этого пакета можно почитать в официальной документации.

После установки, в директории /var/lib/jenkins будет находиться сам Jenkins, репозитории проектов и другие необходимые для работы Jenkins файлы. Теперь вы уже можете зайти в Jenkins через браузер и начать конфигурирование своего CI сервера.

Однако перед этим необходимо установить всё необходимое для сборки Android приложения.

Установка Android SDK+JAVA

На официальном сайте внизу можно найти ссылки на тот дистрибутив, который нам нужен. Обычно его устанавливают вместе с Android Studio, однако в нашем случае, она нам не нужна, поэтому выбираем нужную ссылку и загружаем инструменты командной строки(Command Line Tools).

На момент написания статьи актуальная версия для Linux была: android-sdk_r24.4.1. Для установки выполняем команды:

$ wget http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
$ tar -xvf android-sdk_r24.4.1-linux.tgz

Далее вам необходимо скачать используемые в вашем проекте Platform Tools, Build tools и текущее API, для которого вы собираете приложение. Для этого загляните в build.gradle файл вашего проекта (на уровень модуля приложения). Например, в моём проекте интересующая нас часть выглядит следующим образом:

android {
    ....
    compileSdkVersion 25
    buildToolsVersion '25.0.0'
    ....
}

Это наглядно показывает, какие версии нам необходимо скачать. Для того, чтобы мы могли скачать необходимые нам компоненты, добавим в системные переменные следующие параметры:

$ export ANDROID_HOME=~/android-sdk-linux # Место где мы сохранили SDK-Tools
$ export PATH=$ANDROID_HOME/tools:$PATH
$ export PATH=$ANDROID_HOME/platform-tools:$PATH

Далее в большинстве гайдов предлагается просто скачать все компоненты с помощью команды:

$ android update sdk --no-ui --all

Но так как нам жалко впустую тратить место на жёстком диске, мы будем скачивать только то, что нам нужно. Выполним команду, чтобы узнать, что именно нам нужно скачивать:

$ android list sdk --all

Результаты выглядят следующим образом:

Jenkins для Android на чистой системе и без UI - 1

Теперь мы можем установить только то, что нам нужно с помощью команды:

$ android update sdk --no-ui --all --filter 1,2,163,164,168,169,170,171

Теперь поставим JAVA с помощью команды:

$ sudo apt-get install openjdk-8-jdk

Отлично! Мы поставили всё необходимое для сборки Jenkins и сборки приложения. Теперь переходим к настройке Jenkins.

Настройка доступа к приватному репозиторию

Если ваш git репозиторий публичный или вы решили получать доступ к нему с помощью логина и пароля, можете пропустить этот блок и перейти сразу к следующему.

Итак, мы решили, что мы будем всё делать правильно и доступ к репозиторию получать по SSH. Для этого нам надо сгенерировать ssh ключ и положить его в директорию /var/lib/jenkins/.ssh.

Когда вы установили jenkins, был автоматически сгенерирован пользователь с именем jenkins на unix машине. Для того, чтобы jenkins мог использовать ключ, проще всего будет его сгенерировать из-под jenkins пользователя.

$ sudo su jenkins -s /bin/bash
$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

После этого будет сгенерировано 2 файла (со стандартными именами id_rsa, id_rsa.pub). Файл id_rsa необходимо положить в директорию /var/lib/jenkins/.ssh, после этого необходимо в настройках github репозитория добавить Deploy Key (содержимое файла id_rsa.pub).

Выглядит это следующим образом:

Jenkins для Android на чистой системе и без UI - 2

Далее надо добавить URL репозитория в список известных. Для этого попробуем обратиться к репозиторию и получить список веток репозитория.

$ sudo apt-get install git # Устанавливаем git
$ sudo su jenkins -s /bin/bash
$ git ls-remote -h git@github.com:[your_repo_url]

И соглашаемся на добавление github в список известных хостов. Если у вас нету доступа к файлу id_rsa, значит вы создали его не из-под jenkins пользователя, и в этом случае вам надо дать доступ jenkins пользователю на работу с этим файлом:

$ sudo chmod 700 /var/lib/jenkins/.ssh/id_rsa 

После выполнения этих шагов мы сможем получить доступ к нашему приватному репозиторию для сборки Android приложения.

Создание задачи для сборки приложения

Теперь перейдём в jenkins. По умолчанию он будет доступен на порту 8080. Перейдем в любом браузере на данный порт. После первого запуска залогиниться можно будет с помощью пароля, который находится в файле /var/lib/jenkins/secrets/initialAdminPassword. После этого вы попадаете в Jenkins панель. Для начала нам нужно настроить путь к ANDROID_HOME директории.

Manage Jenkins(Настроить Jenkins) → System Configuration(Конфигурирование Системы):

Jenkins для Android на чистой системе и без UI - 3

Также настроим пути к JDK, версию Gradle и Git. Для этого укажите директорию, где хранится JAVA, и добавьте необходимую для сборки версию gradle.

Manage Jenkins(Настроить Jenkins) → Global Tool Configuration:

Jenkins для Android на чистой системе и без UI - 4

Jenkins для Android на чистой системе и без UI - 5

Теперь мы можем создать первую задачу для сборки приложения. Выберем создание задачи со свободной конфигурацией. На вкладке с «Управлением исходным кодом» выбираем git. Далее указываем url git репозитория для доступа по ssh. Для первой задачи будем собирать из ветки мастера. Именно она по умолчанию и указана.

Далее добавим шаг сборки с помощью запуска gradle скрипта. Выбираем gradle версию, добавленную ранее. На всех важных полях находятся подсказки, зачем они нужны. Например, если у вас нестандартная структура проекта, то вам нужно указать место, где находит корневой build.gradle файл проекта и имя файла, если оно отличается от build.gradle.

Jenkins для Android на чистой системе и без UI - 6

Далее заархивируем .apk файлы, чтобы можно было их позже скачать тестировщикам или кому-нибудь другому. Для этого надо добавить послесборочную задачу и указать формат, какие файлы мы хотим сохранять. Первый раз при создании задачи я предполагал, что поиск нужного файла производится с помощью формата регулярного выражения, однако оказалось, что для поиска файлов Jenkins использует Ant path style. Стандартно предлагается сохранять все файлы с расширением .apk, однако большая часть из них нам не нужна, поэтому будем сохранять только те, которые заканчиваются на debug.apk, и release.apk.

Jenkins для Android на чистой системе и без UI - 7

Сборка подписанных apk с помощью build.gradle скрипта

Для публикации приложения в Google Play .apk файл должен быть подписан специальным ключом. Если вы подписывали с помощью Android Studio, указывая путь к файлу, то надо научиться делать это из build.gradle скрипта. Далее мы рассмотрим самый простой пример и способы, как его можно улучшить. Достаточно положить ключ под версионный контроль, и после этого ссылаться на него из build скрипта. Вот пример, как это можно реализовать.

android {
    signingConfigs {
        release {
            keyAlias 'KEY_ALIAS'
            keyPassword 'KEY_PASSWORD'
            storeFile file('RELATIVE_PATH_TO_KEYSTORE')
            storePassword 'STORE_PASSWORD'
        }
    }
    ...
    buildTypes {
            release {
                signingConfig signingConfigs.release
            }
    }
}

Недостатком решения является то, что ваш приватный ключ хранится под версионным контролем, как и пароль для сборки релизной версии приложения. Обратите внимание на то, что даже если вы в будущем уберёте ключ и поправите build.gradle скрипт, то существует вероятность, что кто-нибудь, покопавшись в истории коммитов, восстановит эту информацию, так что будьте внимательны. Более правильным решением было бы держать property файл, который не находится под версионным контролем и лежит, например, в ~/home директории, в котором были бы написаны все необходимы параметры.

Например, в нашем проекте используется следующий подход. Файл с настройками и файл для подписи отсутствуют в версионном контроле. У разработчиков они подложены руками. Таким образом, мы решаем проблему того, что кто-то, кому этого делать не следуют, может подписать ваше приложение. Теперь надо решить проблему, чтобы подписывать приложение мог сам Jenkins. Для этого будем при подписи приложения проверять наличие файла в двух местах. Первое — это корневая директория проекта. Второе — определённое место на сервере, где ожидается, что этот файл должен храниться. Далее вы подкладываете этот файл на jenkins сервер и получаете возможность собирать подписанные версии приложения. Вот пример gradle скрипта, как это реализуется:

android {
    ...
    signingConfigs {
        if (rootProject.file("release.properties").exists() || new File("/home/ubuntu/release.properties").exists()) {
            def properties = new Properties()
            def fileToLoad = rootProject.file("release.properties").exists() ?
                    new FileInputStream(rootProject.file("release.properties")) : new FileInputStream("/home/username/release.properties")
            properties.load(fileToLoad)
            release {
                keyAlias properties.keyAlias
                keyPassword properties.keyAliasPassword
                storePassword properties.keyStorePassword
                storeFile rootProject.file('keystore.jks')
            }
       }
    }
    ...
    buildTypes {
        release {
            if (signingConfigs.hasProperty("release"))
                signingConfig signingConfigs.release
        }
    }
}

Публикация тестовых сборок в Fabric и оповещение тестировщиков

Fabric — замечательная система для сборка креш-репортов по вашему приложению. Если вы используете стандартную гугл аналитику для сборки крешей, то очень рекомендую посмотреть fabric или firebase. Для использования Fabric нам понадобится установить jenkins плагин.

Настроить jenkins(Manage Jenkins) → Управление плагинами (Manage Plugins) вкладка Доступные, вводим Fabric. Нас интересует Fabric Beta Publisher. После его установки вернёмся в описание задачи и добавим ещё один.

Jenkins для Android на чистой системе и без UI - 8

Jenkins для Android на чистой системе и без UI - 9

Необходимо указать Fabric_api_key, а также secret_key, и указать относительный путь к .apk файлу приложения, которое мы будем заливать для тестировщиков. Посмотреть эти значения можно в личном кабинете fabric. Settings → Organizations → {Your_app_name} → API_KEY, Build Secret.

Далее вы можете прислать нотификации для ваших тестировщиков. Можно указывать как email, так и группу тестировщиков. Каким образом управлять группами, вам будет виднее лучше всего самим. Например, мы предпочитаем использовать 2 группы: dev, production. Все тестовые сборки приложений нотифицируют dev группу тестировщиков, а сборки, которые будут заливаться в Google Play, нотифицируют production группу тестировщиков.

Далее, если наши сборки каким-то образом отличаются друг от друга, то нам необходимо указать эту информацию в Build Notes для тестировщиков. Делаем это через Environment Variable и скрипт, который будет заполнять данную информацию.

На нашем проекте придерживаются следующего подхода. Когда приходит время релиза, из master ветки создаётся ветка delivery_xx, где xx — это номер спринта. К сожалению, Jenkins по умолчанию не умеет собирать сборки из веток, где имя ветки может меняться. Нам придётся научить его этому самим.
Для этого нам понадобится установить ещё один плагин — EnvInject. После этого возвращаемся в вид задачи и устанавливаем новую опцию — Prepare an environment for the run. Благодаря этому плагину мы можем выполнить какой-либо groovy скрипт, и после этого сохранить в переменных среды все необходимые результаты.

В нашем случае, нам необходимо получить список веток у репозитория и найти ветку, имя которой начинается на delivery. Также, нам надо установить build notes для тестировщиков, чтобы они знали, из какой ветки производилась сборка приложения. Скрипт для этого будет выглядеть следующим образом:

def gitURL = "git@github.com:[your_repo_url]"
def command = "git ls-remote -h $gitURL"

def proc = command.execute()
proc.waitFor()
if (proc.exitValue() != 0) {
    println "Error ${proc.err.text}"
}

def branch = proc.in.text.readLines()
.collect { it.replaceAll(/[a-z0-9]*trefs/heads//, '')}
.find { it.startsWith("delivery")}

if (branch == null) {
    def build = Thread.currentThread().executable
    build.doStop()
    return 0
}

def map = [BRANCH: branch, FABRIC_RELEASE_NOTES: "$branch"]

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

Jenkins для Android на чистой системе и без UI - 10

И обновим блок публикации приложения в fabric, как показано на скрине выше.
Теперь мы умеем делать почти все пункты, описанных в начале статьи, и нам осталось только научиться публиковать итоговую сборку приложение в google play.

Публикация в Google Play

Для нас уже подготовили плагин для публикации приложений в Google Play с помощью Jenkins. Ставим его: Google Play Android Publisher Plugin. Далее нам необходимо создать специальный сервисный аккаунт, который будет заниматься публикацией приложений в Google Play. Для этого необходимо выполнить следующие шаги:

  1. Заходим в консоль разработчика под владельцем аккаунта.
  2. Настройки → Доступ к API.
  3. Нажимаем «Создать проект».
  4. Нажимаем «Создать аккаунт приложений».
  5. Переходим по ссылке.
  6. В выпадающем списке нажимаем «Edit» и меняем имя аккаунта на необходимое, например «Jenkins».
  7. В выпадающем списке выбираем «Сreate key» и выбираем тип ключа «JSON».
  8. Нажимаем создать ключ.
  9. Автоматически будет загружен файл, который позже будет использоваться для аутентификации в консоли разработчика плагина для публикации приложения.

То же самое в картинках:

Jenkins для Android на чистой системе и без UI - 11

Jenkins для Android на чистой системе и без UI - 12

Jenkins для Android на чистой системе и без UI - 13

Jenkins для Android на чистой системе и без UI - 14

Далее необходимо выдать соответствующие доступы в консоли разработчика для созданного сервисного аккаунта и публикации приложений. Для этого выполняем следующие шаги:

  1. Возвращаемся в консоль разработчика
  2. Для созданного аккаунта приложений нажимаем «Открыть доступ»
  3. Для созданного аккаунта приложений нажимаем «Открыть доступ»
  4. Необходимо убедиться, что есть разрешения для Изменения информации о приложении и управлении Production, Alpha и Beta версиями приложения
  5. Нажимаем добавить

То же самое в картинках:

Jenkins для Android на чистой системе и без UI - 15

Jenkins для Android на чистой системе и без UI - 16

Теперь мы можем выйти из консоли разработчика и перейти к настройке Jenkins для публикации приложения.

  1. Переходим в Jenkins
  2. Выбираем пункт меню «Credentials»
  3. Выбираем общие доступы и нажимаем «Add Credentials»
  4. Выбираем тип доступа: «Google Service Account from private key»
  5. Вводим имя доступа(позже будет использоваться в настройке задачи), например android-publish
  6. Выбираем тип «JSON key»
  7. Загружаем файл, который мы ранее скачали «JSON key»
  8. Нажимаем ОК, чтобы создать доступ

Тоже самое в картинках:

Jenkins для Android на чистой системе и без UI - 17

Jenkins для Android на чистой системе и без UI - 18

Далее переходим в вид задачи и добавляем Шаг после сборки — Upload Android APK to Google Play.
Необходимо указать credentials для этого шага, которые мы добавили ранее. А также путь к apk файлу и информацию, какой процент пользователей необходимо перевести на данную версию. Вероятно, изначально это будет 100%. Также необходимо указать, какую версию .apk файла вы заливаете. Вы можете публиковать alpha, beta, production версии через jenkins. После этого, можно спокойно запускать задачу, а самому идти пить чай, пока jenkins сделает за вас всю рутинную работу по сборке, подписыванию приложения и публикации.

Jenkins для Android на чистой системе и без UI - 19

Защита критических задач

Теперь мы умеем создавать задачи, можем давать доступы тестировщикам, и они сами будут собирать себе сборки приложений, когда им это будет нужно. Естественно, мы не хотим, чтобы наши QA случайно опубликовали приложение. Предлагаю поступить следующим образом.
Нам будет необходимо вынести публикацию приложения в отдельную задачу и забрать доступы у QA по запуску этой задачи.

  1. Переходим в Настройки Jenkins → Сonfigure Global Security
  2. Блок «Авторизация» → выбираем пункт «Project-based Matrix Authorization Strategy»
  3. Сразу добавляем своё имя пользователя и выставляем для себя все пункты как доступные. Если не сделать этот пункт, вы запретите доступ для самого себя из jenkins (если всё-таки так случилось, смотрите решение тут).
  4. Далее добавляете имя пользователя QA, и устанавливаете ему разрешения, которые считаете нужными. Для нас принципиально не давать ему запускать задачи. Блок «Задача», пункт «Build»
  5. После этого переходим к задаче, доступ для которой хотим дать QA, и даём ему соответствующие доступы.

Jenkins для Android на чистой системе и без UI - 20

Теперь наши QA не смогут случайно опубликовать приложение в Google Play.

Пожалуй, на этом всё. Мы могли бы и дальше улучшать автоматическую сборку и публикацию приложений, однако это выходит за рамки данного tutorial.

P.S. Было бы интересно также сравнить Jenkins с другими CI серверами и посмотреть, как реализовывать аналогичные пункты для сборки приложений. Также интересно попробовать оформить задачу не в виде свободной конфигурации, а с помощью PipeLine, чтобы мы могли всю конфигурацию описывать в документе, который мы поместим под версионный контроль и будем динамически менять в проекте.

Автор: ANublo

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js