“Твою ж мать, какая же это хтонь!”. Примерно так можно было охарактеризовать все наши инфраструктурные скрипты до недавнего времени. Нужно было что-то менять, и мы сделали это.  Меня зовут Павел Стрельченко, я – Android-разработчик компании hh. Я расскажу вам как эволюционировали наши CI скрипты на протяжении трех лет, с какими проблемами мы сталкивались, как анализировали их и пытались изменить, а также что вообще делали и к чему в итоге пришли.  Это текстовая расшифровка выпуска нашего влога, поэтому если вам удобнее смотреть, а не читать, добро пожаловать на наш Youtube-канал. В статью получилось добавить множество дополнительных ссылок, так что можно почитать ещё и их.  После просмотра видео или чтения этой статьи может сложиться впечатление, что настройка CI для сборок Android-приложений — невероятно сложное и трудное занятие, с которым вам может помочь утилита fastlane.  Это неправильное ощущение, гоните его прочь. Во-первых, настроить CI в 80-90% случаев — это довольно просто, благо в 2021 году уже есть множество инструментов, которые максимально упрощают этот процесс (вы можете посмотреть в сторону Jenkins / CircleCI, Github Actions, и так далее).  Во-вторых, fastlane — не панацея от всего, и команда hh пока не готова рекомендовать его использовать. Важно помнить, что перед использованием любого инструмента необходимо проводить его изучение, чтобы понимать, насколько он подойдет в каждом конкретном случае, оценить все риски и взвесить стоимость его адаптации.  Не верьте никому на слово, перепроверяйте информацию сами.  Всем стабильного CI. Начнем с истории трехлетней давности. Об этом периоде времени хорошо рассказал Саша Блинов в своем видео про удивительную историю рефакторинга. В те далекие времена одновременно с изменением кодовой базы происходили и инфраструктурные изменения. На тот момент у нас вообще не было инфраструктурных скриптов. Роль билд-сервера взял на себя специальный разработчик по имени Антон. По требованию тестировщиков он в нужные моменты собирал релизные АПК, дебажные АПК и отдавал их тестировщикам на тестирование. Разумеется, ни о каких регулярных сборках речи вообще ни шло. У нас не запускались на регулярной основе никакие тесты. Следовательно, любой коммит в develop или в мастер-ветку мог разломать абсолютно всё. Во-вторых, у нас не запускались никакие проверки статического анализа. Поэтому code style не соблюдался по всему проекту. Например, в истории коммитов нашего проекта можно было обнаружить специальные коммиты, которые применяли в очередной раз принятый код style на всю кодовую базу. Такое себе удовольствие. Отсутствие чётко зафиксированного код-стайла в настройках IDE часто приводило к необходимости что-то подправить в нескольких файлах внутри коммита. Проходило несколько дней и править приходилось уже сразу большой объём файлов. Мы решили, что больше так жить нельзя, пора заводить CI.  Настроили build-машины — Мы пошли к команде инфраструктуры hh и заказали у них специальные build-машины с нужной нам конфигурацией. Нам нужно было не так много: Java да Android SDK; Сформулировали типы сборок — Мы сделали специальную табличку, в которую выписали название сборки, критерии, по которым нужно запускать тот или иной флоу, что конкретно надо запускать в рамках прогона. В таблице мы подробно описали: зачем нам нужен определённый тип сборки, по какому триггеру он должен отрабатывать, надо ли внутри него прогонять Unit-тесты, UI-тесты, стат. анализ, какие приложения каких build-типов он должен собирать. И таким образом, у нас получилось четыре основных типа сборки: Сборка pull request-а (PR) — разработчик создает PR, и мы запускаем сборку на нашем CI-сервере. Эта сборка проверяет компиляцию приложений, прогоняет стат. анализ и Unit-тесты; Ночная сборка (Night) — это регулярные ночные сборки. Наши разработчики ведут работу надо фичами в отдельных фиче-ветках. И эти фиче-ветки мы будем собирать каждый день до тех пор, пока они не будет смерждены в develop. Здесь мы запускаем прогоны Unit-тестов, UI-тестов, проверяем компиляцию всех приложений. Короче говоря, максимально собирающий билд; Сборка PR to Develop — сборка запускается при попытке merge-а в develop. Когда разработчик заканчивает разработку своей фичи, он пытается ее смерджить в develop. В этот момент мы должны хорошенько проверить эту фичу: прогоняются все тесты, прогоняется статический анализ и компиляция всего и вся; Custom build — это билд, который можно было настроить абсолютно для всего. Потенциально он мог собирать и релизные версии, и дебажные версии, и различные флейворы, а также прогонять или не прогонять Unit-тесты etc. Как мы реализовали эти четыре flow? Мы написали один-единственный большой Gradle-скрипт, который, по нашей задумке, должен был уметь делать всё, учитывать любые шаги, запускать какие угодно тесты и так далее. То есть реализовывал бы Custom build-план. На один экран весь скрипт не влезет, в этом же файле были размещены все нужные ему Groovy-классы и множество утилит. И с помощью различных переменных с нашего CI-сервера мы настраивали включение тех или иных особенностей билда. Благодаря этому единому скрипту мы настроили все четыре наших flow. is_crashlytics_release — флажок, от которого зависела отправка готовой сборки в Crashlytics Beta (скупая слеза ностальгии); step_app_names — тут можно было указать список приложений для сборки, в данном плане было указано только соискательское; step_build_types — можно было указать через запятую все необходимые типы сборок: Debug, PreRelease, Release; step_extra — тут можно было добавить дополнительный запуск какой-нибудь gradle-задачи; step_to_fabric — уже неясно, почему тут стояло true, ведь этот шаг требовал проброса пар вида “AppName:BuildType” для сборки дополнительных тестовых приложений; step_ui_tests — надо ли запускать UI-тесты; step_unit_tests — указание gradle-задачи для прогона тестов именно для данной сборки; Если кратко, кастомизировалось всё довольно гибко, пусть и не очень консистентно. Ура, у нас заработал CI!  Жизнь без CI – это боль. И чем раньше вы его включите и начнете на регулярной основе запускать тесты, прогонять статический анализ, тем раньше качество вашего приложения и кодовой базы резко улучшится.  Так прошло полгода. За полгода использования и периодических фиксов нашего супер-скрипта мы поняли, что на самом деле он не так хорош, как нам бы того хотелось.  Скрипт был длинной простыней Groovy-кода — Он находился в одном-единственном Gradle-файле. Большую часть скрипта занимала огромная Gradle-таска, которая была написана совершенно без учета каких-то best practices Gradle-а; То, что скрипт был написан на Groovy на самом деле не так уж и плохо. Потому как в те времена про Kotlin Scripts мало кто знал, и сам KTS находился в зачаточном состоянии.  Мы не сразу поняли, что делала gradle-таска А она, внимание: Формировала текст bash-команд; Записывала их в специальный shell-файлик; А потом этот специальный shell-файлик запускала. То есть у нас происходило так: на CI мы запускали Gradle-таску. Она запускала bash, который… снова запускал Gradle.  В видео я вставил совсем не ту шутейку, которую хотел, так что вставлю её в статью. В какой-то момент мы обнаружили, что наши сборки на PR-ах идут примерно по часу. А билд сканы мы посмотреть толком не можем — из-за того, что запускаемая gradle-задача была странно написана.  Непонятки со статическим анализом — Эти скрипты запускались через неведомые тропы, поэтому мы не могли нормально обновить тот же detekt; Добавьте к вышесказанному то, что одновременно со всем этим довольно интенсивно зашевелилась и разработка соискательского приложения. Из-за огромного количества новых фич и рефакторингов нам потребовалось ускорить прохождение регрессов. Постарались уйти от использования супер-кастомного скрипта — Для этого мы вынесли генерацию bash-команд просто в bash-скрипты, которые мы сконфигурировали с помощью UI нашего CI-сервера. На Bamboo так можно. Мы пока вообще не думали про проблемы, заключенные внутри этих bash-скриптов, мы их просто вынесли и начали запускать на регулярной основе; В Bamboo есть специальный тип задач — Script Configuration, вот в него мы и скопировали нужные скрипты. Начали настраивать прогон наших UI-тестов — попросили помочь с этим наших коллег из инфраструктурных команд. Они подняли кластер Kubernetes, настроили для нас запуск эмуляторов (про всё это можно посмотреть отдельный доклад). Со своей стороны мы описали небольшой Docker-контейнер, внутрь которого положили java, Android SDK и утилиту Marathon, с помощью которой собирались запускать наши UI-тесты. Что у нас получилось сделать? Перестали использовать скрипт Custom Build-а на всех flow, кроме релизного — Скрипт до недавнего времени всё ещё жил в нашей кодовой базе, а избавились от него мы буквально месяц назад; Логика инфраструктурных скриптов стала чуть понятнее — Мы визуализировали ее в UI-интерфейсе нашего CI-сервера, разбили на шаги, и поэтому жить стало чуть проще; Исправили проблему, связанную с прогоном наших PR — Они больше уже не шли по часу, плюс мы восстановили работу build scan-ов; Регулярные прогоны UI-тестов ускорили регрессы — Мы получили возможность чаще релизиться. Мы научились тому, что все-таки не стоит писать эти инфраструктурные скрипты на скорую руку. Лучше разобраться в используемой технологии, чтобы не получалось, что “gradle запускает bash, который запускает gradle”.  А ещё, UI-тесты – это классно. Чем раньше вы их затащите на регулярной основе, тем лучше. Но помните, что для них понадобится подготовить инфраструктуру, принять множество решений про инструменты, обсудить процесс работы и так далее. Так прошло еще полгода. За полгода в команде произошли некоторые изменения и накопилось несколько проблем: Сменились люди, работавшие над инфраструктурными скриптами — Из-за низкого bus-фактор-а, нам пришлось заново разбираться во всех build-скриптах; Долго не рефакторили bash-скрипты — При этом мы постепенно расширяли их функциональность, они становились всё сложнее и сложнее; Дублирование логики в скриптах — Мы вскоре заметили, что скрипты дублировали друг друга почти в каждом flow, который мы запускали на CI. Они отличались в мелочах: где-то нужен был специальный gradle-флаг, где-то необходимо было запускать тесты, где-то нет. Изменения или обновления, которые мы хотели внести в эти все скрипты, приходилось дублировать почти во всех вкладках настроек разных планов и flow; А теперь представьте, что таких вкладок не две, а около десяти. Изменения в CI-скриптах раскатывались сразу на всех — Конфигурация планов CI была единой для всех разработчиков, поэтому приходилось либо создавать какой-то промежуточный план для тестов обновлений, либо выдумывать специальные трюки; В логах на CI было трудно разобраться — Наш CI-сервер, конечно, пишет в лог, когда запущен тот или иной шаг внутри нашего билда. Но в потоке логов, которые занимали несколько мегобайт, эти строчки легко было пропустить. Иногда менялись настройки build-машины — Иногда команда инфраструктуры что-то меняла на уже настроенных машинах, и там исчезали нужные нам для запусков утилиты. Попробовали решить задачу обновления sh-скриптов — Для этого мы вытащили их из нашего CI в отдельные sh-файлы и добавили внутрь репозитория. Теперь CI запускал не какой-то зафиксированный для всех sh-скрипт, а запускал тот скрипт, который находился внутри нашего репозитория. Бонусом к этому мы получили возможность изменять кусочек любого sh-скрипта и тестировать его внутри определённой ветки. Да, добавлять новые шаги на CI по-прежнему было нельзя, потому что это сразу раскатывалось на всех. Однако bash-скрипт мог запустить какой-нибудь другой bash-скрипт, и таким образом мы могли проверить новые шаги; Много. И многие из них ещё и дублировались раньше в разных планах. Немного упростили чтение логов — Мы добавили к нашим скриптам небольшую утилитку, которая на вход принимала название скрипта, который нужно запустить и его аргументы. При помощи специальной конструкции в bash мы понимали, какой именно скрипт хотим запустить, писали понятное описание, добавляли кучу специальных символов, чтобы их можно было легко отличить в логах. И дальше уже запускали нужный нам скрипт. Если вам когда-нибудь в жизни было интересно, как выглядит выражение “switch-case” на bash-е, то вот оно: Перенесли часть задач в Docker — Для уменьшения рисков, связанных с переносом сборок на различные машины, мы начали потихонечку задумываться над тем, чтобы перенести выполнение наших задач, которые пока что выполнялись на билд-машинах, внутрь Docker-контейнера. В частности, например, сборку APK. Нужно увеличивать bus-фактор — Расширяйте экспертизу всех разработчиков в работе инфраструктурных скриптов, чтобы не терять важную информацию при уходе людей. С тех пор мы проводим демки, пишем статьи в wiki, чтобы знания, которые хранятся в головах разработчиков, хранились еще где-нибудь; Docker упрощает перенос инфраструктуры — Сейчас мы в любой момент можем перенести наши сборки с одной машины на другую, главное чтобы там были bash и Docker; Не пишите сложные конструкции на bash-е — Объемные функции и всякие switch-case-ы на bash-е выглядят сложно и трудно поддерживаются. В таком состоянии наша кодовая база жила почти до сегодняшнего дня. Сейчас мы переместимся чуть ближе к маю 2021 года. Мы уже год как возобновили активную фазу разработки нашего нового работодательского приложения. До этого оно почти год находилось в заморозке и никаким образом не запускалось на CI. А ещё мы продолжали изменять функциональность существующих инфраструктурных скриптов без их глобального рефакторинга. Да, мы понимали, что там есть проблемы. Мы знали, как можно что-нибудь улучшить. Но повода как-то не представлялось. И ежики кололись, бодались, но продолжали жрать кактусы. Я не мог ему отказать. Запутанные bash-скрипты — Нам сильно аукнулось то, что мы переносили bash-скрипты из нашего старого Groovy-скрипта без каких-либо изменений. Мы особо не вчитывались в логику bash-скриптов, когда переносили их на Bamboo, мы не вчитывались в них и когда переносили их обратно в репозиторий. Это сыграло злую шутку с нами, потому что в какой-то момент мы поняли, что эти скрипты очень запутаны. Один скрипт вызывает другой, второй вызывает третий, и все это как-то непоследовательно. Аргументы передаются из скрипта в скрипт и конвертируются непонятно во что; Каждый второй скрипт использует переменные Bamboo — Почему это плохо? Потому что мы оставались сильно привязаны к нашему CI-серверу. И если бы мы, допустим, захотели в это время перейти на какой-нибудь другой CI-сервер (ну скажем, Jenkins), то нас бы ждал очень неприятный сюрприз и большое количество дополнительной работы; В нескольких скриптах нам нужно было использовать название текущей ветки, плана, на котором запущена сборка и т.д., в других скриптах нам могли понадобиться значения номера сборки, пути к артефактам, и многое другое. Не вся работа выполняется внутри Docker-контейнера — На дворе 2021 год, а мы все еще не перевели всю работу наших инфраструктурных скриптов внутрь Docker-контейнера. Часть скриптов выполнялась по-прежнему на билд-машинах, и она страдала от переноса сборок на те или иные машины. Часть выполнялась внутри Docker-контейнера; Зоопарк языков программирования в инфраструктурных скриптах — За три года существования этих инфраструктурных скриптов у нас сформировался целый зоопарк языков, на которых мы писали эти скрипты. Там был и Groovy, и Kotlin, и Bash, и Python, и даже Go! “Посторонние скрипты” — часть инфраструктурных скриптов находилась в совершенно постороннем репозитории, который был никак не связан с мобильными разработчиками. И когда эти скрипты ломались, мы даже не знали, куда идти, кого спрашивать и как вообще фиксить проблемы, с ними связанные. Добавьте ко всем проблемам еще и то, что все наши инфраструктурные скрипты по-прежнему настраивались через UI-интерфейс нашего CI-сервера. Почему это плохо? Потому что эта конфигурация планов никаким образом не версионировалась. И она была единой для всех. Из этого вытекал целый океан новых проблем. Усложнялось добавление новых шагов — нам приходилось либо терпеть и страдать от каких-то падений инфраструктурных скриптов, либо ждать, когда скрипт с фиксом будет подмержен в develop. Был и третий вариант: проверять, существует ли определенный файлик внутри нашего репозитория. А если существует, то запускать его. Это очень сильно осложняло работу по обновлению скриптов; Дублирование шагов между планами Bamboo — Для переиспользования шагов описанных flow нам приходилось дублировать описания этих шагов между настройками плана на Bamboo. Допустим, один план запускал сборку APK, и другой план его запускал. Нам приходилось дублировать все эти запуски различных скриптов между планами; Отсутствие код-ревью для настроек планов — а это значит, что их качество может сильно страдать; Не каждый разработчик может поправить настройки — потому что доступ к конфигурации планов на CI имеет только ряд избранных, и только они могут что-то исправлять; Нельзя переиспользовать общую логику между платформами — а такая логика была. Например, отправка нотификаций в Slack, проставление ссылок в Jira, Github. Но так как скрипты конфигурировались на Bamboo, мы не могли это переиспользовать. Всё это образовало ту самую ХТОНЬ. Нужно было от этого уходить, но нам не хватало какого-то триггера, повода всё изменить. Пока триггер не появлялся на горизонте, мы всё равно планировали наши улучшения.  Перенос инструментов в Docker — Вопрос запуска тех или иных утилит вне Docker-а можно было решить переносом всех инструментов, которые мы используем, и перемещением их в Docker-контейнер. Мы описали Docker-файл, внутри которого установили всё, что нам нужно: и Java, и Android SDK, и Marathon, и Allure, и Python, и Gradle-Profiler и массу всего другого; Единая точка входа в Docker — Мы описали специальный bash-скриптик, который сегодня стал новой единой точкой входа для наших инфраструктурных скриптов. Этот bash-скрипт просто запускает Docker-контейнер и передает управление в него, чтобы нужные нам команды и утилиты вызывались внутри контекста Docker-контейнера; Написав такой скрипт, можно использовать его на CI вот так: Конвертация CI-переменных в ENV — Проблему сильной привязки наших инфраструктурных скриптов к нашему CI-серверу можно было решить описанием списка всех CI-переменных в одном месте. Мы пробросили их внутрь Docker-контейнера как ENV-переменные, после чего используем внутри всех скриптов только их. Если мы захотим когда-нибудь уйти от Bamboo, нам будет достаточно поменять только один файл, внутри которого используются эти Bamboo-переменные; А в Docker это можно пробросить вот так: Это пробросит только те ENV-ы, где значение после знака '=' не является пустой строкой. С остальными проблемами, вроде отсутствия версионирования скриптов, зоопарка языков, трудностей обновления скриптов и отсутствия review, нам мог бы помочь такой подход, как Infrastructure as Code, то есть, описание инфраструктуры как какого-то кода. Полностью честный Infrastructure as Code мы вряд ли осилили бы. Однако по максимуму перевести инфраструктуру в код мы могли себе позволить. И нам оставалось только выбрать инструмент: как именно мы будем реализовывать все эти инфраструктурные вещи внутри нашей кодовой базы. И тут появились они. Наши тестировщики, наш великолепный QA-отдел. В одном из выпусков “Охэхэнных историй” наш тестировщик Даня уже рассказывал об автоматизации нашего релизного флоу. В связи с этим в кодовой базе Android-приложения появились такие инструменты как Ruby и fastlane. И наши тестировщики были настроены серьезно. Им не хотелось переписывать уже работающие под iOS скрипты на какие-то другие инструменты, поэтому они топили за использование fastlane. Несмотря на то, что для Android-разработчиков более привычными инструментами являются Kotlin, Gradle-скрипты и Bash, мы все-таки решили попробовать и дать шанс fastlane с Ruby. Сможем отказаться от зоопарка языков — Потому что всё, что может Python и Groovy, в целом может и Ruby; Возможность использовать наработки iOS-команды — iOS-разработчики у нас уже имеют большую экспертизу в использовании Ruby и Fastlane. Поэтому мы могли, использовать их опыт для review и созданные ими наработки для наших автоматизаций; Обширная экосистема Ruby и fastlane — У Ruby и fastlane есть довольно обширная экосистема, которая используется в задачах, связанных с continuous integration и continuous delivery. Мы провели небольшое исследование и поняли, что в целом все задачи, которые нам нужны, мы можем реализовать через Ruby и fastlane, отказавшись от использования старых скриптов; И вообще, в ходе каких-нибудь революционных изменений, типа перехода на fastlane и Ruby, мы могли разом исправить и те проблемы, о которых я уже упоминал. Мы подумали: «Почему бы и нет?». Да, мы понимали все риски. Android-разработчикам совершенно непривычно работать с Ruby. И перевод вообще всех скриптов на новые рельсы – очень долгое и непростое дело. Однако объем накопившихся проблем перевесил все эти риски, поэтому мы начали нашу подготовку к изменениям. На этот раз мы решили подойти к изменению инфраструктурных скриптов системно, поэтому мы создали специальную Miro-доску, в которую выписали всю имеющуюся у нас информацию про наши CI flow: какие шаги где используются, какие из них можно переиспользовать, какие аргументы подаются на вход, что нам надо запускать параллельно, а что можно последовательно и так далее. Мы начали изменять наши скрипты, начиная с плана, связанного с pull request. Так как мы твердо решили запускать все эти действия на CI внутри Docker-контейнера, соответственно, мы туда добавили и инструменты, которые помогут нам запускать Ruby и fastlane (rvm / bundler). И после этого мы начали переводить существующие инфраструктурные скрипты на язык Ruby. Попутно пытались переиспользовать уже имеющиеся у iOS-команды утилиты.  Для этого мы создали отдельный репозиторий, который оформили как fastlane-плагин, и начали выносить туда общий код. Вынести получилось довольно много: работу с Jira, утилиты для git-а, общие константы и модели, да еще и код для нашего релизного флоу. Структура репозитория была создана автоматически через fastlane new_plugin  После всего этого вы бы точно спросили меня: «Неужели всё так хорошо и здорово получилось с этими Ruby и fastlane?». Кажется, стоит рассказать и про наши страдания. Я вас не разочарую, потому что, разумеется, их у нас было в достатке. Android-разработчики редко работают с Ruby — перестроить свой мозг джависта и человека, который работает с Kotlin, на такой язык довольно сложно. Исчезают привычные концепции: абстрактного класса в Ruby нет, интерфейсов а-ля Java тоже нет. Зато появляются некоторые другие интересные особенности языка, тот же duck typing. Динамическая типизация – это больно — у нас был довольно болючий пример, который мы обнаружили у себя только через месяц после добавления кода.  Мы написали скрипт, который должен был проверять наши тестовые стенды на их актуальность. Одним из его шагов было создание json-модельки и отправки её на сервер. При создании модели нужно было сконвертировать дату в массив чисел для нашего сервера.  Из-за ошибочного формата FULL_DATE_TIME_FORMAT конвертация происходила неправильно, и результатом функции был массив с нулями. Это поломало нам кучу UI-тестов, случались какие-то дикие флакования. По-хорошему, раз функция не смогла что-то сконвертировать, она должна была выкинуть какое-то исключение, но нет, Ruby интерпретировал ошибку как 0.  Вот так работает правильно: Кратко — боль. Android Studio не умеет работать с Ruby — Для работы с Ruby можно использовать VS Code (с плагинами VSCode Ruby, Ruby, ruby-rubocop, Ruby Solargraph), либо IntelliJ IDEA Ultimate (с плагином Ruby), либо RubyMine; Недостаточная поддержка fastlane в IDE — fastlane-специфика не поддерживается и можно нехило выхватить с этого. Однажды я полчаса искал ошибку, по которой у меня не вызывался fastlane Action, и оказалось, что у меня в классе Action не было суффикса Action. IDEA-инспекции тут бы могли спасти, но не спасли. Потому что их не было.  Проблемы с приватным общим репозиторием — Потратили много времени, прежде чем научились использовать написанный fastlane plugin на CI, были какие-то проблемы с пробросом специальной ENV-переменной access token для доступа к Github-репозиторию; Отсутствие общих правил написания Ruby-кода между платформами — Код-стайл мы легко расшарили с помощью Rubocop, а вот как мы именуем функции, как обобщаем нужный код, — таких правил у нас не было. iOS-коллеги писали код по-своему, мы по-своему. На review у нас постоянно были какие-то непонятки; Проблемы с запуском bash-команд из fastlane-а — У нас почему-то стабильно не срабатывала команда grep, но в итоге мы её переписали на Ruby, и всё стало хорошо. Несмотря на все трудности, переход на fastlane существенно улучшил наши скрипты инфраструктуры. Получилось описать flow наших CI-планов в виде высокоуровневых функций — Такие функции легко читать и поддерживать; Перевели практически все CI-планы на использование fastlane — Собираемся добивать остальные, потому что нам это понравилось. Ушли от зоопарка языков — У нас остался только bash в виде одного скрипта + Ruby; Конфигурация CI-планов теперь лежит в исходном коде репозитория — Теперь эти скрипты проходят code review. Более того, они могут проходить ревью и у Android-разработчиков, и у iOS-команды; Получилось переиспользовать общую логику с iOS-командой — Мы вынесли общие скрипты в отдельный репозиторий, обе команды могут ими пользоваться. Какие решения мы считаем неудачными за всю историю нашей эволюции? Отсутствие CI — Это, пожалуй, самая большая проблема и ошибка, которую мы допустили на старте. То есть, чем раньше вы запустите у себя CI, который начнет на регулярной основе собирать ваше приложение, тестировать его, проверять на валидность с помощью скриптов статического анализа, тем лучше. Не затягивайте с этим, потому что это резко улучшит качество ваших приложений и кодовой базы; Маленький bus-фактор в вопросах инфраструктуры — Если вы потеряете сакральные знания о разных частях приложения, вам потребуется много времени, чтобы их восстановить. Распространяйте знания, устраивайте демо, пишите wiki; Зоопарк языков в инфраструктурных скриптах – это зло — Желательно сводить к минимуму количество инструментов, которые вы используете. Меньше инструментов = проще разобраться.. Описание CI-задач только в UI-интерфейсе CI-сервера — Из этого вытекает множество проблем, о которых я уже писал выше. Какие решения мы считаем удачными: Подход Infrastructure as Code — Этот подход позволяет версионировать ваши инфраструктурные скрипты, позволяет их ревьювить, легко менять, обновлять, создавать новые шаги; Переиспользование общих скриптов между платформами — Это может подойти далеко не каждой команде, но здорово, когда получается переиспользовать общую инфраструктурную логику; Увеличение bus-фактора — Чем больше людей знает про вашу инфраструктуру и всякие тонкости, тем лучше. На этом у меня всё. Если у вас будут какие-то вопросы, пожелания, критика оставляйте их в комментариях.  Загадочная история одного рефакторинга || Охэхэнные истории Эволюция CI в Android || Охэхэнные истории Интервью: как устроена автоматизация мобильного тестирования в HH Стартовая точка для знакомства с fastlane Android-разработчик