つばくろぐ @takamii228

知は力なり

flutterのバージョンをGitLab CI Runner上で動的に切り替える

この記事はFlutter Advent Calendar 2019 (#2)の記事です。

※12/21 8時時点で18日分に空きがあったので埋めさせていただきました(参考

はじめに

GitLab CIを使ってFlutterのCI/CDをやっているのですが、CI環境において複数のFlutterのバージョンを管理する必要があり、少し悩んだので以下にまとめます。

GitLab CI Runnerは以下のような感じで、shellモードでMac mini上で動かしています。

f:id:takamii228:20191220225237p:plain

今回、Flutterのバージョンアップの検証が必要になったのですが、すでに配信中のアプリで利用しているFlutterのバージョンに対してはバージョンアップが完了するまでの間に緊急パッチを宛てる可能性があります。そのため、緊急パッチのビルド時につかうFlutterのバージョンと、バージョンアップ検証で使うFlutterのバージョンをそれぞれのビルドで使い分ける必要があります。

検討した解決策

CI環境上で複数のflutterのバージョンを動作させる方法として、以下の2つ案を考えました。

  1. 配信版と追加開発をブランチを分けて、ブランチごとに固定のGitLab CI Runnerを割り当てる
  2. GitLab CIの実行時に使うバージョンを動的に切り替えられるようにする

メリット・デメリットは以下の通りです。

メリット
デメリット
案1
  • 要件を満たすことができる
  • 各バージョンを設定したCI Runnerに対応するブランチの命名規則を決める必要がある
  • .gitlab-ci.ymlの設定が煩雑になる。
  • 各CI Runnerに対して別々にバージョンの設定する必要がある
案2
  • 要件を満たすことができる
  • CI Runnerとブランチの命名規則を対応させる必要がない
  • .gitlab-ci.ymlの設定がシンプルになる
  • 各CI Runnerの設定を分ける必要がない
    • バージョンが変わる場合、CIの実行の度に都度バージョン切り替えの処理が走るためCIの実行時間が長くなる

    GitLab CIではCI Runnerにタグをつけることができ、tagonlyexcept等を記述することで特定のブランチのジョブを特定のCI Runnerで実行させる、という設定ができます。

    https://docs.gitlab.com/ce/ci/yaml/

    しかしデメリットで書いた通り、普段の開発においてはfetuareブランチやhotfixブランチを切ったりリリースタグを切ったりとブランチの命名規則を複数考慮してgitlab-ci.ymlを設定するのはなかなか骨が折れます。

    今回は設定が煩雑になることに加え、バージョンアップ版のリリースまでの移行期間のみ並行運用が走ること、また都度実行で増える実行時間が2-3分程度で待てるレベルであることを踏まえて2を選択することにしました。

    実装

    各ビルドで使うflutterのバージョンはpubspec.yamlに記載できるため、それを使ってflutterのバージョンを切り替えてからCIを実行するように.gitlab-ci.ymlを設定します。

    ...
    environment:
      sdk: ">=2.1.0 <3.0.0"
      flutter: 1.9.1+hotfix.6
    ...
    

    yamlから特定のキーの値を取得するのにはyqが使えます。

    yq.readthedocs.io

    pubspec.yamlに記載されたバージョンでflutterのversionを切り替えるシェルは以下のように記載できます。

    #!/bin/sh
    
    set -x
    set -e
    
    # get tagert flutter version
    TARGET_FLUTTER_VERSION=`cat pubspec.yaml | yq -r .environment.flutter`
    
    TARGET_FLUTTER_VERSION_TAG_NAME=v${TARGET_FLUTTER_VERSION}
    
    CURRENT_DIR=`pwd`
    FLUTTER_DIR=${HOME}/development/flutter
    
    cd ${FLUTTER_DIR}
    
    # current version
    flutter --version
    
    # flutter checkout
    git checkout -f master
    git pull
    git checkout -f ${TARGET_FLUTTER_VERSION_TAG_NAME}
    
    flutter precache
    
    flutter doctor
    
    # updated version
    flutter --version
    
    cd ${CURRENT_DIR}
    

    ポイントとしてはflutterのバージョンはpubspec.yamlにはvなしで記載されているのに対し、flutterのタグではvがついているためgit checkoutするときに付与する必要があります。

    flutter.dev

    flutterのバージョンの切り替えはflutter versionコマンドを使うこともできますが、これを実行するには対象のバージョンがあるchannelをflutter channelで事前に指定し、かつupgradeを実行して最新化しておく必要があります。

    最新化がされていなかったり、切り替えたいバージョンのchannelが異なっている場合はflutter versionコマンドは失敗してしまいます。

    $ flutter version -f v1.13.3
    There is no version: 1.13.3
    Unable to checkout version branch for version 1.13.3.
    

    なので、今回はflutterコマンドがインストールされている場所~/development/flutterに移動し、master上でpullして都度タグをcheckoutする方式を取るようにしました。

    チェックアウトした後はflutter precacheすることで依存するライブラリが落ちてきます。

    macOS install - Flutter

    最後に↑のシェルを.gitlab-ci.ymlbefore_scriptの中で実行するように設定すれば完成です。

    ...
    before_script:
      - ./set-flutter-version.sh
    ...
    

    似たような感じでxcode-selectすればXCodeのバージョンも指定できそうですね。

    gradle wrapperやmaven wrapperでのバージョン指定、Docker buildなどと同様にビルドさせる環境のツールのバージョンはgitレポジトリに定義含めてしまって、CIの実行の度に動的に変えられるようにしておくと管理が便利になりますね。

    あとはflutterのバージョン切り替えもDocker Buildみたいにキャッシュできたらいいのになー。