Windows でも go-task を便利に使う
タスクランナーといえば GNU Make だったり npm だったりといろいろあるが、 Windows になるととたんに使えないことが多い。
が、 go-task は Windows でもかなり便利に使える。なのにあまり Windows で使っている記事が見当たらない。非常にもったいないので備忘録として自分で書いておくことにした。
参考リンク
- https://taskfile.dev/
- Usage | Task
- Goで作られたタスクランナー (makeの代わりに, go-task/task, Taskfile.yml) - いろいろ備忘録日記
- 📘Windowsにも優しいタスクランナーTaskを試してみた - Minerva
インストール手順
パッケージマネージャには Scoop を使う。
# コマンドライン
scoop bucket add extras
scoop install task
使い方
タスクの定義は Taskfile.yaml
または Taskfile.yml
に記述する。
違う名前を使うことも task -t TaskAnother.yml
で可能だが、混乱のもとなのであまりお勧めはしない。タスク定義を分割したいなら、後述する includes を使った方がいい。
task --init
を実行すると Taskfile.yaml
が以下の内容で生成される。
# https://taskfile.dev
version: '3'
vars:
GREETING: Hello, World!
tasks:
default:
cmds:
- echo "{{.GREETING}}"
silent: true
タスクを実行する場合は task <TASKNAME>
で実行できる。 task
だけで実行するとタスク名 default
のタスクが実行される。
# コマンドライン
$ task
Hello, World!
Taskfile.yaml
から silent: true
を削除すると、実行したコマンドも表示される。
# コマンドライン
$ task
task: [default] echo "Hello, World!"
Hello, World!
タスクファイルを分割する場合は includes
を使う。
分割したタスクを実行するには task prefix:taskname
とする。
# subdir/Taskfile.yaml
version: "3"
tasks:
subtask:
cmds:
- echo "{{.GREETING}}"
vars:
GREETING: '{{default "Hello, World!" .GREETING}}'
# Taskfile.yaml
version: "3"
includes:
sub: subdir/Taskfile.yaml
# コマンドライン
$ task sub:subtask
task: [sub:subtask] echo "Hello, World!"
Hello, World!
注意点
go-task がシェルスクリプトを使えるのは mvdan/sh: A shell parser, formatter, and interpreter with bash support; includes shfmt のおかげ。
ただしいくつか制限があって、実行できるのは PATH が通っている実行ファイルのみ。
IF NOT EXIST {{.DIR}} mkdir -p {{.DIR}}
とやっても "IF": executable file not found in $PATH
となってエラーになる。
※なぜか echo
は使える
バッチファイルのように使いたい場合は cmd /c "hoge"
とやればいい。
# Taskfile.yaml
tasks:
exist:
cmds:
- cmd /c "IF NOT EXIST {{.DIR}} mkdir -p {{.DIR}}"
vars:
DIR: test
また、実行したコマンドが長すぎて複数行に分けたい場合は >-
を使う。
YAML の定義上使えるはずの他の定義だと複数行として認識されなかった。
tasks:
digest:
cmds:
- >-
aws ecr describe-images --repository-name REPO_NAME --image-ids imageTag=latest
| jq -r \".imageDetails[].imageDigest\"
変数の上書き
go-task では変数が便利に使える。
以下のように変数を定義すると、変数部分だけ変えたタスクが定義できる。
# Taskfile.yaml
tasks:
default:
cmds:
- echo "{{.GREETING}}"
vars:
GREETING: '{{default "Hello, World!" .GREETING}}'
hello:
cmds:
- task: default
vars: { GREETING: "Hello, Another!" }
# コマンドライン
$ task hello
task: [default] echo "Hello, Another!"
Hello, Another!
これにより、環境別に実行したいタスクを簡単に定義できる。
# Taskfile.yaml
task:
deploy:
cmds:
- echo sls deploy --stage {{.STAGE}} --aws-profile {{.STAGE}}-profile
vars:
STAGE: '{{default "dev" .STAGE}}'
deploy-stg:
cmds:
- task: deploy
vars: { STAGE: "stg" }
deploy-prod:
cmds:
- task: deploy
vars: { STAGE: "prod" }
# コマンドライン
$ task deploy
task: [deploy] sls deploy --stage dev --aws-profile dev-profile
sls deploy --stage dev --aws-profile dev-profile
$ task deploy-stg
task: [deploy] sls deploy --stage stg --aws-profile stg-profile
sls deploy --stage stg --aws-profile stg-profile
$ task deploy-prod
task: [deploy] sls deploy --stage prod --aws-profile prod-profile
sls deploy --stage prod --aws-profile prod-profile
ちなみに別のタスクファイルのタスクを呼び出したい場合、 タスク名にプレフィックスを付ければいい。
# Taskfile.yaml
includes:
sub: subdir/Taskfile.yaml
tasks:
subhello:
cmds:
- task: sub:subtask
vars: { GREETING: "Hello, Subtask!" }
動的な変数
sh
を使うと、コマンドの実行結果を変数に格納することができる。
よく Linux で HASH=$(git log -n -1 --format=%h)
とできるのに、 Windows だと for /f "usebackq delims=" %%A in (`git log -n -1 --format=%h`) do set HASH=%%A
としかできなくて悔しい思いをしたやつだ。
# Taskfile.yaml
tasks:
hash:
vars:
HASH:
sh: git log -n 1 --format=%h
cmds:
- echo git commit hash is [{{.HASH}}].
# コマンドライン
$ task hash
task: [hash] echo git commit hash is [695e863].
git commit hash is [695e863].
ディレクトリ指定
go-task の実行ディレクトリは Taskfile.yaml
が存在するディレクトリとなる。
# Z:\src\tasktest\Taskfile.yaml
tasks:
pwd:
cmds:
- cmd /c "cd"
# コマンドライン
$ cd Z:\src\tasktest
$ task pwd
Z:\src\tasktest
$ mkdir subdir
$ cd subdir
$ task -t ..\Taskfile.yaml pwd
Z:\src\tasktest
この挙動を変更する場合は dir
を指定する。
# Z:\src\tasktest\Taskfile.yaml
tasks:
pwd:
dir: subdir
cmds:
- cmd /c "cd"
# コマンドライン
$ cd Z:\src\tasktest
$ task pwd
Z:\src\tasktest\subdir
ちなみに includes
した別ファイルのタスクの実行ディレクトリは includes 元の Taskfile.yaml
のディレクトリになる。
この挙動を変更したい場合は includes
の書き方を以下のようにする。
# Taskfile.yaml
includes:
sub:
taskfile: subdir/Taskfile.yml
dir: ./subdir
不要なタスクを実行しない
preconditions
を指定すると、条件に合致する場合のみタスクを実行することができる。
# Taskfile.yaml
tasks:
pwd:
cmds:
- echo subdir exists!
preconditions:
- sh: "[ -e subdir ]"
msg: "subdir does not exist!" # 条件に合致しない場合のメッセージ
# コマンドライン
# subdir が存在しない場合
$ task pwd
task: subdir does not exist!
task: precondition not met
# subdir が存在する場合
subdir exists!
後始末
タスクの途中で一時ファイルを出力したりして、タスク完了時にその一時ファイルを削除したりすることも可能。
# Taskfile.yml
tasks:
default:
cmds:
- echo v1.0.0>VERSION
- cmd /c "type VERSION"
- defer: cmd /c "DEL VERSION"
- cmd /c "if exist VERSION echo VERSION EXISTS!"
この defer
は golang と同じように、このタスクが完了してから実行される。
そのため、このタスクの出力は以下のようになる。
# コマンドライン
$ task
task: [default] echo v1.0.0>VERSION
task: [default] cmd /c "type VERSION"
v1.0.0
task: [default] cmd /c "if exist VERSION echo VERSION EXISTS!"
VERSION EXISTS!
task: [default] cmd /c "DEL VERSION"
タスク一覧
タスクが少ないうちは記憶しておけるが、多くなってくるとさすがにきつい。
そんな時は task -l
でタスク一覧が確認できる。ただし、各タスクに desc
で説明を記載しておく必要がある。
# Taskfile.yaml
tasks:
cmd1:
desc: This is 1st command.
cmds: echo cmd1!
cmd2:
desc: This is 2nd command.
cmds: echo cmd2!
cmd3:
cmds: echo cmd3!
# コマンドライン
$ task -l
task: Available tasks for this project:
* cmd1: This is 1st command.
* cmd2: This is 2nd command.
peco と組み合わせる
task -l
だと一覧が確認できるだけで、そこまで便利ではない。
peco/peco と組み合わせて工夫するとかなり便利になる。
# Taskfile.yaml
version: "3"
tasks:
default:
vars:
COLON:
sh: >-
tmp=$(task -l ^| peco);
if [ "${tmp:0:2}" = "* " ]; then
tmp=${tmp:2} # `*` が `echo *` とパースされるのを防ぐ
list=(${tmp/,/ });
echo ${list[0]}
else
echo
fi
TASKNAME:
sh: tmp={{.COLON}}; echo "${tmp:0:-1}"
cmds:
- cmd /c "cls" # peco の残像が残る場合の対策
- task {{.TASKNAME}}
preconditions:
- sh: '[[ "{{.COLON}}" == "{{.TASKNAME}}:" ]]'
msg: "タスクを選択してください"
cmd1:
desc: This is 1st command.
cmds:
- echo cmd1!
cmd2:
desc: This is 2nd command.
cmds:
- echo cmd2!
cmd3:
desc: This is 3rd command.
cmds:
- echo cmd3!
↓
↓
Discussion