そこそこの規模があるプロジェクトで実行すべきタスクを定義するとき、初手として Makefile を使いがち。
- Pros
- make は事実上どんな環境にもあることを期待してよい
- シェルで実行されるコマンドをそのまま書ける
- タスクの依存関係が明示できる
- Cons
- make では positional arguments が使えない
- 少し複雑なことをしようとすると Makefile 専用の文法を覚える必要がある
- 現代では、ファイルベースのタスクの依存関係は make が発明されたころほどは必要ではない
- Docker とか Go とか Webpack がよしなにしてくれることが多い
例: docker compose のラッパー
ちょっとしたコマンドのラッパーを書きたいことがある。Makefile を書きはじめたらすべてのエントリポイントを make
にしたい。ということで、以下のような Makefile を発明していた。docker compose
のラッパーで、ステージを指定すると docker-compose.*.yaml も読み込んでくれるようなやつだ。
が、make compose STAGE=test COMPOSE_COMMAND='run --rm app go test'
と名前付きパラメタとして渡さなきゃいけないのが面倒。
# Makefile COMPOSE_COMMAND = DOCKER_COMPOSE_BIN = docker compose DOCKER_COMPOSE = $(if $(STAGE),$(DOCKER_COMPOSE_BIN) -f docker-compose.yaml -f docker-compose.$(STAGE).yaml,$(DOCKER_COMPOSE_BIN)) .PHONY: compose compose: $(DOCKER_COMPOSE) $(COMPOSE_COMMAND)
google/zx を使う
そもそも make への引数を渡しかた自体あまり自明じゃない気もするし、そろそろ別のものにしてもいいなってことで代替を考える。最近は JavaScript(Node)がユニバーサルなのでまあこれでいいよね。個人的には JS がコードベースに含まれないプロジェクトでもビルドスクリプトのために Node を導入してもいいと思ってる。
代替として make の利点、「シェルコマンドをそのまま書ける」を潰さないように google/zx を使うことにする。シェルスクリプトっぽく JS を書くためのライブラリ集とラッパーコマンドという感じ。
GitHub - google/zx: A tool for writing better scripts
これのインストールのために npm/yarn を使うわけなので、エントリポイントは npm scripts にしてしまおう。自動的に positional arguments も使えることになる。
複雑なものは scripts/ 以下に
package.json に複雑なコマンドを書くことは不可能なので、リポジトリの scripts ディレクトリなどに zx 向けのコマンドを置くことになる。愚直に書くならこんな感じかな。$
でコマンドを実行できたり、トップレベルで await
が使えたり、argv
が最初から提供されていたりと書きやすい。
#!/usr/bin/env -S npx zx // vim: set ft=javascript: const docker_compose = argv.stage ? [ "docker", "compose", "-f", "docker-compose.yaml", "-f", `docker-compose.${argv.stage}.yaml`, ] : ["docker", "compose"]; await $`${docker_compose} ${argv._.slice(1)}`;
(ちなみにこれはほんとうに初期のバージョンで、docker compose
に渡したいフラグが minimist に食われないようにしたり、TTY が必要なコマンドを実行したり、みたいなことを考えると zx ではなく普通に node のスクリプトを書くことになる……。)
これを package.json に書いちゃえばいい。
{ "scripts": { "compose": "zx ./scripts/compose" } }
% yarn compose --stage=test ps $ zx ./scripts/compose --stage=test ps # yarn の出力 $ docker compose -f docker-compose.yaml -f docker-compose.test.yaml ps # zx の出力 ...
ちなみに #!/usr/bin/env -S npx zx
でファイルを直接起動することもできるようになる。
細かいのはひとつのファイルにまとめる
まあこれでやりたいことは満たせるわけだけど、シェルスクリプトで2行くらいのものをひとつひとつファイルにするものナー。そういう場合はひとつのファイルにまとめてしまえばいい。zx にはなぜか Markdown に書かれたスクリプトを実行する機能があるので、scripts/README.md などにこんなことを書いてしまう。
# scripts
いろんなタスクをまとめたファイルだよ~
```javascript
const tasks = {};
```
## test
テストを実行するよ~
```javascript
tasks["test"] = async () => {
await $`go test ./...`;
}
```
## おわりに
```javascript
const task = tasks[argv._[1]];
if (!task) {
// タスクがなかったら一覧を表示してやる
await $`cat ${__filename} | sed 's/^## //p;d'`;
} else {
await task();
}
```
こんなコードを書いとくと、zx ./scripts/README.md test
などでテストが実行できてしまう。package.json にはコメントを書きづらいので、ここに説明が書けるのも便利だ。