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上で動かしています。
今回、Flutterのバージョンアップの検証が必要になったのですが、すでに配信中のアプリで利用しているFlutterのバージョンに対してはバージョンアップが完了するまでの間に緊急パッチを宛てる可能性があります。そのため、緊急パッチのビルド時につかうFlutterのバージョンと、バージョンアップ検証で使うFlutterのバージョンをそれぞれのビルドで使い分ける必要があります。
検討した解決策
CI環境上で複数のflutterのバージョンを動作させる方法として、以下の2つ案を考えました。
- 配信版と追加開発をブランチを分けて、ブランチごとに固定のGitLab CI Runnerを割り当てる
- GitLab CIの実行時に使うバージョンを動的に切り替えられるようにする
メリット・デメリットは以下の通りです。
案1 |
|
|
案2 |
|
|
GitLab CIではCI Runnerにタグをつけることができ、tag
、only
、except
等を記述することで特定のブランチのジョブを特定の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
が使えます。
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のバージョンの切り替えは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
することで依存するライブラリが落ちてきます。
最後に↑のシェルを.gitlab-ci.yml
のbefore_script
の中で実行するように設定すれば完成です。
... before_script: - ./set-flutter-version.sh ...
似たような感じでxcode-select
すればXCodeのバージョンも指定できそうですね。
gradle wrapperやmaven wrapperでのバージョン指定、Docker buildなどと同様にビルドさせる環境のツールのバージョンはgitレポジトリに定義含めてしまって、CIの実行の度に動的に変えられるようにしておくと管理が便利になりますね。
あとはflutterのバージョン切り替えもDocker Buildみたいにキャッシュできたらいいのになー。
FlutterでID/Password入力画面を作ってみる
FlutterでID/Pass入力画面を作るときに設定するのパラメータをちょっと調べたのてまとめてみます。(※入力のバリデーションやオートフォーカス等のアクションは含みません)
よくあるID/Pass入力画面の要件
よくあるID/Passの入力画面の要件といえばこんな感じでしょうか。
ID入力
- 半角英数が入力できる
Password
- 半角英数が入力できる
- 入力内容はマスクしたい
- 入力内容を確認したいので、マスクの表示非表示を切り替えられるようにしたい
- 予測変換はさせたくないしヒストリーに記録なんてさせたくない
- 入力欄のコピペはブロックしたい
入力フォームはTextInputFiled
を使えば実現できます。中でTextFieldをラップしているようです。
TextFormField class - material library - Dart API
TextField constructor - TextField class - material library - Dart API
上記の要件に絡みそうなパラメータは以下の通りです。
- obscureText
- 入力内容のマスク表示の切り替え
- autocorrect
- オートコレクト・予測変換の有効・無効化
- enableInteractiveSelection
- 入力エリアのセレクトアクション(コピペ、選択、削除など)の有効・無効化
- TextInputType
- 立ち上げるキーボードタイプ。以下の種類がある。
- TextInputType class - services library - Dart API
- datetime
- emailAddress
- multiline
- number
- phone
- text
- url
- visiblePassword
今回の要件では、以下のような設定にするのがよさそうです。
ID入力欄
パラメータ | 値 | 理由 |
---|---|---|
obscureText | false | IDは常に見せておくため |
autocrrect | false/true | どちらでもよさそう。メールアドレスとかだとサジェストがあると嬉しいかもしれない。 |
enableInteractiveSelection | true | コピペしたいケースがありそうなので。 |
TextInputType | emailAddress | IDがメールアドレスのケースも多く、とりあえず半角英数で立ち上がってほしいので。 |
Password入力欄
パラメータ | 値 | 理由 |
---|---|---|
obscureText | true/false | 右端に目のアイコンを用意し、クリックで切り替えられるようにする。 |
autocrrect | false | パスワードは守りたいので。 |
enableInteractiveSelection | false | コピペはブロックしたいので。 |
TextInputType | visiblePassword | パスワードを見せる専用のキーボードタイプ。v1.9.1+hotfix-6から使える模様。 |
Widgetに上記の設定をしてiOS/Androidそれぞれエミュレータで動かしてみます。
今回試したコードサンプルはこちら。
なおPassword入力画面のパーツについては本家のflutter garallyにもあり、今回の実装の参考にしました。
実行環境は以下の通りです。
$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel unknown, v1.9.1+hotfix.6, on Mac OS X 10.14.6 18G87, locale en-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) [✓] Xcode - develop for iOS and macOS (Xcode 11.3) [✓] Android Studio (version 3.5) [!] IntelliJ IDEA Ultimate Edition (version 2019.3) ✗ Flutter plugin not installed; this adds Flutter specific functionality. ✗ Dart plugin not installed; this adds Dart specific functionality. [!] VS Code (version 1.41.0) ✗ Flutter extension not installed; install from https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter [✓] Connected device (2 available) ! Doctor found issues in 2 categories. $ flutter devices 2 connected devices: AOSP on IA Emulator • emulator-5554 • android-x86 • Android 9 (API 28) (emulator) iPhone 11 Pro Max • 424952E6-D634-4B19-AA9A-8552B62DE670 • ios •com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator)
挙動確認
iOS
想定通り動いてそうです。
Android
Androidも大丈夫そうです。
余談:TextInputTypeがvisiblePasswordじゃないとAndroidでautocorrectが効かないらしい。
そもそもFlutterにはautocorrectのバグがあったようです。
このバグの対処のために、最新のstableであるv1.12.13+hotfix.5
ではenableSuggestions
というパラメータが追加されたようです。
このパラメータ追加で予測変換を一律制御できるのかな〜と思ったのですがそもそもAndroidのみとのこと。またIssueのコメントにあるようにTextInputType.text
だと予測変換の無効化は効かない模様。まじかー。
入力のI/O周りはAndroidとiOSの両OSのモジュールをうまくラップして制御するのが難しいのでしょうかね。そういえばWebやDesktopもあるんだった・・・。
あと今回他のサービスはどうなってるのかな〜と思っていろいろ調べたのですが、ほとんどがWebViewでの入力画面が立ち上がるパターンでした。。。
GitLab APIを使ってtag名とtagのタイムスタンプをcsv形式で出力する
リリース頻度のデータを分析するために、GitLabのtag名とtagを作成した時のタイムスタンプの一覧が欲しくなったのでGitLab API経由で取得するシェルを書きました。
#!/bin/sh BASE_URL="xxxx" API_TOKEN="xxxx" PROJECT_ID="xxxxx" # 全tag一覧を取得するための最大ページ数を取得する TOTAL_PAGES=`curl -D - -s -o /dev/null -X GET -H "PRIVATE-TOKEN: ${API_TOKEN}" "${BASE_URL}/api/v4/projects/${PROJECT_ID}/repository/tags?per_page=100" | grep "X-Total-Pages" | tr -d "X-Total-Pages: " | tr -d "\r"` # タグとタグのタイムスタンプをカンマ区切りで取得する RESULT="" for i in `seq 1 ${TOTAL_PAGES}` do RESULT=${RESULT}`curl -X GET -H "PRIVATE-TOKEN: ${API_TOKEN}" "${BASE_URL}/api/v4/projects/${PROJECT_ID}/repository/tags?per_page=100&page=${i}" | jq -r '.[] | (.name|tostring) + "," + (.commit.authored_date|tostring)'`"\n" done # UTCで返却されるので、JSTに変換し出力する for line in ${RESULT} do TAG_NAME=`echo ${line} | cut -d ',' -f1` TIMESTAMP_UTC=`echo ${line} | cut -d ',' -f2` TIMESTAMP_JST=`date -v+9H -j -f "%Y-%m-%dT%T.000Z" ${TIMESTAMP_UTC} +%Y-%m-%dT%T.000Z` echo ${TAG_NAME},${TIMESTAMP_JST} done
tag一覧の取得は/api/v4/projects/${PROJECT_ID}/repository/tags
のエンドポイントを叩くと取れます。
https://docs.gitlab.com/ce/api/
tag一覧の取得はページングのハンドリングが必要なので、レスポンスヘッダーのX-Total-Pages
からページ数を取得し、最終的にはfor文で回してjqで加工したものを連結します。
タイムゾーンの設定によってはUTCで結果が帰ってくるので、最後にdateコマンドでJSTに変換しています。
上記のシェルを実行すると以下のようなcsvが取得できます。
1.5.0,2019-09-30T10:59:49.000Z 1.4.0,2019-09-26T15:35:21.000Z 1.3.2,2019-09-20T19:00:11.000Z 1.3.1,2019-09-17T18:21:30.000Z 1.3.0,2019-09-11T21:01:37.000Z 1.2.0,2019-09-03T22:08:34.000Z 1.1.0,2019-08-27T22:41:38.000Z 1.0.2,2019-08-20T22:37:28.000Z 1.0.1,2019-08-13T22:13:56.000Z 1.0.0,2019-08-06T21:38:05.000Z 0.3.0,2019-07-30T21:16:30.000Z 0.2.0,2019-07-23T22:38:51.000Z 0.1.0,2019-07-17T15:30:01.000Z
あとはこれをSpreadSheetなりExcelなりPythonなりで集計すればリリース頻度の分析の集計ができます。
LeanとDevOpsの科学[Accelerate] テクノロジーの戦略的活用が組織変革を加速する (impress top gear)
- 作者:Nicole Forsgren Ph.D.,Jez Humble,Gene Kim
- 出版社/メーカー: インプレス
- 発売日: 2018/11/22
- メディア: 単行本(ソフトカバー)