背景
日頃長いコマンドを打つことが多く、
fish
のコマンド履歴に頼り切りになっています。何度も履歴を遡ると、それなりの手間です。
$ # テスト実行
$ cabal test --enable-options
$ # 特定のテスト実行
$ cabal test --enable-options --test-options '-p /SegTree/'
$ # doctest の実行
$ cabal repl --with-ghc=doctest --repl-options='-w -Wdefault'
$ # などなど
ここではタスクランナーを導入し、より簡単にコマンドを打てるようにします。
タスクランナー
haskell-jp の皆様の動向を伺いつつ、幾つかのタスクランナーを試してみました。
bash
無意識に選択肢から外していましたが、凄腕の方も bash
を使われていました。僕も
man bash
を読んだので bash script が書けます:
x
スクリプト
#!/usr/bin/env -S bash -euE
IFS=$'\n\t'
_ME="$(basename "$0")"
_cmd_help() {
cat <<EOS
${_ME} is a simple task runner
help Shows this message
doctest Generates Haddock document
test Runs cabal test
EOS
}
_cmd_doctest() {
cd "$(dirname "$0")"
cabal repl --with-ghc=doctest --repl-options='-w -Wdefault' "$@"
}
_cmd_test() {
cd "$(dirname "$0")"
cabal test --enable-tests --test-options ''"$@"
}
_run() {
if [ $# -eq 0 ] ; then
_cmd_help "$@"
return
fi
local _cmd="${1}"
shift 1
case "${_cmd}" in
'h' | 'help' | '-h' | '--help')
_cmd_help "$@" ;;
'dt' | 'doctest')
_cmd_doctest "$@" ;;
't' | 'test')
_cmd_test "$@" ;;
*)
echo "no such command ${_cmd}" ;;
esac
}
_run "$@"
使い勝手は良いと思います:
$ ./x
x is a simple task runner
help Shows this message
doctest Generates Haddock document
test Runs cabal test
$ ./x dt
$ ./x t '-p /SegTree/'
サブコマンドを
fzf
で選ぶようにしても良いですね。問題は、見ての通りスクリプトが長く、スマートではありません。よりきらびやかな世界を探してみます。
make
make
も無意識に選択肢から除外していましたが、やはり凄腕の方々が使われており、普通に便利なツールに見えます。
compilerbook (挫折) 以来ですが、
Makefile
も試してみます。
主な文法
make <target>
で定義されたコマンドを実行できます。参考:
Makefile Tutorial
target: prerequisites
command
- シェルコマンドの
$
は$$
の形でエスケープします @
を付けるとコマンドの実行履歴の表示を抑制できます
(脱線) Emacs でタブ文字を表示する
エディタの設定でタブ文字と行末の空白を表示します:
(setopt show-trailing-whitespace t)
(setopt whitespace-style '(tabs tab-mark))
(require 'whitespace)
(global-whitespace-mode 1)
この記事
を参考に、サイドバー (neotree)
ではタブ表示 (と行番号表示) を抑制します:
(defun my-neotree-setup (&rest _)
(display-line-numbers-mode -1)
(whitespace-mode -1))
:hook (neo-after-create-hook . my-neotree-setup)
準備できました:

リスト
まずはサブコマンド (target) の一覧を表示してます。 Stack overflow
からコマンドを拾ってきました:
.PHONY: help
help: ## Shows this help.
@echo 'Makefile targets'
@echo ''
@sed -ne '/@sed/!s/## //p' $(MAKEFILE_LIST)
.PHONY: doctest
doc: ## Runs doctest.
cabal repl --with-ghc=doctest --repl-options='-w -Wdefault'
.PHONY: test
test: ## Runs local test
cabal test --enable-tests --test-options "$(p)"
これで target の一覧を表示できます:
$ make
Makefile targets
help: Shows this help.
doc: Runs doctest.
test: Runs local test.
ただ target: prerequisites
の形でコマンドを書くと破綻します。また最近の
make
には --print-targets
オプションもあるとか。
エイリアスを定義する
エイリアス相当の target も作れます:
.PHONY: t
t: test
$ make t
引数を渡す
Target に引数を渡すためには、 arg=value
の形で変数定義します:
.PHONY: test
test: ## Runs local test
cabal test --enable-tests --test-options "$(p)"
あまり使い勝手は良くないですね:
$ make test p='-p /SegTree'
cabal test --enable-tests --test-options "-p /SegTree"
Build profile: -w ghc-9.8.4 -O1
In order, the following will be built (use -v for more details):
- ac-library-hs-1.1.0.0 (test:ac-library-hs-test) (file /home/tbm/dev/hs/ac-library-hs/dist-newstyle/build/x86_64-linux/ghc-9.8.4/ac-library-hs-1.1.0.0/cache/build changed)
- ac-library-hs-1.1.0.0 (test:benchlib-test) (file /home/tbm/dev/hs/ac-library-hs/dist-newstyle/build/x86_64-linux/ghc-9.8.4/ac-library-hs-1.1.0.0/cache/build changed)
強引に引数を扱うハック
も見ましたが、制限があります。
サブディレクトリからの実行
サブディレクトリからは make <target>
できませんでした。残念。
just
just もベター
make
に見えます。
エディタの設定 (Emacs)
;; https://github.com/leon-barrett/just-mode.el/blob/main/just-mode.el
(leaf just-mode) ;; :ensure t
;; https://github.com/psibi/justl.el
(leaf justl)
シェルの設定 (fish
)
if command -sq just
alias j just
end
Justfile
早速使ってみます。先程の Makefile
より綺麗です:
# shows this help message
help:
@just -l
# runs the benchmark
bench:
cabal bench --benchmark-options='--output a.html'
# generates Haddock document
doc:
cabal haddock "$@"
[private]
alias d := doc
test opts='':
cabal test --enable-tests --test-options '{{opts}}'
[private]
alias t := test
# 略
リスト表示が素敵です:

just
実際のコマンド実行も良い感じに:
$ just t
$ just t '-p /SegTree/'
$ just dt
その他メリットとしては、
$
のエスケープが必要ありませんでした。-
サブディレクトリから
just
コマンド実行すると、ルートからの実行になりました。
task
task
も良さそうですね。未だに書いたことがありませんが、 GitHub Actions
の独自言語に近そうです。
version: '3'
tasks:
doctest:
aliases: [dt]
cmds:
- cabal repl --with-ghc=doctest --repl-options='-w -Wdefault'
test:
aliases: [t]
cmds:
- cabal test --enable-tests --test-options ''{{.CLI_ARGS}}
引数を渡すには --
で区切る必要がありそうです。これだけちょっと面倒です:
$ task t -- '-p /SegTree/'
cargo-make
cargo-make
もベター
make
的なツールです。 cargo make
として実行できる他、
makers
がスタンドアローン版としてインストールされます。
Rust プロジェクトを前提にしている節はあります:
$ makers
[cargo-make] INFO - cargo make 0.37.23
[cargo-make] INFO -
[cargo-make] INFO - Build File: Makefile.toml
[cargo-make] INFO - Task: default
[cargo-make] INFO - Profile: development
[cargo-make] INFO - Execute Command: "cargo" "fmt"
`cargo metadata` exited with an error: error: could not find `Cargo.toml` in `/home/tbm/dev/hs/ac-library-hs/verify` or any parent directory
This utility formats all bin and lib files of the current crate using rustfmt.
Usage: cargo fmt [OPTIONS] [-- <rustfmt_options>...]
Makefile.toml
を書いてみます:
[tasks.bench]
alias = "b"
command = "cabal"
args = ["bench", "--benchmark-options='--output a.html'"]
[tasks.doc]
command = "cabal"
args = ["haddock", "$@"]
[tasks.d]
alias = "doc"
[tasks.test]
command = "cabal"
args = ["test", "--enable-tests", "--test-options", "${@:}"]
[tasks.t]
alias = "test"
ちゃんと使えますね:
$ makers t
$ makers t '-p /SegTree/'
サブディレクトリから実行すると、親ディレクトリの
Makefile.toml
を見つけてくれませんでした。そこは残念です。
shake
shake
も
make
の代替です。タスクランナーとしても利用できます:
{- cabal:
build-depends: base, shake
-}
import Development.Shake
import Development.Shake.Command
import Development.Shake.FilePath
import Development.Shake.Util
main :: IO ()
main = shakeArgs shakeOptions {shakeFiles = "_build"} $ do
phony "doc" $ do
cmd_ ["cabal", "haddock"]
phony "doctest" $ do
cmd_ ["cabal", "repl", "--with-ghc=doctest", "--repl-options='-w -Wdefault'"]
-- alias の代わり
phony "dt" $ need ["doctest"]
phony "test" $ do
cmd_ ["cabal", "test", "--enable-tests"]
phony "t" $ need ["test"]
呼び出し方はもう少しスマートにしたいところです:
$ cabal run Shakefile.hs -- t
リスト機能は
minad 神からもリクエストされていました:
$ cabal run Shakefile.hs -- --help
<略>
Targets:
- doc
- doctest
- dt
- test
-
引数を受け取るには
shakeArgsWith
を使うことになりそうですが、使い方を理解するのが大変そうです。
その他の選択肢
-
cargo-xtask
参考: Make your own make
nix run
感想
シンプルなタスクランナーとしては、
just
と
task
が良さそうです。特に
just
が好みだったので、僕のリポジトリには追加していくと思います。