[[📒Articles]] > [[📒2021 Articles]]
![[2021-08-08.jpg|cover-picture]]
[[Task]]という[[タスクランナー]]を試してみたところ、[[Make]]と比べて、特に[[Windows]]では優れているポイントがいくつもあったので記事を書いてみました。
## はじめに
[[タスクランナー]]とはタスク単位で処理を自動化するツールのことです。
[[Node.js]]なら[[npm]]、[[Rust]]なら[[Cargo]]のようにエコシステムが[[タスクランナー]]としての機能をもつケースもあります。しかし、それが提供されていない言語も多く、[[タスクランナー]]によって仕様が異なるため[[コンテキストスイッチ]]の切り替えコストも高いです。
そんな課題がある中、2015年頃に様々な環境で使える[[タスクランナー]]が話題になったことがありました。[[Make]]です。
<div class="link-card">
<div class="link-card-header">
<img src="https://www.gnu.org/graphics/gnu-head-mini.png" class="link-card-site-icon"/>
<span class="link-card-site-name">www.gnu.org</span>
</div>
<div class="link-card-body">
<div class="link-card-content">
<div>
<p class="link-card-title">Make
- GNU Project - Free Software Foundation</p>
</div>
</div>
</div>
<a href="https://www.gnu.org/software/make/"></a>
</div>
[[Make]]は[[Linux]]ベースの環境ではほぼプリインストールされているため、利用することへのハードルが低いです。`Makefile`さえ書いてしまえば実行するだけです。ところが、一部の環境では別の苦しみに悩まされていたのも事実です。[[Windows]]です。
### [[Windows]]で[[Make]]を使うことの苦しみ
[[Windows]]で[[Make]]を使うときハマりやすいポイントが2つあります。
#### 文字コード
文字コードについては言うまでもないでしょう。[[Windows]]のターミナルはエンコーディングとして[[Shift_JIS]]を採用していることが多いです。一方、その他は[[UTF-8]]です。
この性質から **`Makefile`で日本語の入出力を取り扱いとき、==文字化け==に悩まされる** ことが非常に多いです。[[Windows]]だけでしか利用しないならまだマシですが、他のOSで開発することがある場合は地獄でしょう。
#### シェル
2つ目はシェルの問題です。[[Windows]]以外の環境であればデファクトスタンダードなシェルは[[Bash]]だと思います。[[Bash]]で動くスクリプトを書いておけばほとんどの場合は問題ないでしょう。
一方、[[Windows]]の標準スクリプトは[[cmd]]です。最近は[[PowerShell]]に移行しつつあるものの、その文法は[[Bash]]と異なります。通常は[[Bash]]用に作成したスクリプトが期待通り動くことなどないでしょう。
そして、[[Windows]]ユーザーの環境に[[Bash]]を適合しようとすると、これまた混乱を招くことが多いです。詳細は割愛しますが、各々の環境下における[[Bash]]の挙動が微妙に異なるため、諦めて[[PowerShell]]で処理を書き直したことが多々あります。
## [[Task]]とは
さて、ここからが本題です。まずは[[Task]]を紹介します。
<div class="link-card">
<div class="link-card-header">
<img src="https://github.githubassets.com/favicons/favicon.svg" class="link-card-site-icon"/>
<span class="link-card-site-name">GitHub</span>
</div>
<div class="link-card-body">
<div class="link-card-content">
<div>
<p class="link-card-title">GitHub - go-task/task: A task runner / simpler Make alternative written in Go</p>
</div>
<div class="link-card-description">
A task runner / simpler Make alternative written in Go - GitHub - go-task/task: A task runner / simpler Make alternative written in Go
</div>
</div>
<img src="https://opengraph.githubassets.com/f7434eb47dfa8785af13efb144742021ba788201a349b3ecaa9f037e2b4f431e/go-task/task" class="link-card-image"/>
</div>
<a href="https://github.com/go-task/task"></a>
</div>
主な特徴は以下の通りです。
- タスク定義は[[YAML]]で書く
- シングルバイナリで各OSの[[パッケージマネージャー]]によるインストールに対応
- クロスプラットフォーム対応
クロスプラットフォーム対応なので[[Windows]]サポートもバッチシです。
> Truly cross-platform: while most build tools only work well on Linux or macOS, Task also supports Windows thanks to this awesome shell interpreter for Go;
[[インタプリタ]]として[[sh]]を使っているため、[[Bash]]がインストールされていない[[Windows]]でも[[Bash]]の処理を記述できているようです。
## [[Task]]をいじってみる
普段は[[PowerShell]]を使っていますが、ここではコマンドプロンプトを使うことにします。
### タスク定義ファイルの作成
`task --init`コマンドで一瞬です。
```cmd:
> task --init
Taskfile.yml created in the current directory
```
中身は『Hello, World!』を出力するコマンドになっています。
```yaml:Taskfile.yml
version: '3'
vars:
GREETING: Hello, World!
tasks:
default:
cmds:
- echo "{{.GREETING}}"
silent: true
```
### タスクの実行
`task`コマンドで`tasks.default`のタスクが実行されます。
```cmd:
> task
Hello, World!
```
変数`GREETING`はコマンドから指定できます。
```cmd:
> task GREETING=はろー世界!!
はろー世界!!
```
設定によっては[[タスクのコマンド定義を簡略化]]できます。ついでに`default`以外のタスクを設定してみました。
```yaml:Taskfile.yml
version: "3"
silent: true
tasks:
run: echo run
run2:
- echo run2-1
- echo run2-2
run3:
cmds:
- echo run3-1
- echo run3-2
```
`default`以外のタスクを実行する場合は、`task`コマンドのあとに指定します。
```cmd:
>task run
run
>task run2
run2-1
run2-2
>task run3
run3-1
run3-2
```
### 日本語の取り扱い
[[UTF-8]]の`Taskfile.yml`に日本語でデフォルト値を設定してみます。
```yaml:Taskfile.yml
version: '3'
vars:
GREETING: はろー世界!!
tasks:
default:
cmds:
- echo "{{.GREETING}}"
silent: true
```
コマンドプロンプトのエンコーディングは[[Shift_JIS]]ですが、文字化けせずにしっかり表示されます。
```cmd:
> task
はろー世界!!
```
ちなみに[[UTF-8]]の`Makefile`に対し、[[Make]]で実行するとこうなります。
```cmd:
> make run
縺ッ繧阪・荳也阜!!
```
## よく使う書き方
[[YAML]]の記載ルールはすべてUsageで紹介されています。
<div class="link-card">
<div class="link-card-header">
<img src="https://taskfile.dev/favicon.ico" class="link-card-site-icon"/>
<span class="link-card-site-name">taskfile.dev</span>
</div>
<div class="link-card-body">
<div class="link-card-content">
<div>
<p class="link-card-title">Usage</p>
</div>
<div class="link-card-description">
Create a file called Taskfile.yml in the root of your project. The cmds attribute should contain the commands of a task.
</div>
</div>
</div>
<a href="https://taskfile.dev/#/usage"></a>
</div>
この中で私がよく使う書き方をいくつか紹介します。
### [[Taskの環境変数設定]]
`env`で指定できます。
```yaml:Taskfile.yml
version: "3"
silent: true
tasks:
default:
cmds:
- echo Hello $NAME
env:
NAME: tadashi-aikawa
```
```cmd:
> task
Hello tadashi-aikawa
```
`dotenv`プロパティで`.env`ファイルを読み込む方法もあります。
### [[変数の設定]]
変数は`${VAR}`ではなく`{{.VAR}}`の形式で表現されます。
```yaml:Taskfile.yml
version: "3"
silent: true
tasks:
default:
cmds:
- echo Hello {{.NAME}}
```
変数は`task`コマンドの後ろで指定します。[[Bash]]のように`NAME=xxx task`と指定できるスクリプト言語もありますが、[[Windows]]の[[cmd]]や[[PowerShell]]でも互換性をとるために必要です。
```cmd:
> task NAME=tadashi-aikawa
Hello tadashi-aikawa
```
#### デフォルト値の設定
`vars`でデフォルト値を指定できます。
```yaml:Taskfile.yml
version: "3"
silent: true
vars:
NAME: World
tasks:
default:
cmds:
- echo Hello {{.NAME}}
```
一方、各タスクの中で`vars`を設定すると、`task`コマンドで指定できなくなります。これが仕様かは不明です。
```yaml:taskコマンドで指定して上書きできないNAME
tasks:
default:
cmds:
- echo Hello {{.NAME}}
vars:
NAME: World
```
#### コマンド結果の値を変数に設定
`sh: `を組み合わせることで動的な変数設定ができます。
```yaml:Taskfile.yml
version: "3"
silent: true
tasks:
default:
cmds:
- echo Hello {{.NAME}}
vars:
NAME:
sh: head -1 Taskfile.yml | cut -d' ' -f 2
```
```cmd:
> task
Hello 3
```
### [[Taskで実行するディレクトリを指定]]
デフォルトでは`cmds`で指定したコマンドは、`Taskfile.yml`の配置場所をカレントディレクトリとして実行されます。`dir`でこれを変更できます。
以下の構成で、`.vscode`配下でコマンドを実行したいとします。
```ls
.
├── .vscode
│ └── tasks.json
├── Makefile
└── Taskfile.yml
```
`dir: .vscode`を指定します。
```yaml:Taskfile.yml
version: "3"
silent: true
tasks:
default:
dir: .vscode
cmds:
- ls -l
```
```cmd:
> task
total 4
-rw-rw-r-- 1 syoum syoum 341 Aug 08 19:47 tasks.json
```
なお、以下の書き方は期待通り動きません。`cmds`の各コマンドは`Taskfile.yml`の配置場所をカレントディレクトリとして実行されるからです。
```yaml
default:
cmds:
- cd .vscode
- ls -l
```
### [[タスクの依存関係を設定]]
[[Make]]と同様にタスクの依存関係を設定できます。`deps`で指定します。
```yaml:Taskfile.yml
version: "3"
silent: true
tasks:
hello: echo Hello
sorry: echo Sorry
bye: echo Bye
default:
deps:
- hello
- sorry
- bye
cmds:
- echo "Run default"
```
実行します。
```cmd:
> task
Bye
Sorry
Hello
Run default
```
なお、いずれかのタスクを失敗させると以下のように出力され中断します。
```cmd:
> task
Bye
Hello
task: Failed to run task "sorry": exit status 1
```
`deps`で指定した複数のタスクは、`task`コマンドで指定したタスクを実行する前に[[並列]]に実行されます。そのため、==順番に依存する==複数の依存関係は指定できません。その場合は[[別のタスクを実行]]するといいでしょう。
### [[別のタスクを実行]]
コマンドに`task: `と指定するとタスクから別のタスクを実行できます。
```yaml:Taskfile.yml
version: "3"
silent: true
tasks:
hello: echo Hello
sorry: echo Sorry
bye: echo Bye
default:
cmds:
- task: hello
- task: sorry
- task: bye
- echo "Run default"
```
[[タスクの依存関係を設定]]する場合と異なり、順番が保証されます。
```cmd:
> task
Hello
Sorry
Bye
Run default
```
### 条件を満たす場合のみ実行可能にする
必須のパラメータが指定されなかったときにエラーを出す..というよくあるケースです。[[Make]]で[[クロスプラットフォーム]]対応しようとするとかなり大変ですが、[[Task]]ではエレガントに書けます。
```yaml:Taskfile.yml
version: "3"
silent: true
tasks:
release:
cmds:
- echo Release version {{.VERSION}}
preconditions:
- sh: "[ {{.VERSION}} != '' ]"
msg: "VERSION is required."
```
`VERSION`を指定しなかった場合はエラーになります。
```cmd:
> task release
task: VERSION is required.
task: precondition not met
> task release VERSION=1.2.3
Release version 1.2.3
```
実際にコマンドが実行された結果はちゃんとカラーリングされるのでエラーも分かりやすいです。
### タスク一覧と説明の表示
`task <command>`で対応している`<command>`と説明の一覧を表示するには`task -l`コマンドを使います。ただし、コマンドを一覧に表示させるには`desc`に説明文を書く必要があります。
```yaml:Taskfile.yml
version: "3"
silent: true
tasks:
default:
- task: build
build:
cmds:
- echo build
test:
desc: テストを実行する
cmds:
- echo test
run:
cmds:
- echo run
release:
desc: "成果物をリリースする (ex: task release VERSION=1.2.3)"
cmds:
- echo Release version {{.VERSION}}
preconditions:
- sh: "[ {{.VERSION}} != '' ]"
msg: "VERSION is required."
```
`test`と`release`だけに`desc`を追加しました。`task -l`の結果は以下のようになります。
```cmd:
> task -l
task: Available tasks for this project:
* release: 成果物をリリースする (ex: task release VERSION=1.2.3)
* test: テストを実行する
```
## [[YAML]]ファイルのスタイルガイド
[[Task]]には[[YAML]]のスタイルガイド、つまりフォーマットルールのようなものがあります。全部で10にも満たないので一通り見ておくといいでしょう。
<div class="link-card">
<div class="link-card-header">
<img src="https://taskfile.dev/favicon.ico" class="link-card-site-icon"/>
<span class="link-card-site-name">taskfile.dev</span>
</div>
<div class="link-card-body">
<div class="link-card-content">
<div>
<p class="link-card-title">Styleguide</p>
</div>
<div class="link-card-description">
This is the official Task styleguide for Taskfile.yml files. This guide contains some basic instructions to keep your Taskfile clean and familiar to other users.
</div>
</div>
</div>
<a href="https://taskfile.dev/#/styleguide"></a>
</div>
## まとめ
[[Windows]]にも優しい[[タスクランナー]]、[[Task]]を紹介しました。
[[Windows]]以外の面でも[[Make]]と比較して[[タスクランナー]]に便利な機能が詰まっていると思ります。[[クロスプラットフォーム]]対応やポータビリティの面も含めて移行してみる価値はあるのではないでしょうか。