ADVENT CALENDAR 2019

.NETビルドスクリプトFAKEの紹介

By wraikny

これは AmusementCreators 2019 アドベントカレンダー その1 の5日目の記事です。

はじめに

ビルドスクリプト?

GNU Makeに代表されるような、複雑なビルド作業を自動化する簡易的なプログラムを言います。 Visual Studioであればビルド前後にコマンドを実行する機能があったり、*.sh*.cmdファイルを書いて実行することもありますが、基本的にOS等に依存してしまいます。

PythonやRubyで書く場合もあると思いますが、実行環境を別途インストールする必要があったり、その性質上コード量が多くなればメンテナンスも大変になります。 また、.NETの開発であればすべて.NETで済ませたいという気持ちもあります。

今回は.NET上で動く.NET向けのビルドスクリプトFAKE(F#製Make)を紹介していきます。 FAKEはF#の拡張なので、nugetのライブラリを読み込んで利用することもできます。

また、VisualStudioやdotnetコマンドと問題なく共存するため、あくまでも追加機能に過ぎません。

例としてこのようにシンプルな記述ができます。

Target.create "Clean" <| fun _ ->
  !! "src/**/bin"
  ++ "src/**/obj"
  |> Shell.cleanDirs 

Target.create "Build" <| fun _ ->
  !! "src/**/*.*proj"
  |> Seq.iter (DotNet.build id)

"Clean" ==> "Build"
Target.runOrDefault "Build"

それでは、使っていきましょうか。

今回解説するコードはこちらのリポジトリで公開しています。 wraikny/ACAC2019-05 - GitHub

環境

.NET Core SDK 3.0以上が必要です。
未インストールの方はこちらから
https://dotnet.microsoft.com/download

$ dotnet --version
3.0.100

また、VSCodeやVimを使っている方は、Ionideという拡張機能をインストールすることで補完等が効くようになります。 自分はVSCode + Ionideを使っていてオススメです。

そして、FAKEを使用する際のテンプレートをインストールします。

$ dotnet new -i "fake-template::*"

FAKE

FAKEのウェブサイトを見てみると

A DSL for build tasks and more

とありますが、実体はF#スクリプトに演算子の追加などをして拡張したものです。

とにかく、使ってみましょう。

セットアップ

新たなプロジェクトを作ってやっていきます。

$ mkdir ACAC2019-05
$ cd ACAC2019-05
$ dotnet new fake
The template "FAKE - Template" was created successfully.

Processing post-creation actions...
Template is configured to run the following action:
Description: update to latest version
Manual instructions: Run 'dotnet tool update fake-cli'
Actual command: dotnet tool update fake-cli
Do you want to run this action (Y|N)?
Y
Running command 'dotnet tool update fake-cli'...
Command succeeded.

以下の階層が出来上がります。

ACAC2019-05
|--.config
|  |--dotnet-tools.json
|--build.fsx
|--fake.cmd
|--fake.sh

.gitignoreVisualStudio.gitignore - GitHubを使うと良いでしょう。 VSCodeの.gitignore拡張から追加するのも楽ですね。

プロジェクト作成

.NET Coreを使用してプロジェクトを作成しましょう。

$ dotnet new console -o src/TestApp

FAKEでビルドします。

> fake.cmd build # Windows
$ ./fake.sh build # sh
$ dotnet fake build # 直接実行する場合

FAKEでデフォルトでは./src以下にあるプロジェクトをビルドする設定になっているので、適切にできていればビルドが成功するはずです。

なお、.NET Coreに慣れていない人向けに説明すると、dotnetコマンドだけなら以下で良いです。 .NET Coreを利用する多くの場合はこちらで十分だと思います。

$ dotnet build src/TestApp -c Debug # Debugモードでビルド
$ dotnet run src/TestAPp -c Release # Releaseモードで実行

build.fsx

FAKEではbuild.fsxにスクリプトを記述します。 エディタで開いてみましょう。

なお、好みで2インデントにしています。 Tabは使えません。

Target.createで、処理の単位に名前をつけています。

Target.create "Clean" (fun _ ->
  !! "src/**/bin"
  ++ "src/**/obj"
  |> Shell.cleanDirs 
)

Target.create "Build" (fun _ ->
  !! "src/**/*.*proj"
  |> Seq.iter (DotNet.build id)
)

Target.create "All" ignore

!!演算子と++演算子でパスを指定しています。

また、以下で依存関係を記述しています。

"Clean"
  ==> "Build"
  ==> "All"

AllにはBuildが必要で、Buildには Clean が必要という構造です。

リソースフォルダを扱ってみる

Altseedを使ってゲームを作る場合、Debug時はResourcesフォルダ、Release時はResources.packファイルを参照する事が多いですね。

bin以下に直接置くのは怖いですし、プロジェクトルートに置いて自動的にコピーやパッキングをしたくなります。

FAKEのShell経由でFilePackageGeneratorを呼び出してみましょう。

CUI版のあるAltseed1.1.5.3を使用します。 なお、Windows以外の場合はMono Frameworkが必要になります。 (本来はdll参照で直接呼び出せるといいのですが……)

toolフォルダ以下にファイルを置きましょう。

tool
|--FilePackageGenerator.exe
|--FilePackageGeneratorCore.dll

FilePackageGenerator.exeを呼び出す関数を作ります。 Helperというモジュールを作って、そこに以下の型を持つ関数packResourcesを追加しました。

val packResources : target : string -> output : string -> password : string option -> unit

実装は記事末尾に載せておきます。

この関数を呼び出すために、新たにResourcesというターゲットを作ります。

ResourcesフォルダをDebugにはコピー、Releaseにはパッキングを行っています。

let targetProject = "TestApp"
let resources = "Resources"
let password = Some "password"

Target.create "Resources" (fun _ ->
  let outDir x = sprintf "src/%s/bin/%s/netcoreapp3.0" targetProject x

  // for Debug
  let dir = outDir "Debug"
  let target = dir + "/" + resources
  Directory.create dir
  Directory.delete target |> ignore
  Shell.copyDir target resources (fun _ -> true)
  Trace.trace "Finished Copying Resources for Debug"

  // for Release
  let dir = outDir "Release"
  let target = sprintf "%s/%s.pack" dir resources
  Directory.create dir
  Helper.packResources resources target password
  Trace.trace "Finished Packing Resources for Release"
)

FAKEのDirectoryShellの関数はプラットフォームの差異を吸収してくれて楽です。 各関数の使い方はググるリファレンスを見てください。

なお通常の.NETのメソッドを使いたい場合は、openの前に以下のように記述することで呼び出せるようになります。

#r "netstandard"

では、これを実行していきましょう。 まず仮のフォルダを作りました。

ACAC2019-05
|--Resources
|----nyan.txt

それから、ターゲットを指定してFAKEを呼びます。

$ dotnet fake build -t Resources

src/TestApp/bin以下を見れば、ファイルのコピーが成功しているのがわかるでしょう。

まとめ

Shellを直接書くのは大変です。 .NET Core SDKがあるだけで使えて、プラットフォームの差分も吸収してくれるFAKEを使うと簡単にビルドスクリプトが記述できて嬉しいですね。

今回紹介した以外にも多くのことが楽にできるので、特にF#ユーザの方は使ってみるとよさそうです。

FAKEのリファレンスはこちら
https://fake.build/apidocs/v5/index.html

今回のリポジトリはこちら
wraikny/ACAC2019-05 - GitHub

補足:packResources関数の実装

Windows以外ではmonoコマンド経由で実行する必要があり、その差分を吸収しています。

また、扱いやすいようにもしています。

module Helper =
  let shellExec cmd args =
    let args = String.concat " " args

    Shell.Exec(cmd, args) |> function
    | 0 -> Trace.tracefn "Success '%s %s'" cmd args
    | code -> failwithf "Failed '%s %s', Exit Code: %d" cmd args code

  let runNetExe cmd args =
    if Environment.isWindows then
      shellExec cmd args
    else
      shellExec "mono" (cmd::args)

  let packResources target output password =
    let cmd = "./tool/FilePackageGenerator.exe"

    runNetExe cmd [
      yield target
      yield output

      match password with
      | Some(x) -> yield (sprintf "/k %s" x)
      | None -> ()
    ]

参考

SHARE THIS POST