前: Limitations of Usual Tools, 上: Portable Shell


10.11 Makeの制限

make自身には非常に多くの制限があるので苦労します,ここではわず かですが紹介します.とにかく,シェルによってコマンドが実行されるので,そ の弱い部分の全てが継承されていくということを覚えておいてください ....

$<
POSIXでは,makefileの構成物の`$<'は推測される規則 と`.DEFAULT'ルールのみで使用可能だと告げています.通常のルールでの その意味は明記されていません.例えば,Solaris 8のmakeはそれを 引数で置換します.
マクロ名へのアンダースコアの前置
NEWS-OS 4.2Rのように,マクロ名にアンダースコアを前置することをサポートし ていないmakeもあります.
          $ cat Makefile
          _am_include = #
          _am_quote =
          all:; @echo this is test
          $ make
          Make: Must be a separator on rules line 2.  Stop.
          $ cat Makefile2
          am_include = #
          am_quote =
          all:; @echo this is test
          $ make -f Makefile2
          this is test
     

マクロへのバックスラッシュの後置
HP-UXのバージョンによっては,makeには,バックスラッシュ以降の 複数の改行を,空ではない行も含めて読み込むものもあります.例えば以下のよ うにします.
          FOO = one \
          
          BAR = two
          
          test:
                  : FOO is "$(FOO)"
                  : BAR is "$(BAR)"
     

FOOone BAR = twoと等価です.それ以外のmakeでは, バックスラッシュは直後の行だけを含みます.

コメント内のエスケープされた改行
POSIXによると,Makefileのコメントは#ではじまり, エスケープされていない改行まで続きます.
          % cat Makefile
          # A = foo \
                bar \
                baz
          
          all:
                  @echo ok
          % make   # GNU make
          ok
     

しかし現実では,これは常にそうではありません.実装によっては,# から行末までを廃棄し,後置されるバックスラッシュを無視するものもあります.

          % pmake  # BSD make
          "Makefile", line 3: Need an operator
          Fatal errors encountered -- cannot continue
     

このため,複数行の定義をコメントアウトしたい場合,最初の行だけでなく,そ れぞれの行に# を前置してください.

          # A = foo \
          #     bar \
          #     baz
     

make macro=valueとサブ呼び出しのmake
コマンドライン変数のfoo=barのような定義は,Makefilefooの定義に優先します.(GNU makeのような) makeの実装によっては,この優先はサブ呼び出しのmake に伝搬します.古い実装によっては,それ以下のmakeに代入を渡さな いものもあります.
          % cat Makefile
          foo = foo
          one:
                  @echo $(foo)
                  $(MAKE) two
          two:
                  @echo $(foo)
          % make foo=bar            # GNU make 3.79.1
          bar
          make two
          make[1]: Entering directory `/home/adl'
          bar
          make[1]: Leaving directory `/home/adl'
          % pmake foo=bar           # BSD make
          bar
          pmake two
          foo
     

サブ呼び出しのmakefoo=barの優先を伝搬したい場合,移植 性を持たせる方法が無いわけではありません.その一つは,すべての環境変数を Makefileマクロ定義に優先させる-eオプションを使用し, fooを環境変数として定義する方法です.

          % env foo=bar make -e
     

-eオプションは,自動的にサブ呼び出しのmakeに伝搬し,環 境変数はmakeの呼び出し間で継承されるので,fooマクロはサ ブ呼び出しのmakeで期待したように優先されます.

この構文(foo=bar make -e)は,Makefileの外で使用するときだ け,例えば,スクリプトやコマンドラインのときだけ移植性があります. makeルール内で実行するとき,GNU make 3.80とそれ以前 のバージョンは,それ以下でのmake-eオプションを伝搬さ せることを忘れています.

更に-eを使用することで,Makefileで通常定義されるその他のマ クロが環境変数に含まれている場合,予期しない副作用があるかもしれません. (以下のmake -eSHELLの注意も参照してください.)

サブ呼び出しのmakeに優先物を伝搬させるもう一つの方法は, Makefileに手動で行なうことです.

          foo = foo
          one:
                  @echo $(foo)
                  $(MAKE) foo=$(foo) two
          two:
                  @echo $(foo)
     

そうする場合,ユーザが優先したいと思われるすべてのマクロを予測する必要が あります.

SHELLマクロ
POSIX準拠のmakeでは,シェルプロセスを起動したり, Makefileルールを実行するために,内部で$(SHELL)マクロを使用 します.これはmakeで提供される組み込みマクロですが, Makefileやコマンドライン引数で変更することが可能です.

すべてのmakeが,このSHELLマクロを定義するわけではありま せん.例えば,OSF/Tru64 makeがそうです.この実装では,常に /bin/shを使用します.そのため,Makefileで常にSHELL を定義するのは良い考えです.Autoconfを使用している場合,以下のようにして ください.

          SHELL = @SHELL@
     

POSIX準拠のmakeでは,make -eが使用されている 場合でも,環境変数から$(SHELL)の値を入手してはなりません(そうでない場合, SHELL=/bin/tcshの状況でルールによって何が起こるのか考えてみてくだ さい).

しかし,すべてのmakeがこのような例外を実装しているわけではあり ません.例えば,OSF/Tru64 makeSHELLを使用しないので, 保護していなくても不思議ではありません.

          % cat Makefile
          SHELL = /bin/sh
          FOO = foo
          all:
                  @echo $(SHELL)
                  @echo $(FOO)
          % env SHELL=/bin/tcsh FOO=bar make -e   # OSF1 V4.0 Make
          /bin/tcsh
          bar
          % env SHELL=/bin/tcsh FOO=bar gmake -e  # GNU make
          /bin/sh
          bar
     

ルール内のコメント
コメントをルールに書き込まないでください.

タブで始まるものは,タブの直後に#が続いていても,すべて現在のルー ルのコマンドとして扱うmakeもあります.Tru64 Unix V5.1の makeはその一つです.以下のMakefileで,シェルで# fooを実行します.

          all:
                  # foo
     

obj/サブディレクトリ
びっくりしたくなければ,サブディレクトリをobj/と命名しないでくだ さい.

obj/ディレクトリが存在する場合,BSD makeMakefileを読み込む前に,そのなかに入ります.このため,現在のディ レクトリのMakefileは読み込まれません.

          % cat Makefile
          all:
                  echo Hello
          % cat obj/Makefile
          all:
                  echo World
          % make      # GNU make
          echo Hello
          Hello
          % pmake     # BSD make
          echo World
          World
     

make -k
make -kの終了ステータスに依存しないようにしてください.終了ステー タスがエラーかどうかを反映する実装もあります.それ以外の実装では,常に成 功します.
          % cat Makefile
          all:
                  false
          % make -k; echo exit status: $?    # GNU make
          false
          make: *** [all] Error 1
          exit status: 2
          % pmake -k; echo exit status: $?   # BSD make
          false
          *** Error code 1 (continuing)
          exit status: 0
     

VPATH
POSIXでは,VPATHサポートを指定していません.多くの makeVPATHサポートの形式がありますが,その実装は, make間で一貫していません.

VPATH機能を必要としている人々への最高の提案は,makeの実 装を選択しそれに固執するようにと言うことかもしれません.Makefile の結果は常に移植性があるとは限らないので,移植性の高いmakeを選 択するのが良いでしょう(ヒント,ヒント).

VPATHの実装の既知の問題には以下のものがあります.

VPATHと二重のコロンのルール
VPATHへの代入で,Sunのmakeは最初の二重コロンのルールの 組だけを実行します.(このコメントは,1994年からで,現在は無くなっていま す.SunOS 4では移植性があります.これが再生成された場合,それを説明する テストケースを送ってください.)
明示的なルールで$<がサポートされていない
他でも述べたように,明示的なルールで$<を使用するのは移植性があり ません.必要条件のファイルは,ルール内で明示的な名前にすべきです. VPATHの検索で必要条件を見つけたい場合,手動でコード全体を書く必要 があります.例えば,以下のようなパターンを使用します.
               VPATH = ../src
               foo.o: foo.c
                       cc -c `test -f foo.c || echo ../src/`foo.c -o foo.o
          

自動的なルールの再書き込み
SunOS makeのように,VPATHで必要条件を探し,出現するたび に適切なルールにを再書き込みするmakeの実装もあります.

例えば,以下を考えます.

               VPATH = ../src
               foo.o: foo.c
                       cc -c foo.c -o foo.o
          

foo.c../srcで見つかった場合,cc -c ../src/foo.c -o foo.oを実行します.素晴らしいと思います.

しかし,それ以外のmakeの実装では,これに依存することは不可能で, VPATHを手動で検索する必要があります.

               VPATH = ../src
               foo.o: foo.c
                       cc -c `test -f foo.c || echo ../src/`foo.c -o foo.o
          

しかし"必要条件の再書き込み"はこれに適用されます.そのため, ../srcfoo.cがある場合,SunOSのmakeは以下を実行 します.

               cc -c `test -f ../src/foo.c || echo ../src/`foo.c -o foo.o
          

以下を生成します.

               cc -c foo.c -o foo.o
          

そしてこのために失敗します.あぁ.

回避策の一つは,ルールのなかにfoo.cをそのまま書いていないことを確 かめることです.例えば,以下の三つのルールは安全です.

               VPATH = ../src
               foo.o: foo.c
                       cc -c `test -f ./foo.c || echo ../src/`foo.c -o foo.o
               foo2.o: foo2.c
                       cc -c `test -f 'foo2.c' || echo ../src/`foo2.c -o foo2.o
               foo3.o: foo3.c
                       cc -c `test -f "foo3.c" || echo ../src/`foo3.c -o foo3.o
          

必要条件がマクロ内にあるとき,事態はより悪くなります.

               VPATH = ../src
               HEADERS = foo.h foo2.h foo3.h
               install-HEADERS: $(HEADERS)
                       for i in $(HEADERS); do \
                         $(INSTALL) -m 644 `test -f $$i || echo ../src/`$$i \
                           $(DESTDIR)$(includedir)/$$i; \
                       done
          

上記のinstall-HEADERSルールは,for i in $(HEADERS);for i in foo.h foo2.h foo3.h;に展開され,foo.hfoo2.hはそのまま単語となり,このためサブジェクトはVPATH に 調整されるので,SunOSでは信頼できません.

三つのファイルが../srcにある場合,このルールは以下のように実行さ れます.

               for i in ../src/foo.h ../src/foo2.h foo3.h; do \
                 install -m 644 `test -f $i || echo ../src/`$i \
                    /usr/local/include/$i; \
               done
          

最初の二つのinstallの呼び出しは失敗します.例えば, foo.hをインストールする事を考えます.

               install -m 644 `test -f ../src/foo.h || echo ../src/`../src/foo.h \
                 /usr/local/include/../src/foo.h;
          

以下を生成します.

               install -m 644 ../src/foo.h /usr/local/include/../src/foo.h;
          

手動のVPATHの検索には問題が無いことに注意してください.しかし,こ のコマンドは,間違ったディレクトリにfoo.hをインストールします.

ここまで,いくつかのMakefilefoo.cに対して行なってきた, $(HEADERS)をどうにかして引用符で囲むことは役に立ちません.

               install-HEADERS: $(HEADERS)
                       headers='$(HEADERS)'; for i in $$headers; do \
                         $(INSTALL) -m 644 `test -f $$i || echo ../src/`$$i \
                           $(DESTDIR)$(includedir)/$$i; \
                       done
          

実際,headers='$(HEADERS)'headers='foo.h foo2.h foo3.h' に展開され,foo2.hはそのまま単語になります.(一方, headers='$(HEADERS)'; for i in $$headers;の慣用句は,for i in;で構文エラーになるシェルもあるので,$(HEADERS)が空の場合は良 い考えです.)

回避方法の一つは,不要な../src/の接頭辞を手動で削除する事です.

               VPATH = ../src
               HEADERS = foo.h foo2.h foo3.h
               install-HEADERS: $(HEADERS)
                       headers='$(HEADERS)'; for i in $$headers; do \
                         i=`expr "$$i" : '../src/\(.*\)'`;
                         $(INSTALL) -m 644 `test -f $$i || echo ../src/`$$i \
                           $(DESTDIR)$(includedir)/$$i; \
                       done
          

Automakeも同様なことを行ないます.

OSF/Tru64 makemakeは,不思議なディレクトリの必要条件を作成する
必要条件がVPATHのサブディレクトリにある場合,Tru64 make はそれを現在のディレクトリに作成します.
               % mkdir -p foo/bar build
               % cd build
               % cat >Makefile <<END
               VPATH = ..
               all: foo/bar
               END
               % make
               mkdir foo
               mkdir foo/bar
          

ルールは,前に存在している手動のVPATH検索を使用するので,これは予 想外の結果になるはずです.

               VPATH = ..
               all : foo/bar
                       command `test -d foo/bar || echo ../`foo/bar
          

上記のcommandは,現在のディレクトリに作成された,空の foo/barディレクトリで実行されます.

ターゲットの探索
GNU makeは,VPATHで見つかったファイルを使用す べきとき決定するアルゴリズムは幾分複雑です.See How Directory Searches are Performed.

ターゲットのリビルドが必要な場合,GNU makeは,このター ゲットをVPATHで検索している間に見つかったファイル名を廃棄し, Makefileで与えられたファイル名を使用して,ローカルなファイルをビ ルドします.ターゲットをリビルドする必要が無い場合は,GNU makeVPATHで検索している間に見つかったファイル名を使用 します.

NetBSD makeのような,その他のmakeの実装は,より簡単 に記述できます.VPATHで検索している間に見つかったファイル名は,ター ゲットがリビルドを必要としているかどうかにかかわらず使用されます.このた め<新しいファイルはローカルに作成されますが,VPATHに位置する既存 のファイルは更新されます.

しかし,OpenBSDとFreeBSDのmakeは,明示的なルールを持つ依存性を 探すためにVPATHを実行しません.これは非常にイライラします.

VPATHで,autoconfパッケージのビルドを試みるとき(例えば, mkdir build && cd build && ../configure),GNU make makebuildディレクトリですべてのローカ ルにビルドしますが,BSD makeはローカルな新しいファイ ルをビルドし,ソースディレクトリの既存のファイルを更新する事を意味します.

               % cat Makefile
               VPATH = ..
               all: foo.x bar.x
               foo.x bar.x: newer.x
                       @echo Building $@
               % touch ../bar.x
               % touch ../newer.x
               % make        # GNU make
               Building foo.x
               Building bar.x
               % pmake       # NetBSD make
               Building foo.x
               Building ../bar.x
               % fmake       # FreeBSD make, OpenBSD make
               Building foo.x
               Building bar.x
               % tmake       # Tru64 make
               Building foo.x
               Building bar.x
               % touch ../bar.x
               % make        # GNU make
               Building foo.x
               % pmake       # NetBSD make
               Building foo.x
               % fmake       # FreeBSD make, OpenBSD make
               Building foo.x
               Building bar.x
               % tmake       # Tru64 make
               Building foo.x
               Building bar.x
          

NetBSDのmake../bar.xをVPATHのある場所で更新し, FreeBSD,OpenBSD,そしてTru64のmakeは,../bar.xが最新の ときでも,常にbar.xを更新することに注意して下さい.

言及する価値のあるもう一つの点は,GNU makeが一度 VPATHのファイル名を無視する事に決めると(例えば,上記の例の ../bar.xを無視する),ターゲットが他のルールの必要条件になったとき, それを無視し続けます.

以下の例では,bar.x: newer.xのルールを実行している間に bar.xVPATHの結果を無視するので,.x.yのルールを実 行する前に,GNU makeVPATHbar.xを探 さない事を示しています.

               % cat Makefile
               VPATH = ..
               all: bar.y
               bar.x: newer.x
                       @echo Building $@
               .SUFFIXES: .x .y
               .x.y:
                       cp $< $@
               % touch ../bar.x
               % touch ../newer.x
               % make        # GNU make
               Building bar.x
               cp bar.x bar.y
               cp: cannot stat `bar.x': No such file or directory
               make: *** [bar.y] Error 1
               % pmake       # NetBSD make
               Building ../bar.x
               cp ../bar.x bar.y
               % rm bar.y
               % fmake       # FreeBSD make, OpenBSD make
               echo Building bar.x
               cp bar.x bar.y
               cp: cannot stat `bar.x': No such file or directory
               *** Error code 1
               % tmake       # Tru64 make
               Building bar.x
               cp: bar.x: No such file or directory
               *** Exit 1
          

bar.x: newer.xルールからコマンドを削除した場合,GNU makeでは手品のように動作し始める事に注意してください.それは, bar.xが更新されていない事を知っているので,VPATH (../bar.x)の結果がうまく使用できるという結果を廃棄しません.Tru64 でも動作しますが,FreeBSDとOpenBSDではまだそうではありません.

               % cat Makefile
               VPATH = ..
               all: bar.y
               bar.x: newer.x
               .SUFFIXES: .x .y
               .x.y:
                       cp $< $@
               % touch ../bar.x
               % touch ../newer.x
               % make        # GNU make
               cp ../bar.x bar.y
               % rm bar.y
               % pmake       # NetBSD make
               cp ../bar.x bar.y
               % rm bar.y
               % fmake       # FreeBSD make, OpenBSD make
               cp bar.x bar.y
               cp: cannot stat `bar.x': No such file or directory
               *** Error code 1
               % tmake       # True64 make
               cp ../bar.x bar.y
          

すべてのmakeの実装がVPATHによるダーゲットの検索に依存し ないようにお願いするのが,唯一の解決方法だと思います.言い替えると, VPATHはビルドされていないソースへの予約にすべきです.


単一のサフィックスルールと分離された依存性
単一のサフィックスルール(Single Suffix Rule)は,基本的に(推測され る)通常のサフィックスルール(`.from.to:')ですが,ディスティネー ション(destination)サフィックスは空(`.from:')です.

分離された依存性(Separated dependencies)は,ルールを定義すること無 く,ターゲットの必要条件のリストを単純に参照します.通常,一方ではルール を,もう一方で依存性をリストアップすることが可能です.

Solarisのmakeは,単一のサフィックスルールで定義されたターゲッ トに対する,分離された依存性をサポートしていません.

          $ cat Makefile
          .SUFFIXES: .in
          foo: foo.in
          .in:
                  cp $< $ $ touch foo.in
          $ make
          $ ls
          Makefile  foo.in
     

一方GNU Makeはサポートしています.

          $ gmake
          cp foo.in foo
          $ ls
          Makefile  foo       foo.in
     

それは`foo: foo.in'の依存性無しで動作することに注意してください.

          $ cat Makefile
          .SUFFIXES: .in
          .in:
                  cp $< $ $ make foo
          cp foo.in foo
     

そして,それは二重のサフィックスの継承ルールで動作することにも注意してく ださい.

          $ cat Makefile
          foo.out: foo.in
          .SUFFIXES: .in .out
          .in.out:
                  cp $< $ $ make
          cp foo.in foo.out
     

結果として,そのような状況では,ターゲットルールを書く必要があります.

タイムスタンプの分解能
伝統的に,ファイルのタイムスタンプの分解能は1秒になっていて, makeはファイルがそれ以外のものより新しいかどうかを決定するため これらのタイムスタンプを使用していました.しかし,最近のファイルシステム には分解能が1ナノ秒のタイムスタンプになっているものもたくさんあります. makeの実装には,タイムスタンプ全体を見るものもあります.それ以 外では,分数部分を無視し,結果として間違ったものとなるはずです.通常,こ れは問題ありませんが,非常に稀な状況では,タイムスタンプの切り詰めのバグ を回避するため,`sleep 1'のような手法を使用する必要があるかもしれま せん.

`cp -p'と `touch -r'のようなコマンドでは,通常はファイルのタイ ムスタンプを完全な分解能でコピーしません(see Limitations of Usual Tools).このため,以下のようなルールは気を付けるべきです.

          dest: src
                  cp -p src dest
     

それは,タイムスタンプの切り詰め後では,srcよりもdestが古 くなり,このためmakeは次回も不必要な仕事を再び行なうはずです. この問題を回避するため,タイムスタンプファイルを使用することが可能です. 例えば以下のようにします.

          dest-stamp: src
                  cp -p src dest
                  date >dest-stamp