Makefile卒業 & Taskfile入門
はじめに
ある日、はてブを眺めていると以下の記事が流れてきました。
Makefile
と違ってコマンドを書きやすそうでいいなと思いました。
というわけでMakefile
での記述方法とTaskfile.yaml
での代替方法についてまとめます。
記述方法
これまでMakefile
の書き方とTaskfile.yaml
の書き方を比較していきます。
基本
元記事にもあったHello, world!
を両方でも出力するには以下のように記述します。
Makefile
の方は、ファイルを生成しないため.PHONY
という文言を付与する必要があります。
ここまではシンプルですね。
.PHONY: hello
hello:
echo "Hello, World!"
実行するときは$ make hello
です。
version: '3'
tasks:
hello:
desc: This task says Hello, World!
cmds:
- echo "Hello, World!"
実行するときは$ task hello
です。
実行可能なタスクのリスト
地味にあると便利なのが実行できるタスクの一覧です。
make
にはデフォルトでこの機能はないですが次のコマンドを組み合わせることで表示可能です。
make
では$ make
と入力した時には一番上に定義されているタスクが実行されます。
そのため、そこに一覧を表示するようなタスクを定義することで一覧表示ができます。
.PHONY: help
help: ## show commands ## make
@printf "\033[36m%-30s\033[0m %-50s %s\n" "[Sub command]" "[Description]" "[Example]"
@grep -E '^[/a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | perl -pe 's%^([/a-zA-Z_-]+):.*?(##)%$$1 $$2%' | awk -F " *?## *?" '{printf "\033[36m%-30s\033[0m %-50s %s\n", $$1, $$2, $$3}'
.PHONY: hello
hello: ## say hello ## make hello
echo "Hello, World!"
この状態で$ make
を実行すると次のようになります。
一方で、task
にはデフォルトでタスクを一覧する機能があります。
そして、default
というキーでタスクを定義すると$ task
を実行した時に呼ばれます。
それらを組み合わせることでタスクの一覧を$ task -l
とせずとも一覧できます。
注意点として、Taskfile.yaml
のdesc
キーを埋める必要があります。
version: '3'
tasks:
default:
desc: show commands
cmds:
- task -l --sort none
hello:
desc: This task says Hello, World!
cmds:
- echo "Hello, World!"
この状態で$ task
を実行すると次のようになります。
実行ディレクトリの移動
Makefile
だと1つのコマンドの中に以下のように記述する必要がありました。
.PHONY: cd-ls
cd-ls:
(cd example-dir && ls)
一方でtask
だと、ディレクトリを移動する機能が存在します。
便利ですね!
version: '3'
tasks:
cd-ls:
dir: example-dir
cmds:
- ls
定数
定数の定義は以下のように書けます。
MESSAGE := Hello
.PHONY: hello
hello:
echo "$(MESSAGE), World!"
task
では定数の定義をタスクごとか、全体で共有するかも分けることができます。
タスク全体の場合。
version: '3'
vars: { MESSAGE: 'Hello' }
tasks:
hello:
desc: This task says Hello, World!
cmds:
- echo "{{.MESSAGE}}, World!"
タスクごとの場合。
version: '3'
tasks:
hello:
desc: This task says Hello, World!
cmds:
- echo "{{.MESSAGE}}, World!"
vars: { MESSAGE: 'Hello' }
引数によって利用する定数を変更する
例えば環境ごとに実行するタスクの定数などを変えたい場合を想定しています。
Makefile
だと強引に以下のように記述することで、引数に応じて値を変えることもできます。
(本来の使い方から大きく外れている気がするので良くない気もしますが…)。
これは$ make env=dev
や$ make env=prod
のように実行できます。
ifeq ($(env),prod)
MESSAGE := "this is prod. Hello"
else ifeq ($(env),staging)
MESSAGE := "this is staging. Hello"
else
MESSAGE := "this is dev. Hello"
endif
.PHONY: hello
hello:
echo "$(MESSAGE), World!"
task
では環境変数ファイル*.env
を利用することで同じことができます。
MESSAGE="this is dev. Hello"
MESSAGE="this is prod. Hello"
version: '3'
# デフォルトの引数を与えている
# see: https://taskfile.dev/usage/#namespace-aliases
env:
ENV: '{{.ENV | default "dev"}}'
dotenv: ['{{.ENV}}.dist.env']
tasks:
hello:
desc: This task says Hello, World!
cmds:
- echo "$MESSAGE, $env World!"
requires:
vars: [env]
以下のコマンドで実行できます。
注意点としては、変数をtask
コマンドより前に記述することです。
(shellでよく使うコマンドの前に変数を定義しているだけです)
# dev環境
$ ENV=dev task hello
# デフォルトの引数を利用する場合
$ task hello
# prod環境
$ ENV=prod task hello
変数の定義
値を動的に取得する場合(shellを実行して値を取得する場合)は少し煩雑で、
以下のようにeval
を利用する必要があります。
$(eval AWS_ACCOUNT := $(shell aws sts get-caller-identity | jq -r .Account))
.PHONY: hello
hello:
echo "$(AWS_ACCOUNT)"
task
の場合はシンプルに通常のコマンドと同じように記述できます。
いいですね!
version: '3'
vars:
AWS_ACCOUNT:
sh: aws sts get-caller-identity | jq -r .Account
tasks:
hello:
cmds:
- echo "{{.AWS_ACCOUNT}}"
引数によって変数の定義コマンドを変更する
かなり気持ち悪い状況ですが、
例えばAWS_ACCOUNT
をprofile
に応じた値を取得したい場合などに使えます。
Makefile
ではifdef
というものを利用してprofile
が定義されているか判定し、
されている場合とそうでない場合とで実行するコマンドを切り替えています。
これは$ make account profile={profile-name}
で実行できます。
ifdef profile
$(eval AWS_ACCOUNT := $(shell aws sts get-caller-identity --profile $(profile) | jq -r .Account))
else
$(eval AWS_ACCOUNT := $(shell aws sts get-caller-identity | jq -r .Account))
endif
.PHONY: account
account:
echo "$(AWS_ACCOUNT)"
Taskfile.yaml
では、if
と[]
を使って実行するコマンドを切り替えています。
これは$ profile={profile-name} task account
で実行できます。
一応標準的なシェルスクリプトの範囲で収まっているので見やすいですね。
version: '3'
vars:
AWS_ACCOUNT:
sh: |
if [ "${profile:+foo}" ];
then aws sts get-caller-identity --profile $profile | jq -r .Account;
else aws sts get-caller-identity | jq -r .Account;
fi
tasks:
account:
cmds:
- echo "{{.AWS_ACCOUNT}}"
参考:bashで[ -v "$VAR" ]を使わずに変数が未定義か判定する
Taskfile.yaml
を含める
他のあるTaskfile.yaml
に他のTaskfile.other.yaml
などを含めることができます。
make
にはない機能です(あったらすいません)。
例えば、dockerに関する操作をまとめたTaskfile.docker.yaml
を作成します。
version: '3'
tasks:
build:
desc: build image
cmds:
- echo "build"
push:
desc: push image
cmds:
- echo "push"
run:
desc: run image
cmds:
- echo "run"
exec:
desc: exec container
cmds:
- echo "exec"
stop:
desc: stop container
cmds:
- echo "stop"
そして、今まで通りのTaskfile.yaml
以下のように作成します。
version: '3'
includes:
docker: ./Taskfile.docker.yml
tasks:
default:
cmds:
- task -l --sort none
silent: true
hello:
desc: This task says Hello, World!
cmds:
- echo "$MESSAGE, $env World!"
include:
の時に指定したdocker
というキーがprefixとして機能します。
$ task
を実行すると以下のようになり、$ task docker:build
でタスクを実行できます。
CI/CDについて
GitHub Actions
や各種クラウドでのパイプライン時に使う場合を考えてみます。
make
はデフォルトで入っていますが、task
はインストールする必要があります。
そこで、GitHub Actions
とAWSのCodeBuild
への導入方法もまとめます。
GitHub Actions
GitHub Actionsに関しては公式が導入方法を提供してくれています。
以下のコードを追加することで実行できます。
- name: Install Task
uses: arduino/setup-task@v1
with:
version: 3.x
公式サイトではrepo-token
を付与していますが、arduino/setup-taskを見た限りでは、
任意でよくて頻繁に利用する場合のみ付与した方が良いとのことです。
CodeBuild
CodeBuild
に関しても直接の記述はないですが、Install Scriptを参考に導入できます。
ただ、PATH
は通っていないので通す必要があります。
version: 0.2
phases:
install:
runtime-versions:
nodejs: 16
pre_build:
commands:
# install task
- sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
- export PATH=$PATH:~/.local/bin
build:
commands:
- task deploy
上記のpre_build
の内容をそのまま使うことで他のサービスへも流用できます。
おわりに
今回は今まで利用していたMakefile
を卒業して、Taskfile
に入門してみました。
CI/CDでも難なく使えるので徐々に乗り換えて行く予定です!
Discussion