В конечном итоге мы получим отчет о выполненных тестах а так же подписанный apk файл, доступный для скачивания из артефактов сборки Jenkins.
Требования для настройки автоматических сборок
- Необходимые плагины Jenkins
- Android Emulator Plugin
- Git Plugin (если вы используете git)
- Multiple SCMs Plugin (для возможности работать с несколькими репозиториями)
- Xvnc Plugin (для возможности запуска эмулятора, если на сервере не установлен X server)
- Android SDK
Загружаем отсюда. Раздел DOWNLOAD FOR OTHER PLATFORMS -> SDK Tools only - Gradle
Лучше скачать архив тут (gradle-**-bin.zip) и распаковать в /usr/local/lib - Переменные окружения
ANDROID_HOME=/usr/local/lib/android/sdk
GRADLE_HOME=/usr/local/lib/gradle-1.8
JAVA_HOME=/usr/lib/jvm/jdk1.7.0_03
PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$JAVA_HOME/bin:$GRADLE_HOME/bin - Библиотека для запуска эмулятора
На случай если Jenkins работает на 64-битной ОС, необходимо добавить библиотеку ia32-libs, иначе эмулятор не запустится
sudo apt-get install ia32-libs
Когда все требования выполненны, приступаем к настройке
В своей работе я использую Eclipse, поэтому проекты имеют, так сказать, старую структуру, не типичную для gradle проектов (такую как создает androidStudio). Дальнейшие примеры будут приведены исходя из того что проект выглядит таким образом:
Project |-res |-src |-assets |-libs |-tests (папка содержащая проект с unit-тестами) |-src |-res |-AndroidManifest.xml |-AndroidManifest.xml |-build.gradle |-gradle.properties
Настройка
Для начала настроим обычный проект. Первым делом необходимо создать файл build.gradle, в корне проекта
buildscript { repositories { mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:0.6+' } } apply plugin: 'android' android { compileSdkVersion 18 buildToolsVersion "18.1.1" defaultConfig { minSdkVersion 8 targetSdkVersion 18 testPackageName "com.project.tests" testInstrumentationRunner "android.test.InstrumentationTestRunner" } sourceSets { main { manifest.srcFile file('AndroidManifest.xml') java.srcDirs = ['src'] resources.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] } instrumentTest { java.srcDirs = ['tests/src'] manifest.srcFile file('tests/AndroidManifest.xml') java.srcDirs = ['tests/src'] resources.srcDirs = ['tests/src'] res.srcDirs = ['tests/res'] assets.srcDirs = ['tests/assets'] } } dependencies { compile fileTree(dir: 'libs', include: '*.jar') } //Загружаем значения для подписи приложения if(project.hasProperty("debugSigningPropertiesPath") && project.hasProperty("releaseSigningPropertiesPath")) { //Файлы в которых хранятся значения для подписи File debugPropsFile = new File(System.getenv('HOME') + "/" + project.property("debugSigningPropertiesPath")) File releasePropsFile = new File(System.getenv('HOME') + "/" + project.property("releaseSigningPropertiesPath")) if(debugPropsFile.exists() && releasePropsFile.exists()) { Properties debugProps = new Properties() debugProps.load(new FileInputStream(debugPropsFile)) Properties releaseProps = new Properties() releaseProps.load(new FileInputStream(releasePropsFile)) //Дописываем в конфиг загруженные значения signingConfigs { debug { storeFile file(debugPropsFile.getParent() + "/" + debugProps['keystore']) storePassword debugProps['keystore.password'] keyAlias debugProps['keyAlias'] keyPassword debugProps['keyPassword'] } release { storeFile file(releasePropsFile.getParent() + "/" + releaseProps['keystore']) storePassword releaseProps['keystore.password'] keyAlias releaseProps['keyAlias'] keyPassword releaseProps['keyPassword'] } } buildTypes { debug { signingConfig signingConfigs.debug } release { signingConfig signingConfigs.release } } } } }
Для запуска тестов, достаточно указать секцию instrumentTest в android.sourceSets, в которой будут указаны пути к папкам в тестовом проекте. Когда я разбирался с Gradle видел не одну статью, в которой было написано что для запуска unit-тестов необходимо создать отдельную задачу, создать для нее отдельную запись в sourceSets и добавить в зависимости junit. В общем только зря потратил на это время, все гораздо проще.
Часть конфига, отвечающую за подписывание приложения, опишу немного ниже. После создания конфига приступим к настройке Jenkins.
Настройка Jenkins
Первым делом необходимо указать репозиторий с проектом. Затем необходимо выбрать пункт Run an Android emulator during build в котором необходимо выбрать один из двух вариантов — либо указать имя существующего эмулятора, либо указать параметры для запуска нового. При этом оставить галочку на пункте Show emulator window
Следующим делом необходимо добавить шаг сборки Invoke Gradle script и указать необходимые команды для Gradle. Для сборки и прогона тестов достаточно указать build и connectedCheck
Последним делом необходимо добавить действия выполняемые после сборки.
1. Publish JUnit test result report (публикуем отчеты о выполнении unit тестов). В строке для xml файла, с отчетом о выполненных тестах, необходимо прописать следующий путь:
**/build/instrumentTest-results/*/*.xml
Gradle создает 2 типа отчета — html и xml. HTML отчеты можно найти в папке build/reports/instrumentTests/connected/, но для Jenkins необходимо указать xml отчет.
2. Заархивировать артефакты (даем возможность скачать скомпилированное подписанное прилоежение прямо из артефактов). Путь к файлу для архивации:
**/build/apk/workspace-release.apk
Теперь вернемся к подписыванию приложения
Подпись приложения
Для подписи приложения необходимо иметь ключ созданный с помощью утилиты keytool, в котором будут хранится данные о разработчике приложения. Стандартная команда для создания:
keytool -genkey -v -alias appAlias -keyalg RSA -keysize 2048 -keystore release.keystore -validity 10000
Параметр alias необходимо исползьовать для каждого приложения свой, его потом необходимо будет указывать при подписывании приложения.
В целях безопасности ключ не стоит держать под системой контроля версий, т.к. имея этот ключ можно подписать любое другое приложение, но системой оно будет распознаваться как одно и то же. Поэтому файл необходимо хранить непосредственно на сервере CI.
Исходя из этих соображений, я добавил в корень проекта файл gradle.properties, в котором указал всего пару настроек:
releaseSigningPropertiesPath=.androidSigning/releaseProperties debugSigningPropertiesPath=.androidSigning/debugProperties
Значение releaseSigningPropertiesPath указывает на путь (относительно домашней директории, ~/.androidSigning/) по которому расположен файл с параметрами для ключей (пароли и alias). Для проекта в этой папке должно хранится 4 файла:
release.keystore — ключ для релизной сборки приложения
releaseProperties — параметры для релизного ключа
debug.keystore — ключ для дебаг сборки приложения
debugProperties — параметры для дебаг ключа
Каждый из файлов *Properties должен иметь следующую структуру:
keystore=файл-ключ (находящийся в этой директории) keystore.password=пароль к хранилищу ключей keyAlias=alias указанный при создании ключа keyPassword=пароль к ключу
Пример:
keystore=release.keystore keystore.password=mypassword keyAlias=appAlias keyPassword=mypassword
Все эти параметры указываются при создании ключа.
Все остальные действия были указаны в файле build.gradle. Чем удобен Gradle, тем что внутри конфига можно так же выполнять обычный java код, что и позволило сделать подобный механизм подписи. Все, теперь смело можно запускать сборку и получать подписанное и протестированное приложение.
Теперь давайте рассмотрим второй пример сборки приложения, которое использует приложение-библиотеку.
Сборка приложения использующего android-library
В моем случае проект и проект-библиотека находятся в различных репозиториях. Для возможности работать с несколькими репозиториями для Jenkins необходимо установить плагин Multiple SCMs Plugin. Первым делом опять необходимо создать конфиги для gradle. Первый файл находится в проекте-библиотеке
buildscript { repositories { mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:0.6.3' } } apply plugin: 'android-library' android { compileSdkVersion 18 buildToolsVersion "19.0.0" defaultConfig { minSdkVersion 8 targetSdkVersion 18 testPackageName "com.project.tests" testInstrumentationRunner "android.test.InstrumentationTestRunner" } sourceSets { main { manifest.srcFile file('AndroidManifest.xml') java.srcDirs = ['src'] resources.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] } instrumentTest { manifest.srcFile file('tests/AndroidManifest.xml') java.srcDirs = ['tests/src'] resources.srcDirs = ['tests/src'] res.srcDirs = ['tests/res'] assets.srcDirs = ['tests/assets'] } } dependencies { compile fileTree(dir: 'libs', include: '*.jar') } }
Основное отличие проекта-библиотеки это применяемый плагин: apply plugin: ‘android-library’
Второй конфиг уже внутри проекта
buildscript { repositories { mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:0.6.3' } } apply plugin: 'android' android { compileSdkVersion 18 buildToolsVersion "19.0.0" defaultConfig { minSdkVersion 8 targetSdkVersion 18 } sourceSets { main { manifest.srcFile file('AndroidManifest.xml') java.srcDirs = ['src'] resources.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] } } dependencies { compile fileTree(dir: 'libs', include: '*.jar') compile project(':MyLibrary') } //Загружаем значения для подписи приложения if(project.hasProperty("debugSigningPropertiesPath") && project.hasProperty("releaseSigningPropertiesPath")) { //Файлы в которых хранятся значения для подписи File debugPropsFile = new File(System.getenv('HOME') + "/" + project.property("debugSigningPropertiesPath")) File releasePropsFile = new File(System.getenv('HOME') + "/" + project.property("releaseSigningPropertiesPath")) if(debugPropsFile.exists() && releasePropsFile.exists()) { Properties debugProps = new Properties() debugProps.load(new FileInputStream(debugPropsFile)) Properties releaseProps = new Properties() releaseProps.load(new FileInputStream(releasePropsFile)) //Дописываем в конфиг загруженные значения signingConfigs { debug { storeFile file(debugPropsFile.getParent() + "/" + debugProps['keystore']) storePassword debugProps['keystore.password'] keyAlias debugProps['keyAlias'] keyPassword debugProps['keyPassword'] } release { storeFile file(releasePropsFile.getParent() + "/" + releaseProps['keystore']) storePassword releaseProps['keystore.password'] keyAlias releaseProps['keyAlias'] keyPassword releaseProps['keyPassword'] } } buildTypes { debug { signingConfig signingConfigs.debug } release { signingConfig signingConfigs.release } } } } }
Теперь необходимо правильно указать репозитории в Jenkins.
В отличии от предыдущего примера, в этом случае в разделе «Управление исходным кодом» необходимо выбрать не какую то конкретную систему контроля версий а «Multiple SCMs». После чего будет возможность добавить свою систему и указать путь к репозиторию. Первым необходимо указать репозиторий с проектом, вторым — с библиотекой, при этом для репозитория с библиотекой следует указать дополнительную настройку «Check out to a sub-directory», содержащую имя папки в которой будет находится код. Имя этой папки должно соответствовать имени проекта, которое мы указали в зависимостях внутри файла build.gradle (в примере это MyLibrary)
compile project(':MyLibrary')
Таким образом рабочая директория будет выглядеть как обычный проект, только с одной дополнительной папкой MyLibrary, в которой будет находится библиотека.
Все остальные настройки точно такие же как и для обычного проекта. Теперь можно указать параметры для gradle — clean build и получить готовое подписанное приложение. В случае если проект-библиотека содержит unit тесты, тут есть проблема, о ней расскажу в конце.
В качестве бонуса
В своем проекте мне приходится работать с различными окружениями. Например для тестовой версии приложения необходимо отправлять запросы, для получения каких либо данных, на тестовый сервер, в процессе разработки на локальный сервер. Таким образом можно выделить 3 типичных окружения dev, stage и prod. Настройки зависящие от окружения я разместил в ресурсах приложения, в файле res/values/environment.xml, в нем указан URL по которому необходимо обращаться за данными. Файлы с настройками для конкретных окружений я вынес в отдельную папку environment, в которой находятся 3 файла с настройками: dev.xml, stage.xml и prod.xml. Для того чтобы приложение работало с необходимым окружением нужно просто подставить один из этих файлов вместо environment.xml.
Для этого в Jenkins необходимо добавить первым шагом сборки запуск команды shell и указать следующую:
cp $WORKSPACE/environment/prod.xml $WORKSPACE/res/values/environment.xml
В завершении
Я не рассматривал в статье использование lint или proGuard. Ниже я добавил ссылку на техническую документацию, по запуску proGuard там все просто, можно посмотреть и настроить, а вот что касается поддержки lint, ее пока нет, обещают в скором времени добавить.
Я так и не смог разобратся с запуском unit тестов в библиотечном проекте. При запуске тестов возникает ошибка, которую так и не удалось решить. Я до сих пор не получил никакого ответа ни на stackoverflow ни в группе google. Надеюсь в ближайшее время этот вопрос решится.
Полезные ссыли
www.gradle.org/docs/current/javadoc/ — документация Gradle
tools.android.com/tech-docs/new-build-system/user-guide техническая документация
tools.android.com/recent/updatingsdkfromcommand-line — обновление Android SDK через консоль (на случай если нет иксов)
ссылка на оригинал статьи http://habrahabr.ru/post/205308/
Добавить комментарий