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