🛠️

MakefileでPATHが反映されない時の対処法

2025/02/21に公開

はじめに

Makefileで環境変数 PATH を設定したのに、それが期待通りに反映されないという事象に遭遇したことはありませんか?💡 たとえば、which コマンドでは見つかるのに、実際のコマンド実行時に「No such file or directory」とエラーが出る。今回は、この現象の原因と、どう対処すれば良いのかを解説します。

問題の概要

次のようなMakefileがあったとします。

PATH := $(HOME)/.pub-cache/bin:$(PATH)

.PHONY: path
path:
    which fvm
    fvm --version

上記のMakefileを実行すると、次のような出力が返されます。

$ make path
which fvm
/Users/username/.pub-cache/bin/fvm
fvm --version
make: fvm: No such file or directory
make: *** [path] Error 1

whichではfvmの実行ファイルが見つかっているにもかかわらず、fvm --versionの実行時にエラーが発生しています。

原因:GNU Makeのサブシェル動作と「fast path」

GNU Makeでは、通常レシピの各行が個別のサブシェルで実行されます。これにより、各行ごとに異なるシェル環境が生成されるため、1行目で設定したPATHが次の行には引き継がれないという問題が発生します。

さらに、GNU Makeには「fast path」という仕組みがあり、特定の条件下ではシェルを介さずにコマンドが直接実行されます。これが問題を引き起こしている原因の一つです。具体的には、単純なコマンドの場合、シェルを使わずに実行されるため、PATHが正しく適用されません。

fast pathに関する参考リンク:

解決策🌟

方法1: セミコロン(;)でシェル実行を強制する

レシピの各行がシェルを使って実行されるように、行末にセミコロン ; を付けます。これにより、Makeがシェルを介してコマンドを実行し、PATHが反映されるようになります。

PATH := $(HOME)/.pub-cache/bin:$(PATH)

.PHONY: path
path:
    which fvm;
    fvm --version;

方法2: :; を使ってシェルの呼び出しを強制する

もう一つの方法として、意図的にシェルを使わせるために、何もしないコマンド : とセミコロン ; を組み合わせた :; を使うことができます。これにより、シェルが明示的に呼び出されるため、PATHの設定が確実に反映されます。

PATH := $(HOME)/.pub-cache/bin:$(PATH)

.PHONY: path
path:
    :; which fvm
    :; fvm --version

この方法は、見た目に「意図的にやっている」ことが明確になるため、特にチームで作業しているときに有用です。🔧

実行結果例

これらの解決策を適用すると、次のようにコマンドが正しく実行されます。

$ make path
which fvm
/Users/username/.pub-cache/bin/fvm
fvm --version
2.2.6

補足:macOSのGNU Makeバージョンに関するバグ🐛

macOSにプリインストールされているGNU Makeには、「fast path」に関するバグがあることが報告されています。このバグにより、Makefile上で設定したPATHが正しく反映されません。HomebrewでインストールしたGNU Make(gmake)を使用することで、この問題を回避できます。

$ gmake path
which fvm
/Users/username/.pub-cache/bin/fvm
fvm --version
2.2.6

おわりに

Makefileで設定したPATHが正しく反映されない問題は、GNU Makeの「fast path」と呼ばれる仕組みが原因で発生します。この問題に対処するためには、セミコロン ;:; を使ってシェルを強制的に呼び出すことが有効です。また、macOSでは最新バージョンのGNU Makeを使用することで、この問題を避けることができます。

MakefileでPATHの問題に遭遇した際には、今回紹介した方法をぜひ試してみてください!🚀

Discussion