Pull to refresh

Travis CI: автоматическая загрузка собранных модулей на GitHub

Reading time 4 min
Views 15K


В этой очень небольшой заметке я расскажу об очень небольшом усовершенствовании процесса автоматической сборки приложения в Travis CI. Я это проделал на примере Андроид-приложения, но, естественно, это будет работать и для других языков. Постановка задачи очень проста — участники сообщества попросили автоматически собирать в выкладывать приложение после каждого коммита в репозитории на GitHub. То есть речь идёт не о сборке фиксированных версий, а именно о «ежедневных» сборках, которые можно сразу же установить и тестировать, не дожидаясь официальной версии. Я, как разработчик, подобную заинтересованность могу только приветствовать, так как это сильно повышает качество обратной связи. Реализация этого процесса очень проста, только штатные средства GitHub и Travis CI, никакой магии. Так что я до сих пор сомневаюсь, стоит ли вообще о таком писать и отвлекать уважаемых хаброжителей от более серьёзных тем. Но если кто заинтересовался — прошу под кат.


Я, как разработчик под Android, с интересом и удовольствием мониторю некоторые Open Source проекты на GitHub, которые прошли успешное испытание временем и активно развиваются, например: good-weather, AFWall+, Timber, Pedometer, AmazeFileManager, ConnectBot, K-9 Mail.


У всех этих репозиториев есть два общих момента — в них настроена автоматическая сборка приложения на сервере Travis CI после каждого коммита, и результаты этой автоматической сборки так и остаются на сервере Travis CI, то есть собранное проложение просто удаляется. Я же, как уже сказал во введении, хочу извлечь пользу из этого собранного APK-файла и поместить его обратно в GitHub репозиторий, чтобы он был сразу же доступен участникам сообщества для тестирования.


Travis CI предоставляет штатный метод загрузки собранного приложения на GitHub, но он предназначен для работы с тегами, то есть эта сборка, во первых, запускается при создании нового тега в GitHub-репозитории, и, во-вторых, позволяет загрузить APK-файл только в раздел GitHub Releases, но не в ветку репозитория. Так как создавать тег для каждого коммита — это, на мой взгляд, насилие над здравым смыслом, то этот метод я отбросил как несоответствующий духу и сути задачи.


Командный файл Travis CI (.travis.yml), расположенный в корне репозитория, имеет простую структуру:


language: android

jdk: oraclejdk8

android:
  components:
  - platform-tools
  - tools
  - build-tools-25.0.2
  - android-25
  - extra-android-m2repository

branches:
  only:
  - master

install:
  - chmod +x ./gradlew

script: ./gradlew clean assembleDebug

notifications:
  email:
    on_success: change
    on_failure: always

Этот скрипт выполняется на виртуальной машине в корне git-репозитория, который клонирован в т.н. "detached HEAD" режиме, то есть не позволяет напрямую закоммитить что-либо в мастер-ветку удалённого (то есть оригинального) GitHub-репозитория.
Если внимательно посмотреть лог выполнения этого скрипта на виртуальной мишине, то в самом начале (секция git скрипта, которая в данном примере не сконфигурирована) Travis делает вот что:


$ git clone --depth=50 --branch=master https://github.com/user/repo.git user/repo
Cloning into 'user/repo'...
$ cd  user/repo
$ git checkout -qf d7d29a59cef70bfce87dc4779e5cdc1e6356313a

Именно git checkout -qf и переводит локальную ветку в "detached HEAD" режим.
После того, как секция script отработала (в моём примере ./gradlew clean assembleDebug), и в директории ./app/build/outputs/apk появился сгенерированный APK-файл, вызывается секция after_success, где можно средствами Git закоммитить этот файл. Вопрос только, куда?


Есть несколько вариантов.


1) Можно использовать GitHub-Pages и помещать APK-файл туда, то есть коммитить в ветку gh-pages. Основной минус такого подхода в том, что GitHub-Pages рассчитаны на конечных пользователей, которые должны загружать приложение из официальных магазинов. Участники сообщества работают всё же с самим репозиторием, а не с GitHub-Pages. Поэтому я такой вариант не рассматриваю.


2) Можно коммитить обратно в мастер-ветку GitHub-репозитория, например, в папку autobuild. В этом случае, нужно деактивировать "detached HEAD", закоммитить файл, авторизоваться в удалённом репозитории и выполнить push.


install:
  - git checkout master
  - chmod +x ./autobuild/push-apk.sh
after_success:
  - ./autobuild/push-apk.sh

где push-apk.sh выглядит так:


#!/bin/sh
mv ./app/build/outputs/apk/snapshot.apk ./autobuild/
git config --global user.email "travis@travis-ci.org"
git config --global user.name "Travis CI"
git remote add origin-master https://${AUTOBUILD_TOKEN}@github.com/user/repo > /dev/null 2>&1
git add ./autobuild/snapshot.apk
# We don’t want to run a build for a this commit in order to avoid circular builds: 
# add [ci skip] to the git commit message
git commit --message "Snapshot autobuild N.$TRAVIS_BUILD_NUMBER [ci skip]"
git push origin-master

В данном варианте после каждого коммита в мастер-ветке GitHub-репозитория Travis будет делать ещё один коммит, где файл snapshot.apk будет также помещён в мастер-ветку. С одной стороны, удобно, что все в одном месте. С другой, этот файл также нужно постоянно синхронизировать в локальных репозиториях, что не очень удобно для разработчиков.


3) Посте всех экспериментов мне больше всего понравился третий вариант. В репозитории создаётся ветка autobuild, но из неё удаляются все файлы и директории за исключением папки autobuild. Этот огрызок полноценной веткой не является, так как её нельзя синхронизировать с мастер-веткой. Скрипт push-apk.sh будет выглядеть в этом случае так:


#!/bin/sh

# Checkout autobuild branch
cd ..
git clone https://github.com/user/repo.git --branch autobuild --single-branch repo_autobuild
cd repo_autobuild

# Copy newly created APK into the target directory
mv ../repo/app/build/outputs/apk/snapshot.apk ./autobuild

# Setup git for commit and push
git config --global user.email "travis@travis-ci.org"
git config --global user.name "Travis CI"
git remote add origin-master https://${AUTOBUILD_TOKEN}@github.com/user/repo > /dev/null 2>&1
git add ./autobuild/snapshot.apk

# We don’t want to run a build for a this commit in order to avoid circular builds: 
# add [ci skip] to the git commit message
git commit --message "Snapshot autobuild N.$TRAVIS_BUILD_NUMBER [ci skip]"
git push origin-master

Последняя пара слов про авторизацию. За неё отвечает переменная окружения AUTOBUILD_TOKEN. Эта переменная задаётся в разделе


env:
  global:
    secure: 

Данный раздел содержит зашифрованный персональный ключ, который нужно сгенерировать на странице Personal access tokens. После чего он шифруется и добавляется в файл .travis.yml с помощью утилиты travis:


sudo gem install travis
echo AUTOBUILD_TOKEN=<Personal access token> | travis encrypt --add -r user/repo

Вот такое небольшое усовершенствование. Кому интересно, можно посмотреть рабочий вариант в этом репозитории.
Удачного всем прогрева серверов непрерывной интеграции!

Tags:
Hubs:
+3
Comments 12
Comments Comments 12

Articles