makefileにhelpを付けるワンライナー

makeファイルはデフォルトではhelpが存在しない。bashの補完の設定などを入れているとタブで利用可能なタスクの一覧が出る環境もあるが、その設定もしていない場合には利用可能なタスクの一覧も表示できない。

タスクに対するcommentの流派

似たようなことを考える人はいるもので、helpというタスク(ターゲット)を定義してhelpメッセージを表示しようという試みをしている人たちがいる。

ちょっとだけ検索してみた所以下の2つの流派があるみたい。

<task>: ## <comment>

## <comment>\n<task>:

<task>: ## <comment> 派が多いように見えたけれど、概ね元となる一番上の記事のコピーのようだった。

marmelab.com

雰囲気

前者は(<task>: ## <comment> 派) 以下の様に書く形。依存するターゲットがあった場合には ## の前に書く。

task0:  ## comment of task0
  .. do something

task1:  ## comment of task1
  .. do something

task2: task0 task1  ## comment of task2
  .. do something

後者(## <comment>\n<task>: 派)は以下の様に書く形。依存の記述方法で特に気をつけることは存在しない。

## comment of task0
task0:
  .. do something

## comment of task1
task1:
  .. do something

## comment of task2
task2: task0 task1
  .. do something

個人的には、後者(## <comment>\n<task>: 派)の方が良いのだけれど。これを導入するために新たなコマンドのインストールが必要というのは微妙だなーと思ったりした。

.DEFAULT_GOALMAKEFILE_LIST

ちなみにこれはtips的な話だけれど。両者は共に.DEFAULT_GOALMAKEFILE_LISTを使っている。

.DEFAULT_GOAL

.DEFAULT_GOAL は引数無しで make を実行した時に実行されるターゲットのこと。

.DEFAULT_GOAL := bar

foo:
   @echo foo
bar:
   @echo bar

ちなみにこれは、-p (--print-data-base)オプションで調べられる

$ make -p | grep -i default_goal
.DEFAULT_GOAL := bar

そして通常は先頭のタスク。

--- 00defaultgoal/Makefile   2019-04-15 21:58:29.207999840 +0900
+++ 01defaultgoal/Makefile    2019-04-15 21:58:24.384597546 +0900
@@ -1,5 +1,3 @@
-.DEFAULT_GOAL := bar
-
 foo:
    @echo foo
 bar:

.DEFAULT_GOAL := bar の指定がない時にはfoo

$ make -p | grep -i default_goal
.DEFAULT_GOAL := foo

MAKEFILE_LIST

これはmakeで渡されたファイルのこと。通常はMakefileなりmakefileなどが入る。ファイル名を気にせずタスクを記述できるようになっている。

build.mk

default:
  echo @@ $(MAKEFILE_LIST) @@

例えば以下の様に -f 付きでファイルを指定したときなどに値が変わる。

$ make -f build.mk
echo @@  build.mk @@
@@ build.mk @@

ちなみに同様のものとして $(MAKE) というものもある。

## <comment>\n<task>:ワンライナー

個人的には ## <comment>\n<task>: の方がMakefileとしては扱いやすい気がするので好みなのだけれど、気持ちparseするのが面倒なので正直な所unixのコマンドの範囲だとツライ。なにかテキトーなLLでのワンライナーでやってしまいたくなる。

このあたりになるとお里が知れるという感じになるかもしれない。

python自体はワンライナーに向いている言語ではないけれど、慣れているのでpythonでやってみる。rubyで言うeash_consに似たものを作るのにitertoolsのteeとchainを使うのはかなりオーバーエンジニアリング(?)感がある。まぁどこにでもpythonくらいは入っていそうなので。

help:
   @cat $(MAKEFILE_LIST) | python -u -c 'import sys; import re; from itertools import tee,chain; rx = re.compile(r"^[a-zA-Z0-9\-_]+:"); xs, ys = tee(sys.stdin); xs = chain([""], xs); [print(f"""\x1b[36m{line.split(":", 1)[0]:20s}\x1b[0m\t{prev.lstrip("# ").rstrip() if prev.startswith("##") else "" }""") for prev, line in zip(xs, ys) if rx.search(line)]'

一応こんな感じで表示される様になる。

helpの例

ちなみに個人的にはソートされずにファイルに記述された順序でhelpメッセージが出るほうが好きだったりします。