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.renderWhitespace を all にする、.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 完全ガイド にまとめてあります。
原因の切り分けチェックリスト
エラーが出た行番号を開いた上で、上から順に確認してください。
- その行、または直前のレシピ行の先頭はタブか(
cat -A/cat -etで確認) .RECIPEPREFIXを使っているMakefileではないか(使っているなら実行しているmakeのバージョンを確認)- ルール行に
:を書き忘れていないか - レシピの外にシェル構文やコメント以外の行を書いていないか
- それでも分からなければ改行コードを疑い、
file Makefileやxxd Makefile | headでCR/LFの並びを確認
さいごに
missing separator はほとんどの場合タブとスペースの取り違えです。まずは cat -A(またはmacOSなら cat -et)でエラー行の先頭を見る、それでも分からなければ .RECIPEPREFIX とmakeのバージョンを疑う、という順番で追えばだいたい解決します。変数まわりで別のエラーにハマったら Makefile変数チートシート も参考にしてください。仕様の全体像は GNU Make 4.4 日本語マニュアル にまとまっています。