Flutter:Flavorを使ってビルドを切り替える（alpha, product）


# 前置き
- Flutterにおけるビルド種類分け、切り替えについての説明です。
- Mac / windowsを対象としたデスクトップ向けのアプリケーションを想定しています。
- C++ & Dartで構成されたアプリケーションです。
- 開発環境はvscodeです。

# Flutterのビルドの仕組み

androidのビルドの仕組みと似ています。
`ビルドタイプ x flavor`の掛け合わせになっています。

## Flavorとは？
androidには、`ビルドバリアント`という仕組みが存在します。

https://developer.android.com/studio/build/build-variants?hl=ja

そこでは、`ビルドタイプ x product flavor`の掛け合わせがビルドの種類をさし示します。
Flutter関連の記事は未確認ですが、Flutterもこの流れを組んでいる、と思われます。
androidで言えば、

- debug
- release

という２つのビルドタイプが存在し、それに加えて、product flavorが存在します。
仮に

- alpha
- beta
- product

という３つの product flavorが存在する時、それの`掛け合わせ`となるため、

- debugalpha
- debugbeta
- debugproduct
- releasealpha
- releasebeta
- releaseproduct

という`6つのビルドの種類`ができる、ということになります。

### Flavorを使った場合、どのようなビルドになるか？
Flutterのビルドタイプは、３つ存在します。

- debug mode
- profile mode
- release mode

https://docs.flutter.dev/testing/build-modes

profile modeは、debug <-> releaseの間に位置するタイプです。
ある程度ソースコードのデバッグは可能ですが、より release modeに近い環境でデバッグするのを目的としているように思います。

つまり、これら`既存の３種類 x flavor`との掛け合わせとなるわけです。
本記事では、flavor typeに、

- alpha
- product

という２種類のタイプを定義して、それらをどのように切り分けるのかを書いていきます。
イメージとしては下記の整理としています。

|ビルドタイプ|Flavor|備考
|--|--|--|
|debug|alpha||
|profile|product|場合によってはalphaでもいいかな？|
|release|product||

掛け合わせの最大数としてはもっと定義はできますが、今回は必要最低限の種類として考えました。

# 実際にビルドを分けていく
方法は色々ありますが、dart-define-from-fileを使ってビルド分けするのが妥当だと思います。
こちらの記事が参考になりました。

https://zenn.dev/blendthink/articles/392607db0a65dd

## dart-define-from-fileはどのように使うか
タイトルそのままなのですが、ビルドのタイプに応じて、`設定ファイルを切り替える`、というふうに使います。
なので、from-fileなわけですね。

## 用意するファイル
ビルドの設定ファイルは、jsonで用意します。
そしてそれをビルド時に、引数として与えることでビルドの切り替えを行います。

```
flutterProject_Root
├──flavor
│   ├── alpha.json
│   └── product.json
....
```
ファイル名は、flavor名にしておくと良いでしょう。

```alpha.json
{
    "flavor": "alpha",
    "appName": "sample-alpha",
    "appIdSuffix": ".alpha"
}
```

```product.json
{
    "flavor": "product",
    "appName": "sample",
    "appIdSuffix": ""
}
```

基本はこれだけです。

### コマンドでは実際にどのようにビルドするのか

`ビルド`
```
flutter build macos --dart-define-from-file=flavor/alpha.json
```

`実行`
```
flutter run --dart-define-from-file=flavor/alpha.json
```

合わせて、--debug or --release などを追加すると、より明確にビルドができます。


### debug時、launch.jsonでの書き方

vscodeでのデバッグを想定して書いています。
vscode環境で、デバッグ実行するとき、launch.jsonでデバッグしますが、その記載例です。

```launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      // mac向けデバッグ
      "name": "(lldb) Launch",
      "type": "cppdbg",
      "request": "launch",
      "osx" : {
        "program":"${workspaceFolder}/build/macos/Build/Products/Debug/hogehoge.app",
        "MIMode": "lldb"
      },
        "args": [],
        "stopAtEntry": false,
        "cwd": "${workspaceFolder}",
        "environment": [],
        "externalConsole": false,
    },
    {
      // dart debug
      "name": "hogehoge",
      "request": "launch",
      "type": "dart",
      "flutterMode": "debug",
      "args": [
        "--dart-define-from-file=flavor/alpha.json"
      ],
      "windows": {
        "env": { "FLUTTER_FLAVOR" : "alpha" },
      }
    },
    {
      // profile dart debug
      "name": "hogehoge (profile mode)",
      "request": "launch",
      "type": "dart",
      "flutterMode": "profile",
      "args": [
        "--dart-define-from-file=flavor/alpha.json"
      ],
      "windows": {
        "env": { "FLUTTER_FLAVOR" : "product" },
      }
    },
    {
      // release flutter run
      "name": "hogehoge (release mode)",
      "request": "launch",
      "type": "dart",
      "flutterMode": "release",
      "args": [
        "--dart-define-from-file=flavor/product.json"
      ],
      "windows": {
        "env": { "FLUTTER_FLAVOR" : "product" },
      }
    }
  ]
}
```

このように記載します。
windows版についての定義をここで先に見せていますが、次の項でどうしてこのような書き方をしているのか説明します。

## windows版についてのビルドの仕方

windows 版の場合は、一工夫、必要になっています。
```launch.json
...
"windows": {
        "env": { "FLUTTER_FLAVOR" : "product" },
      }
...

```
どうしてこのような定義が必要なのでしょうか？
これは、私が作ったプロジェクトの都合ですが、今作っているプロジェクトが、
`C++ & Dartで構成されたプロジェクト`であるためです。

### windowsにおけるC++のビルド

windowsにおけるC++の呼び出し方法については、割愛します。
下記記事など参考にされると良いと思います。

https://engineering.cocone.io/2023/05/22/flutter-about-platform-calls/


Flutter におけるC++のビルドは、Macではそこまで問題がありません。

Flutterにおけるnative層は、ffiを使って呼び出します。
これは、web版では動作しませんが、macos、windowsでは動作することが可能です。

https://api.flutter.dev/flutter/dart-ffi/DynamicLibrary-class.html

ライブラリとして呼び出すわけですが、windows版の場合は、dllのPathなどを指定して、開く必要があります。
```dart
....
static late DynamicLibrary hogeLib;
hogeLib = Platform.isWindows
        ? DynamicLibrary.open(windowsDLLPath)
        : DynamicLibrary.process();
....
```

そのため、C++のnative層は、dartでビルドする前に、用意しておく必要がある分けです。

### windowsにおけるdart-define-from-fileの落とし穴

C++の部分は、DLLとして用意することにしました。

1. Cmakeにてvisual studioのプロジェクトを生成し
1. それをビルド(DLL生成)
1. dartの部分をビルドして、Flutterをビルド

ただ、この工程での問題は、
dart-define-from-fileを使ってビルドする時に与える、

```launc.json
"args": [
        "--dart-define-from-file=flavor/alpha.json"
      ],
```
このargsを渡せないことにあります。
CMakeでのビルド時に、alpha.jsonで定義している

```json
"flavor": "alpha",
```

の要素を渡したいわけですが、これがCMake側で認識することができないのです。
そこで、`flavor`という値を渡すことを諦めることにしました。

代わりに、

1. ビルド時に環境変数`FLUTTER_FLAVOR`の値を定義
1. CMakeで環境変数をチェック
1. CMakeで環境変数でAlphaが立っていたら、マクロ`FLAVOR_ALPHA`定義
1. C++側では、定義されているマクロによってビルドを分ける

という流れにすることにしました。

```CMakeLists.txt
if ($ENV{FLUTTER_FLAVOR} MATCHES "alpha")
  target_compile_definitions(${BINARY_NAME} PRIVATE "FLAVOR_ALPHA")
endif ()
```

```hoge.h
#ifdef FLAVOR_ALPHA
#else
#endif
```

こんな感じで場合分けします。

# まとめ

alpha or productにおけるビルド種類の切り替えについて紹介しました。
windows版のビルドが若干めんどくさいですが、ここら辺がより簡単になると良いかな〜と思います。