configureをデバッグする

プログラムがコンパイルできない時にはどうすべきか

パッケージのREADMEにはよく、特別の注記もなく「ビルド方法:configureを実行し、次にmakeを実行します」等と書いてあることがあります。でもこれでうまく動かなかった時にはどうすれば良いのでしょうか?この記事ではPeter Seebachが、自動設定スクリプトが動かなかった時には・・開発者として失敗を最小限にとどめるには・・どうすべきかを説明します。結局のところそのビルド・プロセスが動かなければ、ユーザーにとってはプログラムがビルドされても動かないのと同じことになってしまうのです。

Peter Seebach (dwlinux@plethora.net), Writer, Freelance

Author photoPeter Seebachは、たしかこの辺にFileメニューがあったはずだと思っています。Peterはコンピューターを使い始めてすぐに、プログラミングを始めましたが、いまだにGUIを目新しく感じています。



2003年 12月 04日

最近ではオープンソースのプログラムにconfigureスクリプトが付いてきます。そうしたスクリプトの目的の一つは、新しいシステムに組み込む際の試行作業を自動化することにあります。その昔、プログラムにはMakefileが付いてきて5,6個もコンパイラー・フラグやオプションがあり、その一つ以外には注記もなく、その注記も「自分のシステムに適切なフラグを選択してください」と書いてあるだけでした。もっと包括的な設定オプションのセットになると、ホストシステムの変数によっては5,6ダースもある設定フラグを含んだ、config.hと呼ばれる巨大なCヘッダーまでありました。

最初のやり方としては、単にサポートする2つのシステム(例えばBSDとシステムV)用に#ifdefをコードとして持つことでしたが、Unixの変種が増えるに従って、各フィーチャーに対して#ifdefを持った方がより実用的になりました。システム毎のコードはこんな風なものを生成します。

リスト1. システム毎のコード
    #ifdef SUNOS4 || NEXT || NETBSD || FREEBSD || OPENBSD
    #include <string.h>
    #else
    #include <strings.h>
    #endif

フィーチャー毎のコードは次のようなものを生成します。

リスト2. フィーチャー毎のコード
    #ifdef HAS_STRING_H
    #include <string.h>
    #else
    #include <strings.h>
    #endif

2番目のやり方は新しいシステムには適用させやすいのですが、開発者にとっては大仕事が必要になります。ところがターゲットにするシステムが潜在的に何十もあることを考えると(設定ヘッダー・ファイルを自動的にビルドしてしまうという点を除けば)開発者にも2番目の方法を使うメリットが大いに出てきます。一つの方法としてはGNUautoconfコードを使ってconfigureスクリプトをビルドすることです。このスクリプトは必要なテストを行い、正しい値で設定ヘッダー・ファイルを生成してくれます。

そうしたスクリプトのもう一つの機能としては、事前定義の変数を一貫して設定できるということです。フラグを手動編集するときによく起きるのが、(例えば/usr/localではなく/usr/gnuにインストールするために)Makefileを手直ししておきながら、ヘッダーファイルで対応する値を変更するのを忘れてしまうという問題です。当然ながら、コンパイルされたプログラムは自分のデータファイルがどこにあるか分からない、という結果になります。configureスクリプトを使う利点の一つは、(スクリプトを維持管理する人がすべきことをきちんとしていれば)一貫性のあるインストールを自動的に生成してくれることです。

開発者に分かっておいていただきたいのは、良くできたconfigureスクリプトを使えば、ユーザーが/usr/gnuの代わりに/usr/localをプリファレンスとして設定したりできるようにもなる。というメリットがあるという点です。

最後に、configureスクリプトはどんなオプション・パッケージがインストールされているか、どの要件が欠けているかといった判断作業もできるのです。例えばXウィンドウ・システムで動作するように作られたプログラムであれば、Xがどこにインストールされているか、さらに、そもそもXがインストールされているのか 、といったことを知る必要があるはずです。

一体それはどうやったらできるのでしょう?

コンパイルして、もう一度試す

configureがすることの大部分は簡単な機構で行われます。これを自分で確認するために簡単なプログラムとして、設定した条件を満足した時にのみコンパイルするプログラムを作ってみてください。それを一時ファイルに保存し、コンパイルします。例えば、/usr/X11R6/というパスにXウィンドウ・システムがインストールされているかどうかを知りたいとしましょう。一つのやり方としては次のようなテストプログラムになると思います。

#include <X11/X.h>
int main(void) { return 0; }

さて、コンパイラにこれをコンパイルさせると、<X11/X.h> がコンパイラのインクルード・パスにある場合にのみコンパイルが成功します。ですから、Xがインストールされていそうなディレクトリそれぞれについて、コンパイラのインクルード・パスを(directory)/includeとしてプログラムをコンパイルしてみます。コンパイルできる、という値が得られたら、正しいインクルード・パスが得られたことになります。

autoconfには、ありとあらゆるものに対する定義済みのテストが用意されています。自分でテストを書くよりも、できるだけこうしたテストを使うようにしてください。その利点はいくつかあります。第一にautoconfの新しいバージョンではこうしたテストが改善されており、それまでは自分で直さなければならなかったバグも直っているかも知れません。第二に時間の節約になります。もちろん、ずっと良いのはテストそのものをしないで済ませることです。十分な裏付けを持った上でテストが必要でないと思ったら(例えば8ビットより大きなバイトを持つマシンであっても、今だにsizeof(char)が1であることを要求されているのです)、全くテスト無しで済ますこともできるのです。

一部のテストは機能テストです。memcmp()という機能があることを知っているだけでは十分ではなく、正しい意味体系を持っている必要もあるのです。多くの場合、こうしたテストは一つか二つのプラットフォームでしか起きないような、非常によく分からないバグに対するテストです。これらのテストは実際にテストプログラムを実行し、その出力をチェックします。テストプログラムは普通、標準のUnixの約束に従います。つまり実行が成功すれば0を返し、失敗すれば0以外の値を返します。

これらを使いこなせれば、必要なコンパイラ・フラグや定義を自動的に決定し、どこかにあるヘッダーファイルに置けるようになります。たいていの場合は、configureスクリプトの一部、またはすべての試行動作をユーザーが上書きして、既知の正しい解を入れられるようになっています。

特に、壊れたmemmove()があるシステムのような例を考えてみてください。ある一部のプログラムにしか影響を与えないようなバグがあることを知らなかったら、時々致命的な障害が起きることを知らずにプログラムをビルドして、製品に入れ込んでしまうかも知れません。

多くの場合、長く複雑なconfigureスクリプトの実質的な結果はこうです。ターゲットのシステムはこのプログラムが使う標準フィーチャーをすべて提供しており、そうしたフィーチャーはすべて正しく動作する、というのです。そういう場合であれば、手動でフラグを立てたらどうなのでしょう?これは開発者にとっては極めて妥当ですが、多くのユーザーにとっては逆なのです。ユーザーは自分の使っているLinuxのディストリビューションに既知のバグがあることに気が付いていないかも知れませんし、どんなパッケージがどこにインストールされているかもよく分かっていないかも知れません。configureスクリプトは、ごく普通のことをするのに最大限の助けが必要な人の助けにはなります。その妥当な代償として、スクリプトがおかしくなった時には余計な仕事が発生するというわけです。


どこがおかしいのか?

configureが何をするか基本的なことは分かったので、おかしくなった場合にどうなるかを学んでみましょう。configureが失敗するのには2つの可能性があります。一つはconfigureが正しくても、必要なものがシステムに無い場合ですが、大部分の場合、これはconfigureスクリプトで診断できます。もっと面倒なのはconfigureが正しくない場合です。この場合はコンフィグレーションが生成できず、不正なコンフィグレーションを生成する結果になります。

configureの試行作業が正しく、システムに必要要件が欠けている場合であれば、その要件を満たしさえすれば良いわけです。欠けている必要要件を見つけ出してインストールし、configureスクリプトを再実行すればすべて完了となるはずです。(config.cacheには前のテストの結果がキャッシュされているので、config.cacheを削除するのを忘れないでください。configureを再度先頭から開始する必要があります。)

もしconfigureを開発しているのであれば、意味のあるエラー・メッセージを出すようにしてください。例えば一般的に普及しているアドオン・パッケージの一部に対する機能テストならば、足りない機能の名前をユーザーに知らせるようなことはしないでください。ユーザーには必要なパッケージの名前を伝えるべきです。それに、必要条件をREADMEファイルに記述しておくのを忘れないでください。また、他のパッケージのどんなバージョンでテストしたかを、どうか必ず、必ず明記してください。

もちろん、必要要件がインストールされた後でも、configスクリプトはその追加されたプログラムを見つけられないかも知れません。その場合には、configureの試行作業が間違っている場合の所まで戻ることになります。

ドキュメンテーションを読んでください

configureが失敗した時に最初にすべきことはconfigure -hを実行し、引数のリストをチェックすること、と言って良いでしょう。インストールしたはずのライブラリが見つからなかった場合には、そのライブラリを置く別の場所を指定するオプションがあるかも知れません。また、ある機能を使用可能にしたり使用不可にしたりできるのかも知れません。例えばAngband (Rogueライクのゲーム)に使われているconfigureスクリプトにはオプションのフラグとして--enable-gtkがあり、GTKサポートでビルドするように指示できます。このフラグが無いとビルドもしません。

そのシステムが変な風に設定されている場合には、configureスクリプトに対して極めて綿密な変数設定をしなければならないのかも知れませんし、クロスコンパイルをする場合にはおそらく、かなりおかしなことをする羽目になるでしょう。configureがCコンパイラに対して使う変数であるCCに値を指定することで、かなりの問題を解決することができます。コンパイラを指定すれば、configureはどれを使うか推測したりせず、そのコンパイラを使います。オプションやフラグは次のようにコマンドラインで指定できます。例えばデバッグのシンボル付きでコンパイルしたいのであれば、次を試してみてください。

CC="gcc -g -O1" ./configure

(これはsh-ファミリのシェルを使っていると言う前提です。cshシェルで環境変数CCを設定する場合はsetenvを使ってください。)

config.logを読む

configureが実行される時にはconfig.logと呼ばれるファイルを生成します。これは実行されたテストのログとテストの経過を記録したものです。例えば、典型的なconfig.logは次のようなものです。

リスト3. config.logの典型的な内容
    configure:2826: checking for getpwnam in -lsun
    configure:2853: gcc -o conftest -g -O2 -fno-strength-reduce conftest.c -lsun >&5
    ld: cannot find -lsun
    configure:2856: $? = 1
    configure: failed program was:
    (a listing of the test program follows)

もし私が、-lsungetpwnam()を提供するはずのシステムの中にいるとしたら、そのチェックをするのに使われたコマンドラインそのものと、使われたテストプログラムが見えたはずです。それらをちょっとデバッグしてみれば、configureスクリプトをいじるのに十分な情報が得られるはずです。ヒントになる行番号に注目してください。このテストはconfigureスクリプトの2,826行目から開始します。(シェルのプログラマーであれば、configureスクリプトの中で行番号を出力する部分を読んでみると面白いと思います。$LINENOを自動的に妥当な値に拡張したりしないシェルでは、configureスクリプトは行番号を入れた上で、sedを使って自身のコピーを作るのです!)

テストが失敗した時やおかしな結果が出てしまった時に、なぜそうなったかを理解するには、まずログファイルを読んでみるのが一番でしょう。ただし時には失敗したテスト自体は大して重要ではなく、単に前回のテストが失敗したので今回実行しただけ、と言う場合もあることには注意してください。例えばconfigureが途中で停止してしまうのは、聞いたこともないような怪しげなライブラリが見つからないせいかもしれません。configureは単に標準のCライブラリのフィーチャーに対するテストプログラムが失敗したので、そのライブラリを探そうとしているだけなのかもしれないのです。こうした場合には最初の問題を手直ししてしまえば2回目のテストも全く実行されなくなるのです。

バグのあるテストプログラム

他にもconfigureが時々試行作業を誤る場合がいくつかあります。一つはテストプログラムが正しく作られておらず、システムによってはコンパイルできない場合です。例としてstrcmp()機能があるかどうかをテストするものとして提案された次のテストプログラムを考えてみてください。

リスト4. strcmp()があるかどうかをテストするプログラム
    extern int strcmp();
    int main(void) {
                    strcmp();
    }

このプログラムは<string.h>ヘッダーを使わないように書かれています。その意図は、ライブラリにstrcmp()が存在すれば、プログラムはコンパイルもリンクも正しくできますが、存在しない場合にはリンカーはstrcmp()への参照を解決できず、プログラムはコンパイルできないということです。

あるバージョンのUnixWareコンパイラでは、strcmp()への参照は自動的にインテルのプロセッサー・ネイティブの文字列比較命令に変換されます。これはstrcmp()に渡される引数を単純に一行のアセンブリ・コードで置き換えることで行われます。残念ながらサンプル・プログラムではstrcmp()を引数無しで呼んでいるため、結果として得られるアセンブリ・コードは無効で、コンパイルも失敗します。そのシステムでstrcmp()を使うことは実際にはできるのですが、テストプログラムが、足りないものがあると判断してしまっているのです。

autoconfがターゲットにしている主流のプラットフォーム(主に各種のLinuxですが、主要なUnixのディストリビューションも)では、バグのあるテストは稀であり、あまり広くテストされていないようなコンパイラやプラットフォームでテストを実行した結果によるものがほとんどです。例えば、UnixWareのgccでは上記のバグを起こしませんし、システムのネイティブ開発パッケージに付属のコンパイラだけがバグを起こすのです。たいていの場合、一番簡単なのはconfigureの中で関係のあるテストの部分はコメントにしてしまい、テストすべき変数を直接設定してしまうことです。

実はコンパイラが動いていない

configureの初期段階で選択されたコンパイラ・フラグは実行ファイルにリンクできたにもかかわらず、その実行ファイルが実行できない時には致命的なエラーが起きます。こういう時にはテストは見事に失敗します。例えば使っているリンカ・コマンドのどこかが悪い場合には、プログラムのリンクは正しくできても実行できないかも知れません。この記事を書いている時点ではconfigureスクリプトではこれを特定することができないので、ターゲットのプログラムが実行されることを要求するテストだけが失敗をレポートします。これをデバッグするのは驚くべきことのようですが、config.logスクリプトで何が悪かったかが明確に分かるのです。例えば、あるテストシステムでは次のような出力がありました。

リスト5. テストconfig.log出力
configure:5644: checking for working memcmp
    configure:5689: gcc -o conftest -g -O2 -fno-strength-reduce
        -I/usr/X11R6/include    -L/usr/X11R6/lib conftest.c -lXaw -lXext
        -lSM -lICE -lXmu -lXt -lX11 -lcurses  >&5
    configure:5692: $? = 0
    configure:5694: ./conftest
    Shared object "libXaw.so.7" not found
    configure:5697: $? = 1
    configure: program exited with status 1

おかしくなった本当の原因は、コンパイラに別なフラグが必要だったということです。つまり実行時には、動的にリンクされたライブラリを検索するために、ディレクトリのリストの中に/usr/X11R6/libが必要なことを示す別のフラグが必要だったのです。ただ、プログラムが問題なくコンパイルされてから、コンパイルされたテストプログラムを(停止するのではなく)実際に実行するのはこれが初めてでした。これはごく些細な問題です。

このシステムでの解決方法としては、CFLAGS変数に次のオプションを追加することでした。

-Wl,-R/usr/X11R6/lib

コマンドラインで次のようにすると、configureがこのテストを正しく実行するようになります。

$ CFLAGS="-Wl,-R/usr/X11R6/lib" ./configure

これはクロス・コンパイルには特に致命的で、実際にクロス・コンパイラで生成された実行ファイルはおそらく実行できないでしょう。もっと新しいバージョンのautoconfでは非常に苦労していて、テストプログラムを実際に実行するのを要求するようなテストは避けるようにしています。

見つからないライブラリやインクルードを探す

configureスクリプトでもう一つよくあるのは、あるパッケージがあり得ないところにインストールされていて、configureがそれを見つけられないという問題です。良くできたconfigureスクリプトでは普通、必要なファイルの位置を指定できるようになっていますが、そうしたファイルがおかしな場所にインストールされてしまっているかも知れないのです。例えば、configureスクリプトの多くは標準的な手段として、どの場所でXライブラリを探すべきかをスクリプトに伝える手段を持っています。

リスト6. Xライブラリを探す
    X features:
      --x-includes=DIR    X include files are in DIR
      --x-libraries=DIR   X library files are in DIR

もしそれが動かなければ、単純な力わざで強行する手もあります。つまりCC環境変数の一部、またはCFLAGS環境変数の一部として、必要なコンパイラ・フラグを指定するわけです。

その他の小細工

autoconfconfigureスクリプトを生成するのに使用するconfigure.inファイルを開発者が提供しているのであれば、最新バージョンのautoconfを実行してみるのも回避策の一つです。問題なく動くかも知れませんし、たとえ完璧に動かなくてもいくつかの問題は解決してくれるかも知れません。ここでの目標は、使われている特定のテストを新しいものに更新することです。問題を起こしているのは、単に古いバージョンのconfigureのバグのせいかも知れません。それを念頭に置いた上で、もし読者がこの関連の開発者であれば、必ず自分が使ったconfigure.inファイルを配布するようにしてください。

configureに渡す引数をいじるのに何度も何度も繰り返しをしていて、シェルのコマンドライン編集ではうまく行かない時には、適切な引数でconfigureを呼ぶラッパー・スクリプトを作ってみてください。ちょっといじって何回かテストに失敗しながら動かしてみると、スクリプトとしては次のようなものになっているはずです。

リスト7. ラッパー・スクリプト
    ./configure --with-package=/path/to/package \
       --enable-widget \
       --disable-gizmo \
       --with-x=29 \
       --with-blah-blah-blah
       CFLAGS="-O1 -g -mcpu=i686 -L/usr/unlikely/lib \
          -I/usr/unlikely/include -Wl,-R/usr/unlikely/lib"

スクリプトを一カ所に置いておくと、コマンドラインで同じようなものを何度も何度もタイプするのよりはずっと便利です。後でそれを参照することもできるし、誰かにコピーをメールで送ることもできるわけです。


堅牢なconfigureスクリプトを開発する

予防に勝る対策はありません。configureスクリプトを正しく動作するようにする一番の方法は、スクリプトを作る際に、失敗しないようにすべく最大限の努力をすることです。

堅牢なconfigureスクリプトをビルドする上で大事なことは単純です。テストする必要がないものには絶対にテストをしないことです。sizeof(char)はテストしないこと。これはCでのsizeof演算子は、何かを保持するのに使われるchar-サイズのオブジェクトをいくつか返しますが、(たとえcharが8ビット以上のマシンであっても)sizeof(char)はいつも1なのです。1989年のバージョンが発表されて以来ANSI/ISO Cの一部である機能にアクセスできるかどうかテストしたり、標準Cヘッダーがあるかどうかをテストする理由は、ほとんどの場合何も無いのです。もっと悪いのは標準フィーチャーがあるのに非標準のフィーチャーをテストすることです。<malloc.h>があるかどうかのテストなどしないように。そんなテストは要らないのです。malloc()がしたいなら、<stdlib.h>を使えば良いのです。

多くの場合、あやふやなフィーチャーへの依存性を除去する方が、どちらを使うか判別するために入念なテストをするよりはずっと信頼性が高いものです。可搬性のあるプログラムを書くのは10年前に比べればそれほど難しくはないのです。

最後に、最新バージョンのautoconfを使っていることを確認してください。バグは十分に修正されています。古いバージョンのautoconfに残っているバグは、新しいバージョンでは修正されている可能性が高いのです。

参考文献

  • さらに詳しくはautoconf home pageを見てください。
  • GNU Makeでは望む以上のことが学べます。
  • 可搬性のあるコードを書くには? 10 Commandments for C Programmersでの指示に従ってください。
  • Red HatのAutobookにはGNU自動ツールでのWriting Portable Cという章があります。
  • Red Hatと言えば、IBMdeveloperWorks の記事「RPMによるソフトウェアのパッケージング」で、もっとずっと便利なインストーラーの作り方を学んでください。
  • Linuxが初めてであれば、ソフトウェアのインストールについて、「WindowsからLinuxへのロードマップ: 第9回」を読んでみてください。
  • Peterがこの記事の中で引用しているゲーム、Angbandのルールや遊び方をダウンロードするにはthangorodrim.netを見てください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=228109
ArticleTitle=configureをデバッグする
publish-date=12042003