Flutterへのコントリビューション仲間を増やしたい
FlutterはGoogleが中心となって開発しているプロジェクトです。そのため、Flutterのソースコードを改善したり、バグを報告することは難しいイメージがあるのではないでしょうか?
次のPRを見てみてください。どちらも筆者が提出したPRになります。
いかがでしょうか? どちらも「簡単じゃん!」と思ったのではないでしょうか? そうです、Flutterへのコントリビューションは簡単なのです!
また「Flutterのコードだけど、Flutterのコードではないじゃん!」と思った方向けに、筆者の手に余りつつも、レビュアーの力を借りてPRをマージまで進めることができたPRも紹介しましょう。順調にいけば、Flutter 3.27にてリリースされる予定の修正です。
テストコードが長く見えますが、読んでみると、そう複雑なことをしているわけではありません。修正内容についても、Unicodeの仕様を反映しただけになります。
もちろん難しい修正もたくさんありますが、難しい修正だけがFlutterへのコントリビューションではありません。
本記事では、Flutterにコントリビューションするための環境構築について解説します。対象はflutter/flutterやflutter/engineです。本記事は公式ドキュメントの内容を元に、筆者の経験を交えて記述しています。公式ドキュメントも十分に短い解説になっているので、気になる方はそちらも参照してください。
なおflutter/packagesも含めるか迷ったのですが、少しリポジトリの雰囲気が異なるため、今回は対象としません。flutter/packages
へのPRについては、より一般的なOSSへのコントリビューションに近いと思ってください。[1]
flutter/flutter
Flutterの中心的なリポジトリです。普段記述するWidgetたちはここにいます。また。ビルドやテストに利用するツールもいます。
普段利用している"Flutter"の改修を目指す場合には、このリポジトリのpackages/flutter
を修正することになります。
この記事では、筆者の経験をもとに環境構築の手順を紹介します。ただengine
を編集しない場合には、この記事と公式ドキュメントの難易度はほぼ同じです。
実はFlutterアプリを書いている皆様は、このリポジトリをclone済みです。というのも、Set up Flutterで開発マシンに落としているのが、flutter/flutter
リポジトリであるためです。flutter channel
やflutter upgrade
のコマンドは、gitで言えばflutter/flutter
のbranchやtagを切り替えることで実現されています。
確かめてみましょう。筆者は~/flutter
に開発用のSDKをインストールしています。次のようにディレクトリを移動して、git remote -v
を実行すると、以下のような出力が得られます。
ぜひ、自分の環境でも確認してみてください。
$ cd ~/flutter
$ git remote -v
origin https://github.com/flutter/flutter.git (fetch)
origin https://github.com/flutter/flutter.git (push)
このリポジトリをcloneするとFlutter SDKとして使える仕組みは、flutter/flutter
の変更を手元で試しやすく、PRを作成しやすいというメリットにつながります。[2] このメリットを活かして、flutter/flutter
へのコントリビューションの準備を進めていきましょう。
flutter/flutter
のPRを作成する場合、次の手順が必要です。一部をスキップしたり追加したりすることもありますが、基本的な流れは次の通りです。
-
flutter/flutter
をGitHub上でforkする - forkしたリポジトリを開発環境にcloneする
- cloneしたリポジトリを修正する
- Flutter SDKのパスをcloneしたリポジトリに設定する
-
python3とdepot_toolsをPATHに追加する
- 筆者は
brew install python3
でpython3をインストールしました
- 筆者は
-
flutter/flutter
のrootでflutter update-packages
を実行する - コードの変更や動作確認を行う
- 修正内容をforkしたリポジトリにpushする
- 修正内容をPRにして提出する
ポイントはcloneしたリポジトリをFlutter SDKのパスに設定する点です。
flutter run
やflutter test
で実行されるSDKが、コードを変更したものになるため、任意のアプリやテストを実行できます。
もしもグローバルな環境をいじりたくない場合には、fvmが利用できます。都度git push
を行う必要がありますが、PRの対応に長い時間を要しそうな場合には検討の余地があるかもしれません。[3]
~/packages/flutter
に開発用のリポジトリをクローンしたとします。すると、次のような環境変数の設定をすればOKです。通常の開発で利用するFlutter SDKのパスは、一時的にコメントアウトしています。
# flutter
# export PATH=$PATH:$HOME/flutter/bin
export PATH=$PATH:$HOME/.pub-cache/bin
# flutter dev
export PATH=$PATH:$HOME/depot_tools
export PATH=$PATH:$HOME/packages/flutter/bin
これで、任意のアプリをflutter run
すれば、~/packages/flutter
にcloneしたリポジトリが利用されます。簡単ですね。
Issueの探し方
flutter/flutter
には、多くのIssueが登録されています。flutter/packages
に関連づくIssueも登録されているため、最初は適切なIssueを探すのが難しいかもしれません。
貴重な時間を割くものでもあるので、自分のスキルや興味に合ったIssueを探すことは大切です。
Issueに関連づけられているlabelを見ることで、ある程度の絞り込みができます。初めてのIssueを探す場合には、次のようなlabelが付与されたIssueを探すと良いでしょう。
ただgood first issue
は初心者向けのIssueとされていますが、「本当に初心者向け〜?」と感じるものも混じっています。一方、has reproducible steps
は再現手順が記載されているため、Issueの内容を手元で確認しやすくなっています。どうしてもIssueによって難度がバラつくので、has reproducible steps
が付与されている、関心が高かったり理解度が高い領域のものを確認するのが良いと思います。
もしも腕に覚えがある場合には、重要度の高いIssueを探すのが良いでしょう。flutter/flutter
のIssueには、開発チームが重要度を付与したものがあります。
-
P0
Critical issues such as a build break or regression
-
P1
High-priority issues at the top of the work list
-
P2
Important issues not at the top of the work list
-
P3
Issues that are less important to the Flutter project
P0
やP1
は対応が進んでいることが多いのですが、P2
やP3
になると(手が回らないのか)対応が遅れているIssueが見つかります。例えば、記事の前半で紹介したPRに関連づけられたIssueにはP2
のラベルが付与されていました。
重要なIssueを修正できれば、それだけFlutterの安定性に寄与できます。達成感もありますし、PRをマージされたときの喜びも大きいものがあります。一度P2
やP3
のついた、自分の関心が高いWidgetや機能に関連づけられたIssueを探してみるのはいかがでしょうか? P
は難度ではなく重要度の指標であるため、(未解決なため)簡単ではないでしょうが、しかし取り組めないほど難しいものだけではないはずです。
engine
flutter/flutter
に比べると、格段に難しいのがengine
です。Flutterのコア部分である描画処理などに加えて、AndroidやiOSのネイティブAPIをFlutterで利用するための実装が含まれます。このため、プラットフォーム固有の不具合を調査していると、engine
をいじる必要が出てくることがあります。
2024年12月以降、flutter/flutterとflutter/engineが統合されました。このため、flutter/flutter
にてengine
開発用のステップを実行すると、旧flutter/engine
の開発環境が構築できます。
engine
の開発のためには、いくつかの不思議なステップを踏む必要があります。flutter/flutter
に比べると複雑なことをしなければならないため、少々難度が高いかもしれません。
この記事では、筆者がmacOS上で環境構築した際の知見をもとに、そこそこお手軽にengine
の開発環境を整えることを目指します。
なお、ビルドには時間がかかるため、余裕を持って作業することをお勧めします。もしくは、いいマシンに更新しましょう。[4]
ビルドの前に
engine
の開発において、重要なのはgclient
とexport FLUTTER_ENGINE
です。
gclient
はdepot_tools
に含まれているツールで、engine
の依存関係を解決するために利用します。依存関係の整理の際、どのリポジトリを取得するか記述されるのが.gclient
ファイルです。
この記事を読まれる方はGooglerではないと思われるので、standard.gclient
を.gclient
にリネームしつつflutter/flutter
のrootに配置します。そしてディレクトリを移動せず、gclient sync
を実行します。
これで、各種依存関係が解決され、engine
の開発環境が整います。
続いて、FLUTTER_ENGINE
について。環境変数にFLUTTER_ENGINE
が登録されていると、flutter run
時に「開発中のlocal engineやweb sdkを指定してね」というログが出力されます。逆に言うと、FLUTTER_ENGINE
がセットされていなければ、ローカルでビルドしたengineは利用できません。
この『環境変数にFLUTTER_ENGINE
が登録されていると』と言う条件は厄介です。タイミングによってはFLUTTER_ENGINE
を設定していても、ビルドしたengineが反映されません。そんな時にはunset FLUTTER_ENGINE
や再起動で対応しましょう。
engineのビルド
monorepo化したことにより、engine
のビルドに関する公式ドキュメントが整備されました。この記事を参考にしつつも、常に最新のドキュメントを参考にすることをお勧めします。
もしかすると筆者のセットアップが悪いのかもしれないのですが、ドキュメント通りにコマンドを打ったとしてもninjaが見つからないと言われることがあります。その場合には、brew install ninja
を実行してください。
先ほど紹介した通り、筆者は~/packages/flutter
にflutter/flutter
をcloneしています。このためFLUTTER_ENGINE
は$HOME/packages/flutter/engine/src
です。
AndroidやiOSの場合、ビルドは$FLUTTER_ENGINE
で行います。と言うのも、$FLUTTER_ENGINE/out
に成果物が出力されるためです。どうやらコマンドの一部箇所で相対パスを利用しているらしく、コマンドの実行場所によってビルドが成功するか失敗するかが変わるようです。
なお、Webの場合にはfeltを利用します。felt
は(どうやら)$FLUTTER_ENGINE
からパスをセットするらしく、どこからでも実行できるようです。大変便利なので、export PATH=$PATH:$FLUTTER_ENGINE/flutter/lib/web_ui/dev
をセットしましょう。
なお、ビルド時にはengineが対応するCPUアーキテクチャ等を指定します。このオプションは、公式ドキュメントを見ながら設定してください。筆者は次のようなaliasを設定していますが、どれもエミュレーター/シミュレーターで動作させることを想定しています。
alias build:ios='./flutter/tools/gn --ios --simulator --unoptimized --simulator-cpu=arm64; ./flutter/tools/gn --unoptimized --mac-cpu arm64; ninja -C out/ios_debug_sim_unopt_arm64 && ninja -C out/host_debug_unopt_arm64;'
alias build:android='./flutter/tools/gn --android --android-cpu arm64 --unoptimized; ninja -C out/android_debug_unopt_arm64 && ninja -C out/host_debug_unopt_arm64;'
またAndroidのビルドにはANDROID_SDK_ROOT
を書き換える必要があります。筆者は次のように記述し、コメントアウトを切り替えることで対応しています。
# export ANDROID_HOME=~/Library/Android/sdk
export ANDROID_HOME=$HOME/packages/flutter/engine/src/flutter/third_party/android_tools/sdk
export ANDROID_SDK_ROOT=$ANDROID_HOME
Webのビルドを行う場合には、先述の.gclient
ファイルに変更が必要です。
コメントにある通りcustom_vars
の設定を有効にして、gclient sync
を実行します。この設定によりEmscripten SDK(emsdk)が取得され、CanvasKitのビルドができるようになるそうです。 [5]
なおfelt
のオプションに関しては、あまり気にしなくても良いはずです。最初は時間がかかるかもしれませんが、felt build
を実行しましょう。細かくオプションをセットすることで、ビルド時間を短縮できるはずですが、ビルドキャッシュが存在すればそこまでの差にはならないはずです。
Issueの探し方
flutter/engine
に関連するIssueを探そうと思って探す人はいないと思いますが、探したい人はengineを確認すると良いでしょう。
思い通りのIssueが見つからない場合には、team-engineやtriaged-engineが付与されたIssueを探す手もあります。
大抵の場合、気がついたらflutter/engine
側の調査や修正が必要になっていた、というケースでしょう。例えば、AndroidやiOSのキーボードと連携する機能の不具合を調査していると、EditTextやUITextFieldに到達することがあります。このように、Issueが実はflutter/engine
に関連していることもあるため、engine
が付与されていなくともflutter/engine
の調査や変更が必要になるケースがあります。
おまけ
これまでに紹介した環境変数をまとめて記述しておきます。
筆者はpackages/flutter
やengine
の開発する際に、必要なコメントアウトを切り替えて運用しています。より良い運用方法をご存知の方がいれば、ぜひコメントいただきたいです!
# android
function set-studio-jdk() {
export JAVA_HOME=$1/Contents/jbr/Contents/Home
}
set-studio-jdk "/Applications/Android Studio.app"
export ANDROID_HOME=~/Library/Android/sdk
export ANDROID_SDK_ROOT=$ANDROID_HOME
# flutter
export PATH=$PATH:$HOME/flutter/bin
export PATH=$PATH:$HOME/.pub-cache/bin
# flutter dev
# export PATH=$PATH:$HOME/depot_tools
# export PATH=$PATH:$HOME/packages/flutter/bin
# export FLUTTER_ENGINE=$HOME/packages/flutter/engine/src
# export ANDROID_HOME=$FLUTTER_ENGINE/flutter/third_party/android_tools/sdk
# export PATH=$PATH:$FLUTTER_ENGINE/flutter/lib/web_ui/dev
# alias build:ios='./flutter/tools/gn --ios --simulator --unoptimized --simulator-cpu=arm64; ./flutter/tools/gn --unoptimized --mac-cpu arm64; ninja -C out/ios_debug_sim_unopt_arm64 && ninja -C out/host_debug_unopt_arm64;'
# alias build:android='./flutter/tools/gn --android --android-cpu arm64 --unoptimized; ninja -C out/android_debug_unopt_arm64 && ninja -C out/host_debug_unopt_arm64;'
# alias run:ios='flutter run --local-engine=ios_debug_sim_unopt_arm64 --local-engine-host=host_debug_unopt_arm64'
# alias run:android='flutter run --local-engine=android_debug_unopt_arm64 --local-engine-host=host_debug_unopt_arm64'
# alias run:web='flutter run --local-web-sdk=wasm_release -d chrome'
# alias run:web:wasm='flutter run --local-web-sdk=wasm_release -d chrome --wasm'
余談
flutter/flutter
にマージされたPRを通知するX(旧Twitter)アカウントがあります。
筆者はこのアカウントをフォローしており、PRがマージされたときに通知を受け取るようにしています。マージされたPRを眺めていると、「次のバージョンにはこんな変更が入るのか」ということに気づいたり、「あの機能、ついに実装されたのか」ということに気づいたりします。また、「え、あの機能バグってたの!?」と埋もれていたバグに気づくこともあるのではと。
flutter/flutter
のIssueを解決することに直接役立つわけではありませんが、コントリビューション欲を高めることができるため、フォローすることをお勧めします。
まとめ
FlutterはOSSであり、広くコントリビューションを受け入れています。この記事ではPRを中心に解説しましたが、見つけた不具合をIssueにすることもコントリビューションの一環です。
台所の包丁を研ぐような感覚で、気軽にコントリビューションしてみるのはどうでしょうか? あなたのコードが、明日のFlutterコミュニティを支えるかもしれません。
-
実装の難度で言えば
flutter/packages
の方が低い傾向にあります。しかしメンテナー不足が影響しているのか、flutter/flutter
よりも大変な印象が筆者にはあります。 ↩︎ -
~/flutter
の中のコードを直接編集するだけで、変更を試すことができます。アプリ開発とflutter/flutter
へのPRを同時に進めるには不適切なため、本格的にコントリビューションを行うためには環境を構築するべきでしょう。さっと動作チェックする分には便利なので、筆者はやりがちです。 ↩︎ -
筆者が調べたところ、
fvm
がローカルマシン内のpathを参照する仕組みはありませんでした。もしもやり方を知っている人がいれば、コメントなどで教えてください! ↩︎ -
筆者はM1 ProのMBPを利用していますが、初回ビルドには30分以上かかりました。 ↩︎
-
https://github.com/flutter/flutter/blob/master/engine/src/flutter/lib/web_ui/README.md#building-canvaskit ↩︎
Discussion