.NET開発者じゃなくても利用できるタスクランナー fake の勧め

発端はこのポストから。

いわゆる、ビルドからデプロイ、テスト実行などを含めたタスクランナーですね。昔話をすると、いわゆる 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 を使うことは非常にまれです。と言いますか、今の今まで忘れていました。

https://fake.build/

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 経由でやりたいところなので、ここはもう少し試してみる予定。

カテゴリー: 開発 パーマリンク