“Твою ж мать, какая же это хтонь!”. Примерно так можно было охарактеризовать все наши инфраструктурные скрипты до недавнего времени. Нужно было что-то менять, и мы сделали это.
Меня зовут Павел Стрельченко, я – Android-разработчик компании hh. Я расскажу вам как эволюционировали наши CI скрипты на протяжении трех лет, с какими проблемами мы сталкивались, как анализировали их и пытались изменить, а также что вообще делали и к чему в итоге пришли.
Это текстовая расшифровка выпуска нашего влога, поэтому если вам удобнее смотреть, а не читать, добро пожаловать на наш Youtube-канал. В статью получилось добавить множество дополнительных ссылок, так что можно почитать ещё и их.
Очень важный дисклеймер
После просмотра видео или чтения этой статьи может сложиться впечатление, что настройка CI для сборок Android-приложений — невероятно сложное и трудное занятие, с которым вам может помочь утилита fastlane.
Это неправильное ощущение, гоните его прочь.
Во-первых, настроить CI в 80-90% случаев — это довольно просто, благо в 2021 году уже есть множество инструментов, которые максимально упрощают этот процесс (вы можете посмотреть в сторону Jenkins / CircleCI, Github Actions, и так далее).
Во-вторых, fastlane — не панацея от всего, и команда hh пока не готова рекомендовать его использовать. Важно помнить, что перед использованием любого инструмента необходимо проводить его изучение, чтобы понимать, насколько он подойдет в каждом конкретном случае, оценить все риски и взвесить стоимость его адаптации.
Не верьте никому на слово, перепроверяйте информацию сами.
Всем стабильного CI.
Как вам жилось без 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-тесты – это классно. Чем раньше вы их затащите на регулярной основе, тем лучше. Но помните, что для них понадобится подготовить инфраструктуру, принять множество решений про инструменты, обсудить процесс работы и так далее.
Так прошло еще полгода.
Я знаю, что вы делали 2 года назад
За полгода в команде произошли некоторые изменения и накопилось несколько проблем:
Сменились люди, работавшие над инфраструктурными скриптами — Из-за низкого 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), то нас бы ждал очень неприятный сюрприз и большое количество дополнительной работы;
Что за CI-переменные?
В нескольких скриптах нам нужно было использовать название текущей ветки, плана, на котором запущена сборка и т.д., в других скриптах нам могли понадобиться значения номера сборки, пути к артефактам, и многое другое.
Не вся работа выполняется внутри 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 вот так:
sh ci/run_in_docker.sh "some_command_for_execution"
Конвертация CI-переменных в ENV — Проблему сильной привязки наших инфраструктурных скриптов к нашему CI-серверу можно было решить описанием списка всех CI-переменных в одном месте. Мы пробросили их внутрь Docker-контейнера как ENV-переменные, после чего используем внутри всех скриптов только их. Если мы захотим когда-нибудь уйти от Bamboo, нам будет достаточно поменять только один файл, внутри которого используются эти Bamboo-переменные;
Проброс CI-переменных
А в Docker это можно пробросить вот так:
docker run \
--env-file <(echo "$envs" | grep -E '.+=.+') \
...
Это пробросит только те ENV-ы, где значение после знака '=' не является пустой строкой.
С остальными проблемами, вроде отсутствия версионирования скриптов, зоопарка языков, трудностей обновления скриптов и отсутствия review, нам мог бы помочь такой подход, как Infrastructure as Code, то есть, описание инфраструктуры как какого-то кода.
Полностью честный Infrastructure as Code мы вряд ли осилили бы. Однако по максимуму перевести инфраструктуру в код мы могли себе позволить. И нам оставалось только выбрать инструмент: как именно мы будем реализовывать все эти инфраструктурные вещи внутри нашей кодовой базы.
Из-за леса, из-за гор показал мужик QA
И тут появились они. Наши тестировщики, наш великолепный 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: какие шаги где используются, какие из них можно переиспользовать, какие аргументы подаются на вход, что нам надо запускать параллельно, а что можно последовательно и так далее.
Картинки из Miro
Мы начали изменять наши скрипты, начиная с плана, связанного с 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 = '[%Y,%m,%d,%H,%M,%S,0]'
private_class_method def self.current_time_for_fixtures(plus_days: nil)
time = Time.now
time -= (24 * 60 * 60 * plus_days) unless plus_days.nil?
time.strftime(FULL_DATE_TIME_FORMAT).split(',').map(&:to_i)
end
Из-за ошибочного формата FULL_DATE_TIME_FORMAT конвертация происходила неправильно, и результатом функции был массив с нулями. Это поломало нам кучу UI-тестов, случались какие-то дикие флакования. По-хорошему, раз функция не смогла что-то сконвертировать, она должна была выкинуть какое-то исключение, но нет, Ruby интерпретировал ошибку как 0.
Вот так работает правильно:
FULL_DATE_TIME_FORMAT = '%Y,%m,%d,%H,%M,%S,0'
private_class_method def self.current_time_for_fixtures(plus_days: nil)
time = Time.now
time -= (24 * 60 * 60 * plus_days) unless plus_days.nil?
time.strftime(date_format).split(',').map do |arg|
Integer(arg, 10)
end
end
Кратко — боль.
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-планов в виде высокоуровневых функций — Такие функции легко читать и поддерживать;
В качестве примера
desc 'Run checks for PR: static analysis + unit tests + app build'
lane :checks_for_pr do
add_jira_link_to_pr(
pull_path: HeadHunter::GitHub.pulls_path(HeadHunter::Platform::ANDROID)
)
static_analysis
build_and_run_unit_tests
approve_if_possible
end
# А дальше — отдельные функции add_jira_link_to_pr / static_analysis / etc
Перевели практически все CI-планы на использование fastlane — Собираемся добивать остальные, потому что нам это понравилось.
Ушли от зоопарка языков — У нас остался только bash в виде одного скрипта + Ruby;
Конфигурация CI-планов теперь лежит в исходном коде репозитория — Теперь эти скрипты проходят code review. Более того, они могут проходить ревью и у Android-разработчиков, и у iOS-команды;
Получилось переиспользовать общую логику с iOS-командой — Мы вынесли общие скрипты в отдельный репозиторий, обе команды могут ими пользоваться.
Подводим итоги всего рассказа
Какие решения мы считаем неудачными за всю историю нашей эволюции?
Отсутствие CI — Это, пожалуй, самая большая проблема и ошибка, которую мы допустили на старте. То есть, чем раньше вы запустите у себя CI, который начнет на регулярной основе собирать ваше приложение, тестировать его, проверять на валидность с помощью скриптов статического анализа, тем лучше. Не затягивайте с этим, потому что это резко улучшит качество ваших приложений и кодовой базы;
Маленький bus-фактор в вопросах инфраструктуры — Если вы потеряете сакральные знания о разных частях приложения, вам потребуется много времени, чтобы их восстановить. Распространяйте знания, устраивайте демо, пишите wiki;
Зоопарк языков в инфраструктурных скриптах – это зло — Желательно сводить к минимуму количество инструментов, которые вы используете. Меньше инструментов = проще разобраться..
Описание CI-задач только в UI-интерфейсе CI-сервера — Из этого вытекает множество проблем, о которых я уже писал выше.
Какие решения мы считаем удачными:
Подход Infrastructure as Code — Этот подход позволяет версионировать ваши инфраструктурные скрипты, позволяет их ревьювить, легко менять, обновлять, создавать новые шаги;
Переиспользование общих скриптов между платформами — Это может подойти далеко не каждой команде, но здорово, когда получается переиспользовать общую инфраструктурную логику;
Увеличение bus-фактора — Чем больше людей знает про вашу инфраструктуру и всякие тонкости, тем лучше.
На этом у меня всё. Если у вас будут какие-то вопросы, пожелания, критика оставляйте их в комментариях.