Flutterでpubspec.yamlのversionだけ切り替える方法
はじめに
こんにちは。Flutterアプリ開発している@kazy_developerです。
Qiitaでの投稿ばかりでしたが初めてZennでの記事となります。
関わっているFlutterプロジェクトで1プロジェクトでソースコードがほぼ同じ2つのアプリを管理することがあったので、今回はそれらのpubspec.yamlのversionを分ける方法について書きます。

稀かもしれませんが、こういったケースとしては、
無料と有料でアプリを分けるケースや、一部機能だけをアプリ別で切り分けるといった場合でしょうか。
そのような1プロジェクトでソースコードがほぼ同じ2つのアプリを管理する場合、
当然それぞれのアプリごとにpubspec.yamlのversionが変わる...というより変えるはずです。
また、それぞれのアプリで接続環境が異なる場合は
さらに、App1、App2のそれぞれでDebug、Staging、Releaseのような複数のビルド環境が存在し、その環境ごとにバージョンを変えるといった場合もあるかもしれませんね。
環境を分ける方法
直接的に関係ありませんが、今回は--dart-defineやFlavorでの環境分けをしている前提で記載します。
Flutterで環境を分ける方法は色々ありますが、
詳しい環境分けについては以下の記事が大変参考になりますのでおすすめです。
本題に進みます。
pubspec.yaml内のバージョンを切り分ける方法
バージョンを切り分ける方法はいくつかありそうで、ざっと思いつくのは以下です。
1. ブランチをバージョン別で切り分ける
2. pubspec.yaml自体を環境別もしくはバージョンごとに分ける
3. pubspec.yaml内のversion行だけscriptで書き換える
1. ブランチをバージョン別で切り分ける
これは一番手っ取り早くわかりやすく難易度低めの方法。
いちいち細かな設定をプロジェクトに入れる必要はなく、一番簡単で楽な運用方法です。
ただ、ブランチが増える上、都度切り替える必要がありますし、
App1のブランチにいる状態でその同一ブランチ内でApp2をbuild/runするとなると、手動でバージョンを書き換える必要ができてきます。
動作確認の際にバージョンがどんな値でも問題なければそれでも良いですが、
例えば、
・Firebase RemoteConfigなどでバージョン管理をしている
・バージョンがその設定値未満であれば強制アップデートダイアログを表示
・その場合はアプリをいじることができない
という実装が施されていると仮定してみます。
以下のような状態。
pubspec.yamlに記載のバージョンが3.0.0の状態で、
RemoteConfigで設定しているApp1の最低バージョンが3.0.0
RemoteConfigで設定しているApp2の最低バージョンが1.0.0
現在、App1ブランチにいる状態で、App2の動作確認をするとなった時、
そのままビルドするとアウトなので、手動で3.0.0以上に変えるかApp2ブランチに移動する必要がででしまいます。
これぐらいであればブランチ切り替えせず、手動version変更でも実害はなさそうですが、そもそも変更する手間はかかってしまいます。
また、APIごとにサーバ側でアプリのバージョン管理していて、特定のバージョンからのリクエストは受付ないもしくは、レスポンスの中身が変わるなどあるならなおのこと頻繁には触りたくないものです。
2. pubspec.yamlを環境別もしくはバージョンごとに分ける
次にpubspec.yamlごと分ける方法。
project配下に環境別もしくはバージョン別に
pubspec_app1.yaml
pubspec_app2.yaml
を用意しておいて、run/build前にscriptでpubspec.yamlに上書きする方法です。
iOSだとFirebaseのinfo.plistを環境別に差し替えたりする時にやるのと同じような感じ。
アプリや環境ごとにパッケージやプラグインがかなり違う、もしくは、それらの依存周りでパッケージバージョンを分ける必要があるケースならこれもあり。
でも今回はアプリのバージョンを変えたいだけなので微妙です。
3. pubspec.yaml内のversion行だけscriptで書き換える
というわけで今回の本題であるpubspec.yaml内の特定行の書き換えです。
ブランチ運用でも良いのですが、ブランチが増えるしマージコミット祭りになるのでこれに落ち着きそうです。
普通に考えて、アプリごとや環境別にversionだけ書き変わってくれれば要件は満たせますので、これを進めていきます。
pubspec.yamlのversionを置き換えるshell script
まず実際に置き換える為のscriptをprojectのpubspec.yamlと同階層に作っておきます。
#!/bin/sh
APP1_VERSION="3.0.0+1"
APP2_VERSION="1.0.0+1"
version=$(grep "version:" pubspec.yaml | awk '{print $2}')
current_flavor=$1
if [ "${current_flavor}" == "app1Development" ] || [ "${current_flavor}" == "app1Staging" ] || [ "${current_flavor}" == "app1Production" ]; then
version="${APP1_VERSION}"
elif [ "${current_flavor}" == "app2Development" ] || [ "${current_flavor}" == "app2Staging" ] || [ "${current_flavor}" == "app2Production" ]; then
version="${APP2_VERSION}"
fi
sed -i '' "s/version:.*/version: ${version}/" pubspec.yaml
実際に叩く時は、Flavorの値を流し込んで判定させます。
./version.sh {Flavorの値}
設定方法
全部コマンド完結するなら後述するようにmakefileなどでまとめて実行させていやれば問題ないと思います。
しかし僕の場合、基本的に動作確認の際はAndroid StudioのGUI右上のRun/Debugボタンから実行する頻度が多めです。

コマンドでflutter runを実行するのは稀ですが、aab、apkやipaを生成する時には、コマンドでflutter buildを叩きます。
(こっちの人が大半じゃないかなとは思います)
なので、
・buildはコマンドからmakefile経由で実行
・runはRun/Debug Configurationsをいじる
というやり方でいきます。
(もっと良い方法もあると思いますが)
Run/Debug Configurationsの設定
元々あるRun/Debug Configurationsはこんな感じでアプリと環境別の設定を追加していて、

コマンドじゃなくてGUIでRunする場合スクリプトは
Edit Configurations > Run/Debug Configurationsを開いて

上記の赤枠のようにShell ScriptとBefore launchを追加する必要があります。
ではShell Scriptを追加していきます。

左上の+を押して、Shell Scriptをクリック

上記のように設定しておきます。
また、Working directoryはプロジェクトのルートを指定しておきます。
これを必要分追加したらFlutterのBuildConfiguraionに戻ってBefore launchを設定します。

上記のようにBefore launchの+を押して、環境の数だけRun Another Configurationから先ほど作成したShell Scriptを選択して紐づけておけば完了です。
今後のアプリのversionはversion.sh内の値を変更して運用するようにします。
これでGUIでのRun/Debugでも環境別でverisionの切り替えができるようになりました。
build用のmakefile設定
buildのほうは、今回はmakefile使って
buildApp1Release:
./version.sh app1Production
fvm flutter build appbundle --release --flavor app1Production --dart-define=FLAVOR=app1Production
buildApp2Release:
./version.sh app2Production
fvm flutter build appbundle --release --flavor app2Production --dart-define=FLAVOR=app2Production
みたいな感じで書いておけば良いのかなと思います。
最後に
今回の方法以外にも、ネイティブ側をいじる等、様々なやり方があると思いますが、pubspec.yamlで一元管理したいのでそれらはあまり調べていません。
また、flutter buildの引数に--build-nameと--build-numberつければ上書きできますが、
これらはflutter runでは使えなかったので断念しました。
(もしかしたらできる方法があるのかもですが)
これはベストプラクティスなのかと言われるとわかりませんし、
他に良いやり方があれば変えようと思いますが、ひとまずはこれで落ち着きそうです。
Discussion