Используем бесплатные возможности Github Actions для CI/CD на Flutter-проекте

    GitHub Actions — инструмент для автоматизации рутинных действий с репозиторием и подспорье при создании CI/CD для вашего проекта.

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

    Даю инструкцию для разработчиков Flutter-приложений: как c помощью GitHub Actions на каждый pull request запускать тесты и анализатор кода, билдить артефакт и деплоить его для тестирования в Firebase.



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

    Если в вашем рабочем проекте всё хорошо с CI/CD — вы счастливый человек.
    Может быть, вы работаете в стартапе и тщательно настраивали все пайплайны и хуки своими руками.

    Может быть и такое, что о вашем благополучии заботится целая DevOps-команда: радует вас каждый месяц новыми интеграциями, тающим на глазах билд-таймом и продвинутыми техниками деплоя сборок во все мыслимые и немыслимые места.

    Не важно. Главное, вы всегда уверены, что ваши сборки жизнеспособны, а сами вы при этом избавлены от большого количества очень скучных рутинных задач, мысли о которых всегда повергают разработчиков в тоску и уныние. Кстати, буду рад, если вы напишите в комментариях, когда в последний раз вы вручную меняли статус у задачи в Jira?

    Выход из зоны комфорта


    Куда выходят из зоны комфорта, а самое главное, зачем? Масса причин. Знакомый попросил помочь написать небольшое приложение для собственного бара, вы наконец-то нашли время для реализации пет-проекта своей мечты или решили зарелизить библиотеку, которая случайно родилась в рамках работы над проектом. Да наконец, просто вы с коллегой решили написать к воркшопу небольшой сэмпл проект.

    Готов поспорить, что в любом из сценариев ваше воодушевление от новых интересных задач быстро столкнётся с суровой реальностью разработки ПО в «безвоздушной среде» (да, в какой-то момент умный сборщик будет нужен вам как воздух).

    «CI/CD это сложно...»


    Что вы там обычно ещё себе в такие моменты говорите? «Я не разбираюсь в этом! Я просто пишу фронтенд/мобилку и ничего не знаю про эти ваши Дженкинсы!» А что, если я скажу, что вам ничего такого и не нужно знать?

    Да-да, вам достаточно уметь собрать ваш проект, пользуясь консольными командами, — и всё. Вы можете значительно упростить свою жизнь, даже если речь идёт о персональном маленьком проекте, а не гигантском многомодульном монстре, который уже с трудом переваривает IDE.

    Github Actions настолько прост, что даже ваша бабушка настроила бы его без особого труда.

    Тогда о чём пост?


    Если всё так просто, зачем тратить время на прочтение сего опуса? Отвечу маркированным списком:

    • Flutter. Мы настроим не абстрактный CI в вакууме. Мы сделаем такой пайплайн, с которым ваше Flutter-приложение будет чувствовать себя хорошо. Поэтому затронем некоторые специфичные моменты, касающиеся конкретного фреймворка
    • Деньги. Github Actions — очень доступный сервис. Вы получаете бесплатный безлимит для публичных репозиториев и 2 000 бесплатных минут в месяц для приватных (что немало). И всё-таки мы сделаем упор на то, чтобы реализовать нужные сценарии максимально эффективно при минимальных затратах.
    • Деплой. Flutter при компиляции чудесным образом превращается в Android и iOS сборки, которые нужно как-то подписывать и деплоить. Это совсем не простой этап, который, тем не менее, мы тоже реализуем в рамках этого поста.

    В cтатье мы сфокусируемся на оптимальной конфигурации CI/CD с учётом ограничений, которые существуют у приватного репозитория на бесплатном тарифе. Некоторые решения обусловлены именно соображениями экономии. Они будут помечены иконкой доллара ($).

    Github Actions, очень приятно!


    Github Actions — это сервис, который позволяет вам автоматизировать рабочий процесс в репозитории. Всё, что вы делаете вручную с вашим проектом, — кроме непосредственно написания кода — вы можете делегировать Github Actions. Если вы предпочитаете сразу знакомиться с первоисточниками, пройдите в официальную документацию.

    Часто мы даже не знаем, что нам вообще нужно автоматизировать. У команды нет времени разбираться в сложном API сервиса, а затем писать и отлаживать решение с нуля. Эту проблему решает Marketplace: там опубликовано почти 5 тысяч готовых Actions, решающих массу типичных задач (например, отправки уведомлений о событиях в Telegram, анализа исходников проекта на наличие техдолга, установки меток на PR в зависимости от изменённых в нём файлов). Плохая новость: многие из них условно бесплатные — с довольно строгими лимитами на использование.

    Рабочий процесс


    Всё в Github Actions крутится вокруг workflows. Каждый workflow отвечает на два вопроса: что сделать и когда сделать.

    Что сделать. Тут вариантов бесчисленное множество: вы можете собирать, тестировать и деплоить ваши билды, используя готовые или созданные своими руками сценарии. Подробнее про конфигурацию workflow

    Когда сделать. Можно триггерить workflows по событиям, происходящим в репозитории. Создание пул-реквеста, пуш тэга на коммит или даже появление новой звёздочки у вашего проекта. Полный список хуков

    Если workflow должен выполняться не по событию, а в определённое время или с определенной периодичностью, в вашем распоряжении синтаксис POSIX cron. Подробнее про регулярные события

    В репозитории может соседствовать сколько угодно разных workflows одновременно. Каждый workflow описывается в отдельном YAML-файле, каждый из которых должен храниться в директории .github/workflows в корне вашего репозитория. Подробнее про синтаксис workflows

    Среда исполнения


    Github Actions предлагает два варианта исполнения ваших workflows:

    • Github-hosted runners — виртуальные машины, которые сервис любезно предоставит вам на время. Доступны машины на Windows, Linux или macOS. Машинки послабее, чем у Codemagic, но всё же довольно бодрые (подробнее про характеристики можно узнать тут). Они полностью сконфигурированы и готовы к работе, предоставляют чистый инстанс с некоторым количеством установленного софта для исполнения каждого джоба, но пользуетесь вы ими на условиях вашего тарифного плана;
    • Self-hosted runners — ваши собственные машины, которые вы конфигурируете каким угодно образом и подключаете в контур. Пользоваться ими Github позволяет бесплатно, но само железо нужно самостоятельно купить и обслуживать.

    В моей статье я остановлюсь на первом варианте. Мы ведь идём по простейшему пути из возможных, верно?

    Настраиваем базовый workflow для Flutter


    Перед тем, как мы приступим к конфигурированию workflow, надо договориться о двух вещах.

    Первое: основной ролью workflow будет усложнение поломки кодовой базы. В основную ветку не должен попасть код, который не собирается, содержит потенциальные проблемы или ломает тесты.

    Второе: в моей конфигурации могут встречаться некоторые тонкости, которые не будут актуальны для вашего проекта. Я буду стараться их пояснять. Однако если вы используете данную статью как руководство — заимствуйте вдумчиво.

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

    Шаг за шагом к готовой сборке


    Приведённый план может использоваться в качестве чек-листа при настройке вашего собственного workflow. Нам предстоит:

    1. дать workflow осмысленное наименование;
    2. указать, по какому событию наш workflow запустится;
    3. решить, на машине с какой конфигурацией он запустится;
    4. определиться с шагами, из которых будет состоять наш workflow:

    • чекаут проекта,
    • установка Java;
    • установка Flutter (как вы помните, каждый раз у нас в распоряжении чистый инстанс),
    • скачивание пакетов проекта,
    • запуск статического анализатора,
    • запуск тестов,
    • собственно сама сборка билда,
    • деплой билда в какое-то место, откуда его смогут получить тестировщики.

    Теперь наша работа приобрела осязаемые очертания. Перейдём к реализации.

    Как будет выглядеть наш workflow в самом конце
    Если вам нужно просто взять сэмпл для вдохновения — он ваш. Если хотите узнать, почему он сделан именно так, читайте дальше.

    name: Flutter PR
    
    on:
     pull_request:
       branches:
         - "dev/sprint-**"
       paths-ignore:
         - "docs/**"
         - "openapi/**"
         - ".vscode/**"
    
    jobs:
     build:
       runs-on: ubuntu-latest
       steps:
         - uses: actions/checkout@v1
         - uses: actions/setup-java@v1
           with:
             java-version: "12.x"
    
         - uses: subosito/flutter-action@v1
           with:
             channel: "stable"
    
         - run: sh ./scripts/flutter_pub_get.sh
    
         - run: sh ./scripts/flutter_analyze.sh
    
         - run: flutter test
    
         - run: flutter build apk --release
    
         - uses: actions/upload-artifact@v1
           with:
             name: APK for QA
             path: build/app/outputs/apk/dev/debug/apk_name.apk
    
         - name: Upload artifact to Firebase App Distribution
            uses: wzieba/Firebase-Distribution-Github-Action@v1
            with:
              appId: ${{ secrets.FIREBASE_ANDROID_PROD_APP_ID }}
              token: ${{ secrets.FIREBASE_TOKEN }}
              groups: testers
              file: build/app/outputs/apk/dev/debug/apk_name.apk
              debug: true
    


    Наименование


    Очевидно, что назвать наш workflow нужно так, чтобы наименование отражало его суть как можно точнее. Наименование (docs) ― первое, что мы увидим в Actions-консоли при выполнении workflow. Почему я назвал мой workflow именно так, вы узнаете через мгновение.



    name: Flutter PR

    Инициирующее событие


    Блок «on» (docs) позволяет указать одно или несколько событий, при регистрации которых мы хотим запускать наш workflow. Более того, некоторые из событий можно тонко настроить. 

    Какое событие выбрать? Чтобы точно не пропустить поломку, можно указать хоть все существующие события. Тогда сборка будет идти практически непрерывно, но хотим ли мы этого? Нет, так как в этом случае лимит по нашему бесплатному тарифному плану закончится фантастически быстро. Будем искать оптимальное решение.

    Предположим, что в нашем проекте соблюдаются договоренности, по которым в основную ветку проекта код не может быть запушен напрямую — только через создание пул-реквеста. Логично, если наш workflow будет реагировать на создание пул-реквеста и собирать проект из изменённой кодовой базы:

    on: pull_request

    $ Этого достаточно для работы, но решение пока не совсем оптимально. Сборка будет триггериться на каждый созданный пул-реквест. Это избыточно, так как нас интересуют только пул-реквесты, направленные в основную ветку проекта. Синтаксис Github Actions позволяет указать наименования (или маски) веток, которые нас интересуют.

    on:
     pull_request:
       branches:
         - "dev/sprint-**"

    $ И снова ищем пути оптимизации процесса. Есть файлы, которые даже в теории не могут повредить вашему проекту: проектная документация, Swagger, общие настройки код-стайла и IDE. Благо, мы располагаем возможностью заигнорить такие файлы по маске пути. В итог блок “on” будет выглядеть следующим образом:

    on:
     pull_request:
       branches:
         - "dev/sprint-**"
       paths-ignore:
         - "docs/**"
         - "drz-swagger/**"
         - ".vscode/**"
    

    Важно: делайте пул-реквест только в том случае, если готовы слить его. Каждый следующий пуш в уже созданный пул-реквест приведёт к перезапуску workflow.

    Конфигурация джоба




    Наконец мы готовы к тому, чтобы сконфигурировать job (docs). Самое время для того чтобы пояснить, какую роль job играет в workflow.

    Каждый workflow должен включать в себя хотя бы один job. Именно job содержит пошаговое описание действий (steps), которые мы производим с нашим проектом. Количество job’ов в одном workflow не лимитировано, как и количеством step’ов в одном job’е. По умолчанию все job’ы исполняются параллельно, если не указана зависимость одного job’а от результатов другого. В нашем проекте будет единственный job, который и будет отвечать за сборку проекта.

    Настройка окружения


    Каждый раз workflow запускается на чистом инстансе виртуальной машины. Единственное, что мы можем выбрать, ― операционную систему, которая будет на этой машине стоять. Что выбрать?

    Велик соблазн выбрать macOS, ведь мы же планируем собирать Flutter приложение под целевые платформы: Android и iOS. Плохие новости. Одна минута использования инстанса с macOS тарифицируется как десять (10!!!) минут использования инстанса с Ubuntu. В инстансе с Windows в нашем случае вообще нет никакого смысла, так как iOS-сборку там собрать всё равно не получится, а его время использования вдвое дороже инстанса с Ubuntu. Подробнее про биллинг

    $ Как же сделать так, чтобы наши 2000 бесплатных минут не превратились в 200? Хорошего решения не существует. Я решил отказаться от сборки билда на iOS при создании пул-реквеста. Это потенциально ударит по стабильности iOS-сборки. Есть и компромиссный вариант ― собирать iOS-билд на macOS только при изменении pubspec.yaml или любого файла из директории /ios, в остальных случаях собирать только Android-билд на инстансе с Ubuntu. Сделать это можно по аналогии с тем, как мы настраивали игнор-файлы для блока «on».

    jobs:
     build:
       runs-on: ubuntu-latest

    Вы можете посмотреть технические характеристики, а также список установленного «из коробки» софта. Flutter и Java, к сожалению, в этот список не входят. Их придётся устанавливать вручную при каждом исполнении workflow.

    Не спешите расстраиваться. На помощь нам придут готовые action’ы, которые мы можем использовать в step’ах нашего job’а. Мы будем использовать два:

    • actions/setup-java — официальный action для настройки Java-окружения;
    • subosito/flutter-action — неофициальный action для скачивания и установки Flutter SDK. Он неплохо себя зарекомендовал: позволяет делать всё, что вам может потребоваться — например, указывать нужный channel фреймворка или переключаться на конкретную версию SDK.

    steps:
          - uses: actions/setup-java@v1
            with:
              java-version: "12.x"
    
          - uses: subosito/flutter-action@v1
            with:
              channel: "stable"

    Клонирование репозитория


    У нас есть чистый инстанс машины, арендованной у Github на несколько минут. В предыдущем шаге мы установили на него весь необходимый софт. Теперь нам следует склонировать репозиторий с исходным кодом нашего проекта. Для этого воспользуемся готовым инструментом:

    • actions/checkout — официальный action для клонирования репозитория с массой настроек, которые в большинстве случаев нам не понадобятся. Так как workflow запускается непосредственно в репозитории, который мы клонируем, нам не нужно указывать его явно.

    - uses: actions/checkout@v1

    Подгрузка зависимостей


    До этого момента мы не реализовывали step’ы своими руками, а пользовались лишь тем, что нам предлагаю готовые action’ы. Теперь мы переходим к реализации активной фазы сборки нашего проекта, поэтому пришло время написать реализацию step’а самостоятельно.

    Перед сборкой нам нужно загрузить все пакеты, которые указаны в блоке dependencies нашего pubspec.yaml файла, а также все их транзитивные зависимости. Для этого Flutter SDK из коробки предлагает простую команду flutter pub get. Реализация step’а может заключаться в вызове одной терминальной команды. В этом случае следующий step будет вызван только по завершению этой команды.

    - run: flutter pub get

    В случае, если ваш проект имеет сложную структуру и содержит ряд dart-пакетов, подключаемых локально, вы столкнётесь с проблемой. Без явного вызова flutter pub get для каждого из этих пакетов собрать проект невозможно. В моём проекте такие пакеты собраны в папке /core, расположенной в корневой директории. Ниже — скрипт, решающий эту проблему. Он описан в файле flutter_pub_get.sh в папке /scripts в той же самой корневой директории.

    flutter pub get
    cd core
    for dir in */ ; do
    
        echo ${dir}
        cd ${dir}
        pwd
        flutter pub get
        cd ..
        pwd
        if [ "$#" -gt 0 ]; then shift; fi
        # shift
    done

    Так как реализацией step’а может быть любая терминальная команда, ничто не мешает нам исполнить наш shell-скрипт.

    - run: sh ./scripts/flutter_pub_get.sh

    Статический анализ кода


    Flutter предлагает нам пользоваться встроенной командой flutter analyze для запуска статического анализатора. Это поможет выявить потенциальные проблемы с нашей кодовой базой на раннем этапе: до того как в проде «выстрелит» баг, либо наш код и вовсе превратится в нечитаемое и неподдерживаемое месиво.

    Мы могли бы воспользоваться коробочной возможностью без доработок, но, увы, стандартное поведение команды flutter analyze имеет изъян, который рушит наш workflow в неподходящий момент. 

    Проблемы, найденные анализатором, классифицируются по трём уровням критичности: info, warning, error. В этом issue описано, что даже в том случае, если в процессе анализа обнаружены только проблемы класса info (а на их исправление не всегда стоит тратить время здесь и сейчас), команда возвращает код «1», в результате чего ваша сборка упадёт.

    В качестве временного решения предлагаю использовать следующий скрипт. Отныне сборка будет падать только при наличии проблем уровня error:

    OUTPUT="$(flutter analyze)"
    echo "$OUTPUT"
    echo
    if grep -q "error •" echo "$OUTPUT"; then
        echo "flutter analyze found errors"
        exit 1
    else
        echo "flutter analyze didn't find any errors"
        exit 0
    fi

    Исполняем shell-скрипт в следующем step’е нашего workflow:

    - run: sh ./scripts/flutter_analyze.sh

    Запуск тестов


    Если у вас в проекте есть тесты — вы на правильном пути! Чтобы тесты работали, мало их написать — их нужно регулярно запускать, чтобы вовремя исправлять огрехи реализации или актуализировать при необходимости. Поэтому следующим step’ом мы реализуем

    - run: flutter test

    Будьте внимательны: пустые тестовые классы, не содержащие ни одного реализованного теста, будут приводить к падению всего workflow. Выход один: не объявлять тестовые классы до тех пор, пока не будете готовы реализовать в рамках него хотя бы один тест.

    Сборка и подписание


    Все подготовительные работы позади. Мы убедились, что код, скорее всего, не содержит очевидных проблем. Теперь мы переходим к самому ключевому этапу — производству артефакта. Иными словами, мы будем собирать APK.

    Сама по себе сборка реализуется крайне просто. В нашем распоряжении терминальная команда flutter build, которая крайне глубоко конфигурируется и позволяет собрать артефакт для конкретного флейвора, main-файла, ABI. Мы не рассматриваем эти нюансы в рамках статьи, поэтому используйте дополнительные флаги команды при необходимости.

    - run: flutter build apk --release

    Наша цель — получить сборку, подписанную релизным ключом. И на этом этапе нам придётся решить проблему секьюрности, ведь нам нужно где-то хранить релизный keystore, а также все его алиасы и пароли.

    Github позволяет безопасно хранить строковые значения в специальном хранилище Secrets. Имеющиеся здесь данные хранятся в рамках соответствующего репозитория и могут быть прочитаны программно из любого step’а вашего workflow. При этом значения невозможно увидеть через web-интерфейс Github. Позволяется только удаление или перезапись.



    Это выглядит подходящим решением для алиасов и паролей, особенно если вы сами себе служба безопасности, но что делать с самим *.jks-файлом? Пушить его в репозиторий не выглядит удачной идеей, даже если ваш репозиторий приватный. К сожалению, никакого безопасного способа хранения файлов Github не предоставляет, поэтому придётся изворачиваться.

    Неплохо было бы представить наш keystore-файл в виде строки. И это реально — стоит лишь закодировать его в base64. Для этого нужно открыть терминал в директории, содержащей наш *.jks-файл и выполнить следующую команду. Рядом будет создан текстовый файл из которого можно скопировать base64-представление нашего keystore, чтобы затем… сохранить его в Github Secrets.

    openssl base64 < key_store_filename.jks | tr -d '\n' | tee keystore.jks.base64.txt

    Теперь, когда все необходимые составляющие успешного подписания сборки в наличии, мы приступим к конфигурации step’а. В блоке env мы объявляем все переменные окружения для этого конкретного step’а. Значения этих переменных мы возьмём из Secrets.

    - run: flutter build apk --release
            env:
              STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
              KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
              KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
              STORE_FILE: ${{ secrets.STORE_FILE }}

    В нашем Android-хосте нам предстоит описать конфигурацию сборки таким образом, чтобы мы обрели возможность подписывать *.apk-файл и на CI, не потеряв возможность собирать подписанную сборку локально. За этот момент ответственен файл keystoreConfig.gradle.

    В случае если keystore_release.properties файл найден, известно, что сборка происходит локально, а значит можно проинициализировать все свойства keystoreConfig, просто прочитав их из файла. В противном случае сборка происходит на CI, а это значит, что единственный источник сенситив-данных — Github Secrets.

    ext {
       def releaseKeystorePropsFile = rootProject.file("keystore/keystore_release.properties")
       if (releaseKeystorePropsFile.exists()) {
           println "Start extract release keystore config from keystore_release.properties"
           def keystoreProps = new Properties()
           keystoreProps.load(new FileInputStream(releaseKeystorePropsFile))
           keystoreConfig = [
                   storePassword: keystoreProps['storePassword'],
                   keyPassword  : keystoreProps['keyPassword'],
                   keyAlias     : keystoreProps['keyAlias'],
                   storeFile    : keystoreProps['storeFile']
           ]
       } else {
           println "Start extract release keystore config from global vars"
           keystoreConfig = [
                   storePassword: "$System.env.STORE_PASSWORD",
                   keyPassword  : "$System.env.KEY_PASSWORD",
                   keyAlias     : "$System.env.KEY_ALIAS",
                   storeFile    : "$System.env.STORE_FILE"
           ]
       }
       println "Extracted keystore config: $keystoreConfig"
    }
    

    А так выглядит файл keystore_release.properties:

    storePassword={тут_будет_ваш_пароль}
    keyPassword={тут_будет_ваш_пароль}
    keyAlias={тут_будет_ваш_алиас}
    storeFile=../keystore/keystore.jks

    Последний шаг в build.gradle файла нашего Android-хоста применяем keystoreConfig-файл к нашей конфигурации подписания релизной сборки:

    android {
       signingConfigs {
           release {
               apply from: '../keystore/keystoreConfig.gradle'
    
               keyAlias keystoreConfig.keyAlias
               keyPassword keystoreConfig.keyPassword
               storeFile file(keystoreConfig.storeFile)
               storePassword keystoreConfig.storePassword
           }
       }
    }

    Подписанная сборка уже у нас в руках! Но как распространить её на своих коллег для тестирования?

    Выгрузка


    Github Actions позволяет настроить выгрузку артефактов практически в любой известный инструмент для дистрибьюции сборок, но мы рассмотрим лишь два варианта:

    • Github Storage — самый простой способ выгрузки сборок в собственное хранилище Github, который работает «из коробки», но имеет некоторые ограничения;
    • Firebase App Distribution — сервис из экосистемы Firebase, пришедший на смену Beta by Crashlytics. Интеграция немного сложнее в настройке, но сам сервис при этом гораздо удобнее для использования.


    Github Storage
    Github Storage легко интегрируется через официальный action. Вам нужно лишь указать наименование сборки таким, каким его увидят ваши коллеги в web-интерфейсе, а также путь к собранному *.apk-файлу на CI.

    - uses: actions/upload-artifact@v1
            with:
              name: APK for QA
              path: build/app/outputs/apk/dev/debug/apk_name.apk

    Основная проблема заключается в ограниченном объёме хранилища. На бесплатном тарифном плане вам предоставляется всего лишь 500 Мб. Самое странное, что я не нашёл никакой возможности вручную очистить всё хранилище сразу через web-интерфейс, поэтому вышел из положения через… написание отдельного workflow, ответственного только за очищение Storage от замшелых артефактов.

    Workflow запускается ежедневно в час ночи и удаляет все артефакты старше одной недели:

    name: Github Storage clear
    
    on:
      schedule:
        - cron: '0 1 * * *'
    
    jobs:
      remove-old-artifacts:
        runs-on: ubuntu-latest
        timeout-minutes: 10
    
        steps:
          - name: Remove old artifacts
            uses: c-hive/gha-remove-artifacts@v1
            with:
              age: '1 week'

    Firebase App Distribution
    Что касается Firebase App Distribution, то для интеграции с ним я использовал готовый action wzieba/Firebase-Distribution-Github-Action

    - name: Upload artifact to Firebase App Distribution
            uses: wzieba/Firebase-Distribution-Github-Action@v1
            with:
              appId: ${{ secrets.FIREBASE_ANDROID_PROD_APP_ID }}
              token: ${{ secrets.FIREBASE_TOKEN }}
              groups: testers
              file: build/app/outputs/apk/dev/debug/apk_name.apk
              debug: true

    Для корректной работы action’а ему необходимо передать параметры:

    • appId — идентификатор приложения, который можно найти в настройках Firebase-проекта;



    • token — токен для аутентификации в ваш FIrebase-проект, что необходимо для загрузки сборки в сервис. Получить токен можно только через Firebase CLI, о чём подробнее можно почитать в официальной документации;
    • file — путь к собранному *.apk-файлу на CI;
    • groups — параметр необязателен, однако позволяет указать вам алиас группы тестировщиков, на который загруженная сборка будет расшариваться автоматически.

    Запуск и наблюдение


    Наш простейший workflow готов! Всё, что нам осталось сделать, — запустить инициирующее событие и наблюдать за ходом исполнения workflow.

    Советы и напутствия


    Теперь вы можете пользоваться всеми преимуществами простого CI/CD механизма на вашем Flutter-проекте, независимо от его масштабов, интенсивности разработки, а также вашего кошелька.

    Напоследок вот вам несколько советов и наблюдений, к которым я пришёл в процессе работы над этим workflow:

    • Настраивайте workflow в публичном репозитории. Если вы настраиваете workflow для приватного репозитория, сконфигурируйте его сперва в публичном, а затем перенесите в приватный. Так вы сэкономите драгоценные бесплатные минуты билд-тайма вашего тарифного плана. Ведь проблемы, из-за которых вам придётся снова и снова перезапускать workflow при настройке, у вас обязательно будут.
    • Реализуйте step’ы в отдельных shell-скриптах. Сложный workflow становится нагляднее. К тому же упрощается ручной запуск скриптов на машине для быстрой проверки. В тестовом примере я не всегда соблюдал эту рекомендацию для упрощения.
    • Следите за Run Duration вашего workflow. Обычно время исполнения одного и того же workflow почти не меняется от запуска к запуску, если на то нет объективных причин. В деталях каждого исполненного workflow можно увидеть, сколько минут и секунд заняло исполнение каждого отдельного step’а. Самой времязатратной всегда будет сама сборка артефакта. Установка Flutter SDK тоже займёт не меньше минуты. Нормальное время сборки для маленького проекта — 5-6 минут.

    Впереди ещё масса потенциальных улучшений и доработок. Пишите в комментарии свои идеи для улучшения workflow. Чего в нём не хватает лично вам? Возможно, реализация самых интересных идей читателей ляжет в основу следующей статьи по теме.

    Все скрипты и workflow можно найти в репозитории с тестовым приложением.

    Спасибо за внимание.

    P.S. Наша команда Surf выпускает много полезных библиотек для Flutter. Выкладываем их в репозитории SurfGear.
    Surf
    Мобильные приложения и цифровая трансформация

    Похожие публикации

    Комментарии 11

      0
      а как обстоят дела с IOS билдом?
        0
        Об этом буду писать в следующей части. Сейчас как раз разбираюсь с этим.
        Скорее всего пойдем решать эту задачу через Fastlane, благо, готовые экшны для него есть.
        +3
        Спасибо за статью.
        Сам Github Actions является самой худшей CI системой которую видел в своей жизни, а повидал их немало. Совершенно ублюдочный интерфейс с языком описания «то ли yaml, то ли неведомая зверушка», определения переменных по ходу нужно делать на каком-то клингонском языке с абсолютно дефективной логикой, придуманной явно пришельцами незнакомыми с устройством мозга.
        Впрочем во всем этом хорошо видны good practices Microsoft.
        Забудьте о каких-то темплейтах, к которым вы привыкли в гитлабе или дженкинсе, тренируйте движения Ctrl+C/Ctrl+V, никакого там DRY, reusability и т.д. Из виртуалок вам только убунту, остальное на контейнерах. А если не дай бох захотите расширения, то JS вам в помощь, ведь всем известно что кроме веба в IT больше ничего не существует. Чтобы проверять какие-то системные вещи придется выкручивать руки и мозги.
        Слава богу есть Docker actions, то есть теперь для использования однострочного баша как функции, нужно заводить специальный отдельный контейнер и исполнять ее там. Спасибо, микрософт.
        Но так как для открытых проектов это полностью бесплатно (и пока что без ограничений), то приходится колоться, плакать и юзать это ужасно спроектированное гуано.
          0
          Спасибо за развёрнутый комментарий, я согласен с вашими наблюдениями.

          Да, вероятно, что серьёзный проект облсужить при помощи Github Actions будет непросто. Но для небольшого опенсорса или пет-проекта — это отличный вариант. Простота интеграции с репозиторием, который и так в 99% случаев будет у вас на Github заставляет простить многое.
            0
            Слава богу есть Docker actions, то есть теперь для использования однострочного баша как функции, нужно заводить специальный отдельный контейнер и исполнять ее там. Спасибо, микрософт.

            Не уловил, в чем проблема запустить "однострочный баш" в Github Actions? К примеру:


            - name: Run cassandra
              run: |
                 docker run --name mycassandra -d -p 9042:9042 -e CASSANDRA_LISTEN_ADDRESS=127.0.0.1 cassandra:3.11

            :)
            Понятно, что тут докер запускается, но могу ведь что угодно сделать.


            Из виртуалок вам только убунту, остальное на контейнерах.

            Вот же пишут, или это другое?


            Virtual environment YAML workflow label
            Windows Server 2019 windows-latest or windows-2019
            Ubuntu 20.04    ubuntu-20.04
            Ubuntu 18.04    ubuntu-latest or ubuntu-18.04
            Ubuntu 16.04    ubuntu-16.04
            macOS Catalina 10.15    macos-latest or macos-10.15
              0
              Не уловил, в чем проблема запустить «однострочный баш» в Github Actions?
              Да я об actions в Github Actions, они даже имя не могли отдельное придумать, аладдин аладдин.
              Вот же пишут, или это другое?
              Да, точно, хотя вмки на винде или маке мне вот ну вообще нафиг не сдались. Неужели трудно было хотя бы центос/федору добавить.
                0
                Да я об actions в Github Actions, они даже имя не могли отдельное придумать, аладдин аладдин.

                Так а зачем action делать, если нужна одна строка на ssh? Мы пишем эту строку прямо в workflow. А то и несколько строк.

                  0
                  Потому что DRY и реюзабилити. Особенно когда надо поменять эту строчку в десяти разных местах.
            +2
            а я правильно понимаю, что на каждый билд вы ставите заново jdk и flutter sdk? а пробовали кешировать зависимости?
              0
              Да, вы всё правильно поняли. Спасибо за ссылку, попробую оптимизировать этот момент! Если взлетит, секунд 40 с каждого билда удастся снять.
              +1
              levelup.gitconnected.com/ci-cd-for-flutter-apps-3a56e3fc6d8e еще одна интересная статья о возможности Github Actions для CI / CD на Flutter-проекте

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

              Самое читаемое