発端はこのポストから。
いわゆる、ビルドからデプロイ、テスト実行などを含めたタスクランナーですね。昔話をすると、いわゆる Makefile を使ったビルドが Java 環境で辛くなり始めたとき Ant が出てきて流行りました。中味は XML 形式で書かれていて、条件分岐や依存関係などが Makefile よりも書きやすいという触れ込みです。それを受けて、MSBuild が出来たわけですが、実は今となっては XML 形式というのは書きづらく、JSON 形式か YAML 形式(Docker など)が流行ったりしています。
一方で、Makefile を C 言語風に書くということで C++ 界隈では CMake が使われます。CMake は OpenCV などをビルドするときに外せない要素で結構複雑怪奇なのですが、関数形式であることと複数の Makefile をひとつにまとめられるという依存関係で重宝されています。
さらに傍流のところに Fake があります。Fake は F# 専用の(という訳でもないのですが)の Make ファイルで、ちょうど Ant/MSBuild の集約の仕方と CMake の依存関係をうまく吸収しています。とはいえ、F# 界隈でしか使われいないし、F# を使っていても実際のところは *.fsproj を使うので Fake を使うことは非常にまれです。と言いますか、今の今まで忘れていました。

fake コマンドをインストールする
いまひとつ、インストールの仕方が解り辛いのですが、以下のように dotnet コマンドで tool-manifest を作ってから fake-cli をグローバルにインストールします。npm でツールをインストールときと同じです。本来ならば、ローカルに入れたいところなのですが、パスを作るのが powershell では面倒なので -g でグローバルに入れたほうが楽です。
fake.build/guide/getting-started.html
dotnet new tool-manifest
dotnet tool install fake-cli -g
.dotnet/tools の中に fake.exe があります。fake.exe 自体は .NET 6 なんですが、まあ無視します。
PS H:\kitami\FolkBearsGroup\folkbears-android\FolkBears> gcm fake
CommandType Name Version Source
----------- ---- ------- ------
Application fake.exe 6.1.0.0 C:\Users\masuda\.dotnet\tools\fake.exe
バージョンはこんな感じです。
PS H:\kitami\FolkBearsGroup\folkbears-android\FolkBears> fake --version
FAKE 6 - F# Make (6.1.3.0) (running on .NETCoreApp,Version=v6.0) (this line is written to standard error, see https://github.com/fsharp/FAKE/issues/2066)
FakePath: C:\Users\masuda\.dotnet\tools\.store\fake-cli\6.1.3\fake-cli\6.1.3\tools\net6.0\any\Fake.Runtime.dll
Paket.Core: 8.1.0
build.fsx を作成する
Makefile にあたるものを作成します。コマンドとしては dontet new fake でひな形が作成できるのですが、https://fake.build/guide/getting-started.html にあるコードをコピペしても構いません。
#r "paket:
nuget Fake.Core.Target //"
// include Fake modules, see Fake modules section
open Fake.Core
// *** Define Targets ***
Target.create "Clean" (fun _ ->
Trace.log " --- Cleaning stuff --- "
)
Target.create "Build" (fun _ ->
Trace.log " --- Building the app --- "
)
Target.create "Deploy" (fun _ ->
Trace.log " --- Deploying app --- "
)
open Fake.Core.TargetOperators
// *** Define Dependencies ***
"Clean"
==> "Build"
==> "Deploy"
// *** Start Build ***
Target.runOrDefault "Deploy"Target.create で、ターゲット名に従ってコマンドを書いてきます。大抵はShell.Exec でコマンドを実行します。依存関係が “Clean” ==> “Build” ==> “Deploy” のところで、ここではデフォルトで “Deploy” が実行されます。
実行するときは fake build のようにします。build は build.fsx のことなので、deploy.fsx とか setup.fsx とかも作れるはずです。
fake build
Android のビルドに使ってみる
では、手元の Android プロジェクトで使ってみましょう。手元の FolkBears プロジェクトは Kotlin で書かれているので、IDE エディタは Android Studio を使っています。コマンドラインからは gradlew コマンドを使いビルドができます。実機へのデプロイは adb コマンドを使います。
Gradle を利用してデプロイなどの機能を修正してもよいのですが、これを fake で作ります。
build.fsx
#r "paket:
nuget Fake.Core.Target
nuget Fake.Tools.Shell //"
#load "./.fake/build.fsx/intellisense.fsx"
open Fake.Core
Target.create "Clean" (fun _ ->
printfn "Cleaning..."
)
Target.create "Build" (fun _ ->
printfn "Building..."
// gradlew でビルド
Shell.Exec("./gradlew.bat", "assembleDebug") |> ignore
// Shell.Exec("gradlew", "build")
)
Target.create "Default" (fun _ ->
printfn "Default target実行中..."
printfn "gradlewでAndroidアプリをビルドします"
// gradlew でビルド
let result = Shell.Exec("./gradlew.bat", "assembleDebug")
if result = 0 then
printfn "ビルド成功"
else
printfn "ビルド失敗"
failwith "gradlewビルドが失敗しました"
)
// Android 実機にデプロイする
Target.create "Deploy" (fun _ ->
printfn "Deploying to Android device..."
Shell.Exec("./gradlew.bat", "installDevDebug") |> ignore
)
// 特定のデバイス(25041JEGR04165)にデプロイする
Target.create "DeployToDevice" (fun _ ->
printfn "Deploying to device 25041JEGR04165..."
// 環境変数でデバイスを指定
System.Environment.SetEnvironmentVariable("ANDROID_SERIAL", "25041JEGR04165")
let result = Shell.Exec("./gradlew.bat", "installDevDebug")
if result = 0 then
printfn "デプロイ成功 (25041JEGR04165)"
printfn "アプリを起動しています..."
// アプリを起動
let launchResult = Shell.Exec("adb", "-s 25041JEGR04165 shell am start -n jp.mamori_i.app.dev/jp.mamori_i.app.screen.start.SplashActivity")
if launchResult = 0 then
printfn "アプリ起動成功"
else
printfn "アプリ起動失敗"
else
printfn "デプロイ失敗"
failwith "デプロイが失敗しました"
)
// アプリを起動する(デプロイ済みの場合)
Target.create "LaunchApp" (fun _ ->
printfn "Starting app on device 25041JEGR04165..."
let result = Shell.Exec("adb", "-s 25041JEGR04165 shell am start -n jp.mamori_i.app.dev/jp.mamori_i.app.screen.start.SplashActivity")
if result = 0 then
printfn "アプリ起動成功"
else
printfn "アプリ起動失敗"
failwith "アプリ起動が失敗しました"
)
open Fake.Core.TargetOperators
// 依存関係を削除して、Defaultが単独で実行されるようにする
Target.runOrDefault "Default"色々と書いてありますが、これは vscode 上で GitHub Copilot + Claude Sonnet を使って書き足したものです。文法はは F# なので、既存の生成 AI のモデルに学習結果が含まれています。このために、Fake の書き方も的確に書いてくれます。
vscode の拡張には、Ionide.Ionide-fsharp を入れておくと便利です。

ビルド&実機へのデプロイを試してみましょう。
fake build -t DeployToDevice

無事、ビルドとデプロイが終わると次のところまで表示がでます。

警告が出ていますが、無視して大丈夫です。
Warning: Paket resolved a FSharp.Core with version '9.0.0.0', but fake runs with a version of '8.0.0.0'. This is not supported.
Please either lock the version via 'nuget FSharp.Core <nuget-version>' or upgrade fake.
Read https://github.com/fsharp/FAKE/issues/2001 for details.
どうやら現在の環境が .NET9 なんですが、fake が .NET8 のままっぽいです。バージョンを 8 のほうに揃えれば出なくなると思いますが、これは後で。

Shell.Exec でコマンドを羅列して戻り値を見るだけなので、結構使えるはずです。難点は文法が F# なところですが、Powershell や sh をちまちま書くよりはいいかもしれません。
これは Android 版なのですが、次は Xcode で Swift 版のほうのビルドをしたいんですよね。Swift 版のほうは macOS で動かさないといけないので、VNC 経由でやるか、mac mini 用のマウスとキーボードを使うとかという処で結構面倒なのです。ビルドとテスト実行ぐらいならば windows から ssh 経由でやりたいところなので、ここはもう少し試してみる予定。
