Android — Сontinuous Integration. Часть 2

в 11:40, , рубрики: android development, continuous integration, intellij idea, maven, testing, Разработка под android, метки: , , , ,

Первая часть, рассказывающая для чего все это нужно здесь.

Содержание

  • Подготовка
  • Maven
    • Root
      • Build profiles
      • Plugins
    • App
      • Resource filtering
    • Lib
    • Test
  • Заключение
  • Ссылки

Пост рассчитан на читателей уже знакомых с основами maven’а и в ходе статьи акцент будет делаться на каких-то специфических, именно для андроида, моментах, а не на общих вопросах самого мавена. Если же вы до этого ни разу не работали с мавеном, то для начала можно почитать здесь и здесь.

Так же я не буду рассматривать установку и базовую настройку инструментов — JDK, Android SDK, Maven и IntelliJ IDEA должны быть установлены и работать. У вас должны быть настроены соответствующим образом переменные окружения JAVA_HOME, M2, M2_HOME и ANDROID_HOME. Так же, для удобства работы, рекомендую добавить в Path директории %ANDROID_HOME%/tools и %ANDROID_HOME%/platform-tools.

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

Подготовка

Прежде чем приступить к рассмотрению самих POM-файлов для начала организуем структуру проекта и настроим его для работы в IntelliJ IDEA. Это даст нам возможность в дальнейшем использовать стандартные средства IDE для запуска и отладки приложений, да и вообще, полноценно пользоваться всеми ее преимуществами. IDEA прямо “из коробки” имеет встроенные плагины для Maven’а и Android’а, и настройка не вызывает почти никаких проблем. Достаточно открыть панель Maven плагина и выбрать соответствующий корневой POM-файл. На основе данных из него, IDEA создаст файлы проекта, которые, возможно немного придется донастроить вручную.

Android — Сontinuous Integration. Часть 2

Проект состоит из четырех модулей:

Root
 |---- App
 |    |---- src
 |    |---- test
 |         |---- JUnit
 |         |---- Robolectric
 |
 |---- Lib
 |    |---- src
 |    |---- test
 |         |---- JUnit
 |         |---- Robolectric
 |
 |---- Test
      |---- src
           |---- Instrumentation
           |---- Robotium
  • Root — корень проекта
  • App — само приложение
  • Test — instrumentation apk, он же модуль с Android и Robotium тестами
  • LibAndroid Library Project, собирается в APKLIB

Помимо отдельного модуля с тестами каждый модуль в проекте имеет папку test, в которой хранятся юнит-тесты (JUnit или Robolectric).

Maven

Для меня одним из стимулов попробовать maven был меньший размер XML-ок по сравнению с Ant'ом, а как следствие, бОльшая читабельность и поддерживаемость. Однако, как это обычно и бывает, то, что в туториале выглядит компактно, на практике разрастается до неузнаваемых размеров. Так и в этот раз, скрипты обросли всеми хотелками и стали ничуть не меньше антовских. Но несмотря на это, по моему субъективному мнению, читать их значительно проще чем Ant. К тому же maven в отличие от скрипто-подобного Ant’a предлагает декларативную парадигму. Т.е. мы описываем не «как» именно мы хотим что то получить, а «что» именно, а как это будет получено нас уже не волнует.

Так же на практике довольно ощутимым плюсом оказалась возможность dependency management’a, хотя в Ant’е она, в принципе, тоже решаема с использованием Ivy. Ну и еще один тычок в сторону муравья, как сказал кто то из известных: “Программировать на XML-е — вообще странная идея” =)

Пройдемся по POM-файлам наших модулей

Root

Корневой модуль проекта. Содержит все остальные модули и общие для всех модулей куски конфигурации. Тип упаковки по умолчанию для таких модулей

<packaging>pom</packaging>

Далее объявляются различные мелочи вроде адреса баг-трекера, адреса репозитория, описание проекта и т.д.

Затем идет блок пропертей:

    <properties>
        <project.version.name.number>1.0.0</project.version.name.number>
        <project.version.code>1</project.version.code>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <project.emulator.name><!-- TODO emulator name --></project.emulator.name>
        <project.version.name>${project.version.name.number}-${project.version.name.qualifier}</project.version.name>
        <project.verbosity>true</project.verbosity>
    </properties>
  • версия и versionCode apk-файла
  • кодировка исходников
  • имя эмулятора, на котором будут запускаться тесты и куда будет деплоится готовое приложение. Значение используется дальше в скрипте android-maven-plugin'ом. Если конкретное значение не указывать, то по умолчанию будут использоваться все эмуляторы / устройства доступные в данный момент. Так же кроме имени устройства допустимыми значениями являются константы usb и emulator.
  • имя проекта. Версия + квалификатор, значение которого тоже берется из соответствующей property уникальной для каждого конкретного профиля (О профилях подробнее будет ниже)
  • флаг -v, который передается многим утилитам из Android SDK, стимулирующий вывод различной дополнительной информации.

Затем блок объявляющий зависимые модули: App, Test и Lib. А дальше секция объявления профилей.

Build profiles

Как отмечалось в предыдущей части, приложение можно собирать в нескольких различных конфигурациях: production, test и development. Профили и управляют этим процессом. Каждый профиль позволяет перекрывать значения различных пропертей, настройки плагинов и другие параметры сборки. При компиляции проекта в зависимости от активного профиля будут взяты соответствующие настройки сборки и использованы в приложении. Например, используя процессинг ресурсов, таким образом, можно изменить ссылку на сервер, которая может отличаться для production и test конфигураций (Об этом подробнее ниже).

Активировать необходимый профиль можно в IDEA с помощью Maven плагина.

Android — Сontinuous Integration. Часть 2

Либо, если POM запускается из консоли или build-сервером, то значение активного профиля можно передать указав дополнительный параметр. Например собрать production сборку приложения из командной строки можно следующей командой:

mvn install -P production

По умолчанию активен development профиль, таким образом, если ничего не указывать дополнительно, то будет собрана dev-конфигурация.

Помимо конкретных настроек приложения, вроде параметров подключения к серверу или базе данных, профиль определяет будет ли оптимизироваться и подписываться apk-файл, включен ли debug-режим и какой квалификатор получит конечный артефакт.

test и development профили небольшие по размеру и их содержимое задает только настройки приложения (параметры подключения к серверу). Немного больше них по размеру определение production профиля. Оно включает в себя настройку процесса подписывания приложения сертификатом.

                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-jarsigner-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>signing</id>
                                <goals>
                                    <goal>sign</goal>
                                    <goal>verify</goal>
                                </goals>
                                <phase>package</phase>
                                <inherited>true</inherited>
<configuration>
                                    <archiveDirectory/>
                                    <includes>
                                        <include>${project.build.directory}/*.apk</include>
                                    </includes>
                                    <keystore>${project.basedir}/keystore</keystore> <!-- TODO add keystore to project -->
                                    <storepass><!-- TODO --></storepass>
                                    <keypass><!-- TODO --></keypass>
                                    <alias><!-- TODO --></alias>
                                    <removeExistingSignatures>true</removeExistingSignatures>
                                    <verbose>true</verbose>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>

В настройках maven-jarsigner-plugin указывается в какой момент будет происходить подписывание пакета, путь к нашему keystore и его параметры. Кстати для отладки скрипта очень удобно запускать его с параметром -X, например, mvn install -X. При этом maven выдает большое количество отладочной информации, анализируя которую можно разобраться с проблемой.

Далее в android-maven-plugin’e отключается debug-режим для production сборки. И последний штрих — maven-compiler-plugin включает оптимизации кода production версии.

В build-секции POM’а задается имя результирующего apk-файла и настраиваются различные плагины.

Plugins

Затем идут настройки плагинов. Плагин для обработки ресурсов, указание maven-compiler-plugin’у использовать 6-ую версию Java, настройка самого android-maven-plugin’а и настройка maven-idea-plugin’а.

maven-idea-plugin позволяет скачивать документацию и исходники зависимостей проекта, что дает нам возможность заглянуть внутрь исходных кодов платформы или библиотеки или быстро ознакомиться с javadoc’ом (Ctrl+Q). Мелочь, а приятно! =)

Android-maven-plugin

Рассмотрим более подробно настройку андроид плагина. Плагин настраивается так же как и все другие maven-плагины в секции <plugins>/<plugin>/<configuration>.

Задаем версию API которой будет компилироваться проект.

                    <sdk>
                        <platform>14</platform>
                    </sdk>

Далее параметры манифеста, которые мы задаем в пропертях, уникальных для каждого из профилей (см выше).

                    <manifest>
                        <versionName>${project.version.name}</versionName>
                        <versionCode>${project.version.code}</versionCode>
                        <debuggable>${project.debug.mode}</debuggable>
                    </manifest>

Настройки эмулятора: имя, таймаут ожидания запуска и другие опции. Т.к. скрипт используется TeamCity для сборки с последующим запуском тестов на эмуляторе в «безголовом» режиме, то указана опция -no-window.

                    <emulator>
                        <avd>${project.emulator.name}</avd>
                        <wait>300000</wait><!-- 5 min -->
                        <options>-no-window</options>
                    </emulator>

Указываем плагину каждый раз удалять приложение прежде чем устанавливать заново.

<undeployBeforeDeploy>true</undeployBeforeDeploy>

Далее задаются параметры выравнивания финального apk-файла и задается имя конечного файла, которое будет состоять из номера версии с добавлением суффикса -signed-aligned

                    <zipalign>
                        <skip>false</skip>
                        <verbose>${project.verbosity}</verbose>
                        <outputApk>${project.build.directory}/${project.build.finalName}-signed-aligned.apk</outputApk>
                    </zipalign>

Затем в секции <executions> задаются цели когда необходимо обработать манифест и выровнять приложение.

                <executions>
                    <execution>
                        <id>zipalign</id>
                        <phase>package</phase>
                        <goals>
                            <goal>zipalign</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>update-manifest</id>
                        <goals>
                            <goal>manifest-update</goal>
                        </goals>
                    </execution>
                </executions>

Оставшиеся POM-файлы модулей App, Test и Lib менее интересны, пройдемся вкратце по ним.

App

Как обычно вначале идут настройки артефакта собираемого модулем и указание родительского модуля. Указывается тип упаковки — APK. Секция с репозиториями для зависимостей отсутствующих в maven-central, например, сюда можно добавить ваш корпоративный репозиторий, если такой есть.

Кстати в maven-central отсутствуют многие версии Android платформы и часто используемые библиотеки из SDK. Например, карты, compatibility package, AdMob SDK и т.д. А если и появляются то с большой задержкой. Для упрощения жизни разработчикам и в этом вопросе Manfried Moser, автор maven-android-plugin'a, разработал еще один полезный проект — Maven Android SDK Deployer, который позволяет одной командой залить все необходимые артефакты в ваш личный репозиторий и уже от туда их смело использовать.

Дальше список необходимых проекту зависимостей. В качестве одной из зависимостей указывается наш модуль Lib

        <dependency>
            <groupId>com.devoxy.android</groupId>
            <artifactId>template-project-lib</artifactId>
            <version>1.0.0</version>
            <type>apklib</type>
        </dependency>

Обратите внимание на тип упаковки apklib — тип для Android библиотек, содержащих ресурсы. В случае IntelliJ IDEA на каждую зависимость apklib автоматически будет создан IDEA-модуль имя которого будет начинаться с ~.

Однако, с apklib-зависимостями все не так гладко. У меня к сожалению так и не получилось заставить работать в IDEA проект с 2-мя и больше apklib-ами. При сборке maven-ом такой проект собирается хорошо, но при сборке через IDEA полученный apk файл получается неработоспособным. Более детально проблема описана в багтрекере JetBrains и на stackoverflow. В качестве workaround'а можно создать maven конфигурацию как на рисунке.

Android — Сontinuous Integration. Часть 2

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

В секции <build> указываем директории с исходниками и тестами.

        <sourceDirectory>src</sourceDirectory>
        <testSourceDirectory>test</testSourceDirectory>

И настраиваем процессинг манифеста и других ресурсов приложения.

Resource filtering

В файле, хранящем настройки приложения (в моем примере это application.properties в /assets, но это может быть любая XML-ка из /res или вообще java файл из /src), можно задавать проперти в виде ${property.name} значение которой на этапе обработки ресурсов, Maven’ом будет заменено на значение проперти из нашего POM-файла. Таким образом, задавая различные значения для одних и тех же пропертей в различных конфигурациях и фильтруя ресурсы, можно добиться необходимого эффекта. Например, задать параметры подключения к серверу.

Так же фильтрация ресурсов здесь позволяет обойти еще одну небольшую проблему. android-maven-plugin умеет изменять некоторые атрибуты в манифесте нашего приложения в частности versionName и versionCode, что нам и нужно. Но для этого плагин использует встроенный парсер и после любого изменения автоматически переформатирует манифест так как ему нужно, а не как удобно нам, убивая начисто многие пробелы и переносы строк, что превращает AndroidManifest.xml в нечитаемую кашу. Что бы избежать этого эффекта и сохранить читаемость оригинального манифеста так же используется resource filtering. Во время процессинга создается копия манифеста и уже в ней происходят замены и переформатирование.

Сначала, все что касается обработки ресурсов у меня хранилось в Root модуле, но небольшой баг в плагине не позволяет использовать эту хитрость в APKLIB модулях. Поэтому данные настройки были вынесены из Root в App модуль, дабы избежать лишних проблем.

Задаем путь к директории /asstets.

<assetsDirectory>${project.build.directory}/filtered-assets</assetsDirectory>

Так как мы используем обработку ресурсов, то этот путь у нас отличается от пути по умолчанию и мы должны явно указать папку с готовыми ресурсами (см. часть про Resource filtering).

По той же причине указывается путь к отфильтрованному манифесту, отличный от пути по умолчанию.

<androidManifestFile>${project.build.directory}/AndroidManifest.xml</androidManifestFile>
Lib

Тип упаковки APKLIB. Вообще здесь можно указать и jar. Зависит от того что будет внутри вашей библиотеки. Если библиотека содержит только java-код, то хватит и jar, если же помимо кода библиотека предоставляет и ресурсы (лейауты, активити, стили и т.д.), т.е. представляет из себя Android Library Project, то ставим APKLIB.

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

Test

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

        <dependency>
            <groupId>com.devoxy.android</groupId>
            <artifactId>template-project-app</artifactId>
            <version>${project.version}</version>
            <scope>provided</scope>
            <type>apk</type>
        </dependency>
        <dependency>
            <groupId>com.devoxy.android</groupId>
            <artifactId>template-project-app</artifactId>
            <version>${project.version}</version>
            <scope>provided</scope>
            <type>jar</type>
        </dependency>

Заключение

Изначально я собирался хранить номер версии в виде Х.Х.Х в качестве проперти в билд-скрипте, но, к сожалению, в случае проекта из нескольких модулей это невозможно. Maven не умеет резолвить проперти в заголовках версии артефакта. Поэтому был необходим способ менять номер версии во всех pom-файлах и в AndroidManifest.xml. По хорошему для этого нужно использовать maven-release-plugin или какие-нибудь костыли (ant-скрипт как у меня).

Так как мне необходимо было быстро найти работающее решение, а времени разбираться с релиз плагином не было, то я сделал временно на костылях, а как известно — нет ничего более постоянного чем временное =)

Номер версии проекта лежит в корне в файле version.properties. Единственный ant-target выполняет обновление всех POM-файлов в соответствии с версией в файле.

Кроме этого хотелось бы сделать более гибкий механизм для запуска различных test-scopes в разных конфигурациях. Android Testing Framework предоставляет несколько различных аннотаций для ваших тестов @SmallTest @MediumTest и @LargeTest, с помощью которых можно разделить все ваши instrumentation-тесты на несколько логических групп. Т.к. запуск и прогон integration-тестов, особенно на эмуляторе, очень долгая операция, то хотелось бы при сборке на TeamCity управлять тем, какие именно тесты и в какой сборке запускать. Например, development сборка может включать только @SmallTest’ы, а production все вместе. Но, к сожалению, maven-android-plugin позволяет указывать только один scope при выборе тестов, т.е. мы можем выбрать либо small, либо medium, либо large, но не small и medium. См. testSize

Ну и по желанию можно добавить obfuscating с помощью ProGuard

Ссылки

И, как обычно, в конце немного ссылок на тему сборки Android проектов Maven'ом

  • Maven Tutorial
  • Android-maven-plugin: Getting Started Plugin Doc
  • Maven: The Complete Reference from Sonatype. Глава про Android
  • Maven Android Archetypes Готовый набор андроид архитайпов
  • Android-maven-plugin Samples. Содержит кучу примеров, включая NDK и библиотеки
  • Gaug.es Типичное приложение типа «веб-клиент», сделано очень качественно, можно посмотреть не только настройки maven'a
  • GitHub for Android Ну и официальное приложение клиент для GitHub, собирается тоже maven'ом

Автор: TheDimasig

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


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