Makefileの「missing separator」エラーを直す:原因はタブとスペースの取り違えがほとんど

make を実行したら

Makefile:2: *** missing separator.  Stop.

と出て止まった。原因はほとんどの場合、レシピ行(コマンドを書く行)の先頭がタブになっていないことです。まずはエラーメッセージにある行番号の行を開いて、行頭がタブかスペースかを確認してください。それで大半は解決します。タブに直したのにまだ消えない、という場合の原因もこの記事に順にまとめてあります。挙動はすべて手元の GNU Make で実際に再現して確認しています。

仕様の詳細は GNU Make 4.4 日本語マニュアル 第5章「ルールのレシピの書き方」 を参照してください。この記事はその実例・トラブルシューティング版です。

まず確認すること

エラーメッセージの Makefile:2: の部分(ファイル名と行番号)を見て、そのMakefileの該当行を開きます。多くの場合、行頭がスペースになっているだけです。

all:
    echo "hello"

上のように見た目はインデントされていても、先頭がスペースだと次のエラーになります(GNU Make 3.81・4.4.1で実機確認)。

$ make -f Makefile
Makefile:2: *** missing separator.  Stop.

先頭をタブに直せば通ります。

all:
	echo "hello"

これで直らない場合は、以下の原因を順に疑ってください。

原因1: レシピ行の先頭がスペースになっている(最多)

GNU Makeのルールでは、レシピ(コマンド)行は必ずタブ文字(または後述の .RECIPEPREFIX)で始まらなければなりません。GNU make公式マニュアルのRecipe Syntaxの節にもそう明記されています。

エディタの自動インデント設定(VS Codeの「Insert Spaces」、コピペ元がスペースインデントだった、等)でタブがスペースに化けるのがよくある事故パターンです。

見落としがちな挙動: ヒントが出るのは8スペース以上のときだけ

GNU Makeは3.78以降、このエラーにヒントを付けてくれることがあります。

*** missing separator (did you mean TAB instead of 8 spaces?).  Stop.

これが出ていれば話は早い。ところが、手元で1〜10スペースを1つずつ試してみると、ヒントが付くのは先頭が8スペース以上のときだけでした(GNU Make 3.81・4.4.1いずれも同じ結果)。7スペース以下では何も教えてくれません。

行頭のスペース数 エラーメッセージ
1〜7 missing separator. Stop.(ヒントなし)
8以上 missing separator (did you mean TAB instead of 8 spaces?). Stop.(スペース数によらず表示は常に「8」)

つまり、エディタの既定インデント幅が4スペースだと、ヒントなしの素っ気ないエラーだけが出て原因が分かりにくくなります。「missing separator とだけ言われて意味不明」というときほど、実はタブ/スペース問題である可能性が高いです。

原因2: CRLF改行(Windows / Gitのautocrlf)は単体では原因にならない

「Windowsで編集したMakefileだと missing separator が出る」という話はよく見かけます。エラーに遭遇して、まず改行コードを疑い始めた方もいるはずです。ところが実際に検証してみると、この通説はそのままでは再現しませんでした。行末がCRLF(\r\n)でも、レシピ行の先頭のタブが保たれていれば、GNU Makeはエラーを出しません。

$ xxd Makefile
00000000: 616c 6c3a 0d0a 0965 6368 6f20 2268 656c  all:...echo "hel
00000010: 6c6f 220d 0a                             lo"..
$ gmake -f Makefile
echo "hello"
hello

CRLFのMakefile(タブは維持)をGNU Make 3.81・4.4.1双方で実行しましたが、どちらもエラーなく通りました。レシピ内でバックスラッシュによる行継続を使ったケースも同様に問題なく動きました。GNU Makeは行末の \r を許容するようになっているようです。

実際にCRLF絡みで missing separator が起きるとすれば、多くは次のような合わせ技です。

  • 改行コード変換に使ったエディタやツールが、インデントも一緒にスペースへ変換していた(原因1に合流するケース)
  • ごく稀に、CRのみ(大昔のMac OS形式)の改行で保存されたファイルを読ませてしまった場合。この場合はファイル全体が1行として読まれてしまい、missing separator ではなく No rule to make target 'echo', needed by 'all'. のような別のエラーになることを確認しています。

とはいえ、Makefileの改行コードはLFに統一しておくのが無難です。.gitattributes に以下を書いておくと、Windows側のGit設定に関わらずMakefileだけはLFで保存されます。

Makefile text eol=lf
*.mk text eol=lf

原因3: 見えない文字を目で確認する

タブかスペースか、余計な文字が混ざっていないかは、エディタの表示だけでは判別しづらいことがあります。ターミナルで可視化するのが確実です。

cat -A Makefile

タブは ^I、行末は $ で表示されます(GNU coreutils = Linux、WSL、Git Bashなど)。

all:$
^Iecho "hello"$

注意: macOS標準の cat(BSD版)には -A オプションがありません。実機で確認したところ cat -et の組み合わせで同じ表示が得られます。

cat -et Makefile   # macOS(BSD cat)の場合

エディタ側で常時可視化しておく方法もあります。VS Codeなら設定の editor.renderWhitespaceall にする、.editorconfig に以下を置いてMakefileだけはタブ・タブ幅を強制する、といった対策が有効です。

[Makefile]
indent_style = tab

原因4: .RECIPEPREFIX を使ったMakefileを、対応していないmakeで実行している

GNU Make 3.82(2010年7月リリース)以降では、レシピ行の先頭文字をタブ以外に変更できる .RECIPEPREFIX という特殊変数が使えます。

.RECIPEPREFIX = >
all:
> echo "hello with > prefix"

この変数の値の最初の1文字が、makeがレシピ行の開始とみなす文字になります。空(デフォルト)の場合は標準のタブ文字のままです(GNU make公式マニュアル 6.14 Other Special Variables)。

このMakefileは新しいGNU Make(手元でHomebrew経由の4.4.1で確認)では問題なく動きますが、3.81以前のmakeで実行すると missing separator になります。.RECIPEPREFIX 自体を理解できず、タブ以外の行頭文字を単なる「タブが無い行」として扱ってしまうためです。

これは特にmacOSで起きやすい罠です。macOSに標準で入っている make はGNU Make 3.81のままで止まっています(GNU Make 3.82以降がGPLv3ライセンスになったため、Appleがそれ以降のバージョンを同梱しなくなった、という経緯です)。実機でも次の通り確認しました。

$ make --version
GNU Make 3.81
$ make -f Makefile   # .RECIPEPREFIX入り
Makefile:3: *** missing separator.  Stop.

Linux環境やDockerコンテナ向けに書かれたMakefileをmacOSでそのまま動かして missing separator になったら、.RECIPEPREFIX が使われていないか確認してください。対処法はHomebrewで新しいGNU Makeを入れて gmake として使うことです。

brew install make
gmake --version   # GNU Make 4.4.1 などが入る

make という名前のまま使いたい場合は、PATH/opt/homebrew/opt/make/libexec/gnubin を優先追加する方法がHomebrewのインストール時メッセージでも案内されます。

原因5: レシピの外に書いてしまった行

タブ/スペースの問題ではなく、そもそも行の位置や構文自体がおかしいケースもあります。

ルールのコロン忘れ

all
	echo "hello"

all:: を書き忘れると、1行目自体がルールとして認識されず、次のエラーになります(実機確認済み)。

Makefile:1: *** missing separator.  Stop.

シェルのif文をMakefileの条件分岐のつもりでトップレベルに書いてしまう

Makefileにも ifeq / ifdef という条件分岐の仕組みがありますが、これはシェルの if とは別物の、make独自の構文です。うっかりシェルのつもりで書くとエラーになります。

if [ -f config.mk ]; then
    include config.mk
fi

all:
	echo "hello"
Makefile:1: *** missing separator.  Stop.

これは if [ ... ] がmakeにとって「タブで始まらない、ルールでも変数代入でもない行」に見えてしまうためです。make独自の条件分岐の書き方は Makefileの条件分岐 ifeq/ifdef 完全ガイド にまとめてあります。

原因の切り分けチェックリスト

エラーが出た行番号を開いた上で、上から順に確認してください。

  1. その行、または直前のレシピ行の先頭はタブか(cat -A / cat -et で確認)
  2. .RECIPEPREFIX を使っているMakefileではないか(使っているなら実行しているmakeのバージョンを確認)
  3. ルール行に : を書き忘れていないか
  4. レシピの外にシェル構文やコメント以外の行を書いていないか
  5. それでも分からなければ改行コードを疑い、file Makefilexxd Makefile | head でCR/LFの並びを確認

さいごに

missing separator はほとんどの場合タブとスペースの取り違えです。まずは cat -A(またはmacOSなら cat -et)でエラー行の先頭を見る、それでも分からなければ .RECIPEPREFIX とmakeのバージョンを疑う、という順番で追えばだいたい解決します。変数まわりで別のエラーにハマったら Makefile変数チートシート も参考にしてください。仕様の全体像は GNU Make 4.4 日本語マニュアル にまとまっています。