目次


Make をデバッグする

make を思いどおりに動作させるためのヒントと秘訣

Comments

UNIX® や Linux® のプログラムの大部分は、make を実行することで作られます。make ユーティリティーは、命令を含んだファイル (一般的には「makefile」または「Makefile」という名前ですが、この先では単純に「makefile」と呼ぶことにします) を読み取り、プログラムをビルドするための様々なアクションを行います。多くのビルド・プロセスでは、makefile 自体は完全に他のソフトウェアによって生成されます。例えば、autoconf/automake プログラムを使ってビルド・ルーチンを作成することができます。またプログラムによっては、皆さんが makefile を直接編集するするように要求するものもあります。そして当然ながら、新規の開発では、皆さんが自分でそうしたプログラムを書かなければならないこともあります。

「make ユーティリティー」という言い方は誤解を招きがちです。一般的に使われているものとして、少なくとも 3 種類、それぞれ明確に異なる、GNU make とSystem V make、そして Berkeley make があります。どれも UNIX 初期の時代のコアとなる仕様から育ったものであり、それぞれに新機能が追加されています。その結果、困難な状況が生じました。ごく一般的に使われる機能、例えば、参照によって他のファイルを makefile の中にインクルードする機能でさえ、移植できません。単純に、makefile を作るプログラムを書くというのも 1 つの解決方法です。GNU make は無料であり、広く配布されているため、それを使ってコードを書いている開発者もいます。同様に、BSD を起源とするいくつかのプロジェクトでは、Berkeley make (これも無料です) を使う必要があります。

そうしたものほど一般的ではありませんが、やはり重要なものとして、JÖrg Schilling による smake があります。そして、他の make が持つ共通機能のサブセットを定義する、5 番目の (陰の) メンバー、historical make があります。デフォルトの make が smake であるシステムはありませんが、smake は優れた make 実装であり、一部のプログラムでは (特に Schilling のプログラムでは) 好んで使っています。

では、makefile を扱う際に突き当たりがちな一般的な問題について調べてみましょう。

makefile を理解する

make をデバッグするためには、makefile を読めなければなりません。ご承知のとおり、makefile の目的はプログラムをビルドするための命令を与えることです。make の重要機能の 1 つに依存関係管理があります。つまりプログラムが更新された場合、make は必要なものだけを再ビルドしようとします。一般的に、これは一連の依存関係ルールで表現されます。依存関係ルールは次のようなものです。

リスト 1. 依存関係ルールの形式
target: dependencies
	instructions

初めて makefile を書く場合に突き当たる大きな問題は、この構造を見るとわかります。いや、見えないと言うべきかもしれません。インデントはタブであり、空白の連続ではないのです。このフォーマットで空白を使ったファイルに対して Berkeley make が発行するエラー・メッセージは、あまり助けになりません。

リスト 2. Berkeley make のエラー・メッセージ
make: "Makefile" line 2: Need an operator
make: Fatal errors encountered -- cannot continue

GNU make でもこのファイルを処理できないことは同じですが、もっとわかりやすいヒントを出してくれます。

リスト 3. GNU make のエラー・メッセージ
Makefile::2: *** missing separator (did you mean TAB instead of 8 spaces?).  Stop.

依存関係も命令も、オプションだということに注意してください。必須のものはターゲットとコロンのみ、つまり構文なのです。では意味体系はどうなのでしょう。 意味体系としては、make が target をビルドしようとする際には、まず依存関係を見ます。実際、make は依存関係を再帰的にビルドしようとします。もし、ある依存関係がまた依存関係を持っていたら、その依存関係をまず処理してから以下のルールを続けます。もし target が存在し、それが作られた時間が dependencies にリストされた全アイテムと同じ、あるいはそれらよりも新しければ、何も行われません。もし target が存在しなければ、あるいはその target よりも新しい依存関係が 1 つ以上あれば、make は instructions を実行します。依存関係は、指定されている順に処理されます。依存関係が何も指定されていなくても、やはり命令は実行されます。依存関係はソースと呼ばれることもあります。

もしターゲットが、コマンド・ラインで与えられた場合 (例えば make foo など) には、make はそのターゲットをビルドしようとします。それ以外の場合は、ファイルの中にリストされた最初のターゲットをビルドしようとします。開発者が使う 1 つの慣習として、最初のターゲットを次のようにすることがあります。

リスト 4. 最初のターゲットに使われる一般的な慣習
default: all

一部の人は、make がこのルールを使うのは名前が「default」だからと思っているようですが、そうではありません。ファイルの中にある最初のルールがこれなので、このルールが使われるのです。名前は好きに付けられますが、「default」の方が読む人に意図が伝わるため、適切な選択です。makefile は make プログラムが読み取るだけではなく、人が読むこともあるのを忘れないでください。

偽のターゲット

一般的に、ターゲットの機能は、他のファイルからファイルを作ることだと言われています。しかし実際には、必ずしもそうとは言えません。ほとんどの makefiles には、ターゲットを作成しないルールが少なくと 2 つか 3 つあるものです。次のようなルールの例を見てください。

リスト 5. 偽のターゲットの例
all: hello goodbye fibonacci

このルールは make に対して (もし make がターゲット all をビルドしようとするなら)、hello と goodbye そして fibonacci が最新であることをまず確認するように命令しています。そして・・・何もしません。何も命令が与えられていないのです。このルールが完了しても、「all」という名前のファイルは作られません。このターゲットは偽物なのです。こうした、make の変種に対して使われる用語は、「phony (偽の)」です。

phony ターゲットは構成の目的で使われ、明確で読みやすい makefile を書く上で非常に便利なものです。例えば、次のようなルールがよくあります。

リスト 6. phony ターゲットのインテリジェントな使い方
build: clean all install

これはビルド・プロセスの操作順を指定しています。

特別なターゲットとソース

make に特殊な効果を与え、構成の機構を提供するために、特別なターゲットがいくつか定義されます。具体的にどんなターゲットがあるかは実装によって異なりますが、最も一般的なものは .SUFFIXES です。.SUFFIXES ターゲットの「ソース」には、認識されたファイル接尾辞リストに追加すべき一連のパターンがあります。特別なターゲットは、make がデフォルトで makefile の中に最初のターゲットをビルドする際の通常のルールの目的にとっては重要ではありません。

一部のバージョンの make では、与えられたターゲットに対して、依存関係と共に特別なソースを指定することができます。例えば .IGNORE は、このターゲットのビルドに使われたコマンドによるエラーを無視すべきであることを示しています (ちょうどコマンドの前にダッシュが付いているのと同じです)。こうしたフラグにはあまり移植性がありませんが、makefile のデバッグ中での理解を助けるために必要な場合があります。

汎用のルール

make には、ファイル名の接尾辞に基づいて汎用の変換を行うための、暗黙のルールがあります。例えば、makefile が存在しない場合には、「hello.c」と呼ばれるファイルを作成し、make hello を実行します。

リスト 7. C ファイルに対する暗黙のルールの例
$ make hello
cc -O2   -o hello hello.c

大きなプログラムに対する makefile は、必要なオブジェクト・モジュール (hello.o や world.o など) のリストを単純に指定し、そして .c ファイルを .o ファイルに変換するためのルールを提供します。

リスト 8. .c ファイルを .o ファイルに変換するためのルール
.c.o:
	cc $(CFLAGS) -c $<

実は、ほとんどの make ユーティリティーには、これとほとんど同じルールが既に組み込まれています。file.o をビルドするよう make に指示すると、file.c があれば、適切に実行されます。「$<」は、特別に事前定義された make 変数であり、ルールの「ソース」を参照します。これにより変数を作成することができます。

汎用のルールは、「接尾辞」の宣言に依存しています。そして make は、それを名前の一部ではなくファイル名の拡張子として認識します。

変数

make プログラムは、共通の値を再利用しやすいように変数を使います。最もよく設定される値は、おそらく CFLAGS でしょう。make 変数に関しては、いくつかの点を明確にしておく必要があります。make 変数は必ずしも環境変数ではありません。指定された名前の make 変数が存在しない場合には、その変数がないかどうか、make は環境変数をチェックします。しかしこれは、make 変数が環境にエクスポートされるという意味ではありません。優先ルールは難解です。一般的に言って、最も優先度の高いものから低いものへと並べると、以下のようになります。

  1. コマンド・ライン変数の設定
  2. 親 make プロセスの makefile の中で設定される変数
  3. この make プロセスの makefile の中で設定される変数
  4. 環境変数

つまり環境変数は、makefile やコマンド・ラインで変数が設定されない場合にしか使われません。(注意: 親 makefile 変数は、常にではありませんが、子に渡されることがあります。ただしこのルールは、ご想像のとおり、make の種類によって異なります。)

make を扱う上で起こりがちな共通の問題として、どういうわけか、変数が、その変数名の一部で置き換えられてしまうことがあります。例えば、$CFLAGS が「FLAGS」で置き換えられてしまうのです。make 変数を参照するためには、その変数の名前を括弧の中に入れ、$(CFLAGS) のようにします。そうしないと、$C と FLAGS、と解釈されてしまいます。

いくつかの変数は、その変数を使う際のルールの関数という特別な意味を持っています。最も一般的に使われるのは、次のようなものです。

  • $< - ターゲットを作る元となるソース
  • $* - ターゲットのベース名 (拡張子やディレクトリーを含まない)
  • $@ - ターゲットの完全名

Berkeley make はこうした変数を廃止する予定ですが、それでも (現在のところ) こうした変数を移植することは可能です。移植とは言っても、正確な定義は make の実装によって異なります。こうした変数を使って何か複雑なものを書くと、その実装専用となってしまう可能性が高いと言えます。

シェル・スクリプト

場合によると、make の移植性を意識したスコープ外のタスクを実行できた方が望ましいことがあります。make はすべてをシェルで実行するため、通常のソリューションとしてはインラインのシェル・スクリプトを書きます。これについて説明しましょう。

注意点の第 1 として、シェル・スクリプトは通常は複数行に渡って書かれますが、命令をセミコロンで区切ることで 1 行にまとめることができます。第 2 に、そうすると読みにくくなります。その妥協策として、スクリプトのインデントは通常どおりにしますが、各行が「; \」で終わるようにします。これによって各シェル・コマンドは (セミコロンで) 構文的に終了しますが、シェルに渡される 1 つの make コマンドのテキスト部分を、一度に作ることができます。例えば、最上位レベルの makefile に次のようなものが現れることがあります。

リスト 9. シェル・スクリプトの行を分割する
all:
	for i in $(ALLDIRS) ; \
	do      ( cd $$i ; $(MAKE) all ) ; \
	done

ここには上記で説明した重要な注意点が 3 つ説明されています。第 1 はセミコロンとバックスラッシュの使い方です。第 2 は make 変数に対する $(VARIABLE) の使い方です。そして第 3 はシェルにドル記号を渡すための $$ の使い方です。たったこれだけです。こんなに簡単なのです。

接頭辞

make はデフォルトで、実行するコマンドをすべて出力し、どれかのコマンドが失敗するとアボートします。しかし場合によると、あるコマンドが失敗したように見えてもビルドを継続したいことがあります。コマンドの最初の文字がハイフン (-) の場合、その行の残り部分は実行されますが、終了ステータスは無視されます。

あるコマンドをエコーしたくない場合には、そのコマンドの前に接頭辞として @ 記号を付けます。これは、メッセージを表示するために最も一般的に使われる方法です。

リスト 10. エコーしないようにする
all:
	@echo "Beginning build at:"
	@date
	@echo "--------"

@ 記号がないと、出力は次のようになります。

リスト 11. コマンドに @ 記号を付けない場合
echo "Beginning build at:"
Beginning build at:
date
Sun Jun 18 01:13:21 CDT 2006
echo "--------"
--------

@ 記号によって make の動作が変わるわけではありませんが、この機能は非常によく使われます。

移植できないものもあります

誰もが望むことでありながら、移植できないものもいくつかあります。しかし、その回避策も多少はあります。

インクルード・ファイル

過去の互換性の問題として最も腹立たしいのは、makefile でのインクルードの処理です。従来の make 実装では、このための手段が提供されないことが多かったのですが、最近はどの make 実装にも提供されているようです。GNU make の構文は、単純に include file です。従来の Berkeley 構文は、.include "file" です。少なくとも 1 つの Berkeley make は、現在は GNU 表記もサポートしていますが、すべてがサポートしているわけではありません。autoconf と Imake の両方で採用されている移植可能なソリューションは、必要と考えられるすべての変数割り当てをインクルードしてしまう方法です。

一部のプログラムでは、とにかく GNU make を使うように要求します。Berkeley make を要求するものもあります。smake を要求するものもあります。どうしてもインクルード・ファイルが必要な場合には、そのツリーを構築すべき make ユーティリティーを単純に指定することも、考えられなくはありません。(ソースで配布されている 3 つの中で、私が好きなのは Berkeley make です。)

変数をネストされたビルドにする

これに対するうまい方法はありません。もしインクルード・ファイルを使うと、クリーンにファイルをインクルードできない、という移植性の問題に突き当たります。すべてのファイルで変数を設定すると、それらをすべて上書きしたい場合にとても大変です。最上位レベルのファイルでのみ設定すると、変数が設定されないため、サブディレクトリーでの個別のビルドが失敗します。

make のバージョンによりますが、まずまずのソリューションは、まだ設定されていなければ設定する、という条件付きですべてのファイルの変数を設定する方法です。こうすると、最上位レベルのファイルを変更しても、フル・ビルドすればサブディレクトリーに変更を反映させることができます。当然ながら、サブディレクトリーに入り込み、そこで make を実行すると、異なった、つじつまの合わない結果を生ずることになります。

この問題は、Imake の何千行ものmakefile と格闘した人はご存知のように、インクルード・ファイルがないことによって拡大されてしまいます。

もっと単純に、再帰 make をまったく使わないというソリューションを主張する人もいます。大部分のプロジェクトにとっては、これは極めて妥当であり、これによってコンパイルが劇的に単純に (そして速く) なります。このための資料としては、Peter Miller による記事、「Recursive Make Considered Harmful」(「参考文献」を参照) が適切でしょう。

問題がある場合にはどうすべきか

まず、あわてないことです。開発者達は、make の完全なバージョンが作成される前から、make に関しておかしな問題を経験してきました。暗黙のルールや予期せぬ変数置換、組み込みシェル・スクリプトによる構文エラーなどは、ほんの序の口にすぎません。

エラー・メッセージをよく読むことです。そのエラーは本当に make によるものでしょうか。それとも make がコールしている何かによるものでしょうか。ネストしたビルドの場合は、実際のエラーを見つけるまでに、山のようなエラー・メッセージをたどる必要があるかもしれません。

プログラムが見つからないという問題であれば、まず、プログラムがインストールされているかどうかをチェックします。インストールされていたら、パスをチェックします。開発者によっては、makefile の中でプログラムに絶対パスを指定する癖のある人もいますが、これは他のシステムでは失敗する可能性があります。もし何かを /opt にインストールし、makefile が /usr/local/bin を参照していると、ビルドは失敗します。パスを修正してください。

時計をチェックします。もっと重要なこととして、ビルド・ツリーの中のファイルやシステム上の他のファイルの日付、そして時計を調べます。入力データの時間順序に一貫性がない場合、make の動作は、まったく影響ないこともあれば深刻な問題が起きることもあるなど、非常に幅があります。時計に問題がある場合 (例えば、いくつかの「new」ファイルの日付が 1970 年になっている、など) には、その問題を修正する必要があります。これには「touch」ユーティリティーが便利です。時計に問題があるために発行されるエラー・メッセージは、それを見ただけでは問題の原因がよくわからないことが普通です。

もしエラー・メッセージが、構文エラーがある、あるいは未設定または正しく設定されていない変数が山のようにあることを示している場合には、別のバージョンの make を試してください。例えば一部のプログラムは、gmake では不可解なエラーが出ますが、smake では問題なくビルドできます。本当におかしなエラーが出るとしたら、GNU make で Berkeley makefile を実行している、あるいはその逆かもしれません。Linux のプログラムは GNU make を想定していることが多く、(このことは、ドキュメンテーションではまったく触れていないにもかかわらず) それ以外のものを使うと説明不可能な失敗をすることになります。

デバッグ・フラグも非常に便利です。GNU make の場合、-d フラグで膨大な情報が得られますが、その一部には有用な情報が含まれています。Berkeley make の場合、-d フラグには、さらにフラグが付きます。-d A では完全な情報が得られます。またサブセットを使うこともできます。例えば、-d vx では変数割り当て (v) に関するデバッグ情報が得られ、また sh -x によってすべてのコマンドが実行され、シェルは受け取ったとおりのコマンドをエコーします。-n デバッグ・フラグを使うと、make は実行するべき内容の一覧を出力します。これは必ずしも正しくありませんが、これによって何が悪いかのヒントが得られることがよくあります。

makefile をデバッグする際のゴールは、make が何をビルドしようとしているのか、そしてどのコマンドでビルドしようとしているのかを判断することです。もし make が適切なコマンドを選んでいるにもかかわらず、そうしたコマンドが失敗しているのであれば、その make のデバッグは完了したのかもしれません。あるいは完了していないかもしれません。例えば、未解決のシンボルがあるためプログラムのコンパイルに失敗するのであれば、もっと前のビルド・フェーズで何か間違ったことをしている可能性があります。そのコマンドのどこが悪いのかわからず、どのコマンドが動くように見えるならば、make によって既に作成されたファイルの 1 つが正しく作成されていないのかもしれません。

ドキュメンテーション

よくあることかもしれませんが、残念なことに GNU make の基本ドキュメンテーションは man フォーマットで利用できず、info システムを使う必要があります。また、単純に man make を実行して make に関する情報を探すことはできません。しかしドキュメンテーションそのものは、よく書かれています。

すべての実装でサポートされている機能の「確実なサブセット」に関する適切なドキュメンテーションを手に入れることは非常に困難です。Berkeley make と GNU make のどちらのドキュメンテーションでも、拡張機能を説明する場合には拡張であることに触れているようですが、標準機能と拡張機能との境目を判断しようとする際には、推測に頼りすぎず、きちんとテストすべきでしょう。

時間の経過と共に BSD 同士でも少しずつ違いが生じたため、それぞれの make の実装の間にも微妙な差が生じています。3 つの BSD の中で、NetBSD 用の make が他のシステムでも積極的にサポートされています。NetBSD の pkgsrc システムは他のプラットフォームでも使われており、NetBSD での make 実装に大きく依存しています。


ダウンロード可能なリソース


関連トピック

  • 「Recursive Make Considered Harmful」は、再帰 make を使うことに関する問題を検証し、第一の原則にまで問題を掘り下げ、そして非常に単純なソリューションを提供しています。
  • Peter が以前寄稿した記事、「configureをデバッグする」(developerWorks、2003年12月) は、不適切なコンフィギュレーション・スクリプトにつまずいた人を助け、また失敗を最低限に抑えるための助言をしています。
  • developerWorks の Linux ゾーンには、Linux 開発者のための資料が他にも豊富に用意されています。
  • 皆さんの次期 Linux 開発プロジェクトを、IBM trial software を使って構築してください。developerWorks から直接ダウンロードすることができます。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Open source
ArticleID=231222
ArticleTitle=Make をデバッグする
publish-date=10242006