Table of Contents
pkgsrc は、多くの Makefile
の断片からなっており、
この各断片が、pkgsrc システムの各部分を明確に形成しています。
pkgsrc のような大規模なシステムのプログラミング言語として make(1)
システムを使う場合、コードを適切かつわかりやすい状態に保つために、
いくらかの規律が必要です。
Makefile
プログラミングの基本的な構成要素は、
変数 (実はマクロ) とシェルコマンドです。
シェルコマンドは、awk(1) プログラムのような複雑なものになることもあります。
各シェルコマンドを意図どおりに動かすため、変数を使うときは、
すべての変数を適切にクォートすることが必要です。
本章では、Makefile
で頻出するいくつかのパターンと、
それらに伴う落とし穴を説明します。
ルールのターゲットとしてファイルを作る場合、 常に、データをまず一時ファイルに書き込んでから、 最後にファイル名を変えるようにしてください。 そうしておかないと、ファイルの生成の途中にエラーが起きた場合、 利用者が make(1) を 2 回目に実行したときに、 前回のファイルが存在したままとなり、ファイルが正しく再生成されません。 たとえば、
wrong: @echo "line 1" > ${.TARGET} @echo "line 2" >> ${.TARGET} @false correct: @echo "line 1" > ${.TARGET}.tmp @echo "line 2" >> ${.TARGET}.tmp @false @mv ${.TARGET}.tmp ${.TARGET}
make wrong を 2 回実行したときに、
1 回目の実行でエラーメッセージが出ますが、
ファイル wrong
は作られた状態になります。
一方、make
correct を実行すると、エラーメッセージが 2 回出るという、
期待どおりの動作となります。
エラーの場合には make(1) が ${.TARGET}
を削除することがあるということをご存知かもしれませんが、
この削除は、たとえば ^C
を押すなど、
割り込みがあった場合にのみおこなわれます。コマンドのどれかが
(上の例の false(1) のように) 失敗した場合には、
削除はおこなわれません。
Makefile
変数は文字列を値として持ち、
文字列は 5 種類の演算子 ``='', ``+='', ``?='',
``:='', ``!='' を使って操作することができます。演算子については
make(1) マニュアルページに説明があります。
Makefile
の変数が解釈される際、
ハッシュ文字 ``#'' とバックスラッシュ文字 ``\'' は特別扱いされます。
バックスラッシュに改行が続く場合、当該バックスラッシュの直前にあるあらゆる空白・
当該バックスラッシュ・改行・改行の直後にあるあらゆる空白は、
ひとつのスペースに置き換えられます。
バックスラッシュ文字とその直後に続くハッシュ文字は、
ひとつのハッシュ文字に置き換えられます。
以上の場合以外は、バックスラッシュはそのまま渡されます。
変数への代入の際は、ハッシュ文字 (その前にバックスラッシュがないもの)
はコメントの開始となり、そこから論理行の最後までがコメントとなります。
註: このようなアルゴリズムで解釈されるせいで、
バックスラッシュ一文字を値として持つ変数を作るには、
``!='' 演算子を使う方法しかありません。たとえば以下のようにします: BACKSLASH!=echo "\\"
.
以上は、変数の定義に関する説明です。このほか、変数に関してできることは、 変数を評価することです。変数が評価されるのは、変数が ``:='' または ``!='' 演算子の右辺にある場合と、変数がシェルコマンドの一部となっている場合 (コマンドが実行される直前に評価される) です。これら以外の場合、 make(1) は遅延評価をおこないます。つまり、 変数は他の処理がすべてすんだ後に評価されます。 このほか、マニュアルページに記載されている「修飾子」も、 変数を評価します。
修飾子のなかには、文字列を語に分割してから、分割した語に対して操作をするものがあります。 それ以外の修飾子は、文字列全体に対して操作をします。 文字列が語に分割される場合、その分割は、 sh(1) の解釈と同様の方式でおこなわれます。
例外のない規則はありません— .for ループはシェルのクォートの規約には従わず、 空白の並びで分離します。
変数には、取り扱い方が異なる複数の種類の変数があります。 文字列 (strings) と、二種類のリストです。
文字列 (strings) には、
任意の文字を含めることができます。とはいえ、
使うのは印字可能文字だけにしておくのがよいでしょう。
例としては PREFIX
や
COMMENT
があります。
内部リスト (internal lists) は、
シェルコマンドに決して渡されることのないリストです。
内部リストの要素は空白で区切られます。このため、
要素自体に空白を含めることはできません。空白以外の文字はすべて使うことができます。
内部リストは .for ループ内で使うことができます。
例としては DEPENDS
や
BUILD_DEPENDS
があります。
外部リスト (external lists) は、
シェルコマンドに渡すことのできるリストです。外部リストの要素には、
空白を含む任意の文字を含めることができます。このことが理由で、
外部リストは .for ループ内では使うことができません。
例としては DISTFILES
や
MASTER_SITES
があります。
本節では、読者がコードを書く際に使うことになるコードの断片を いくつか説明します。適当なコードがここに載っていない場合は、 あなたのコードをテストして、ここに追加してください。
STRING= foo * bar `date` INT_LIST= # empty ANOTHER_INT_LIST= apache-[0-9]*:../../www/apache EXT_LIST= # empty ANOTHER_EXT_LIST= a=b c=d INT_LIST+= ${STRING} # 1 INT_LIST+= ${ANOTHER_INT_LIST} # 2 EXT_LIST+= ${STRING:Q} # 3 EXT_LIST+= ${ANOTHER_EXT_LIST} # 4
文字列を外部リストに追加する場合 (例 3) は、 その文字列をクォートする必要があります。それ以外の場合は、 クォートを追加してはいけません。内部リストと外部リストは、 その各要素がどちらのリストでも適切に処理されることが確実な場合をのぞき、 統合してはいけません。
EXT_LIST= # empty .for i in ${INT_LIST} EXT_LIST+= ${i:Q}"" .endfor
このコードは、内部リスト
INT_LIST
を外部リスト
EXT_LIST
に変換します。内部リストの要素はクォートされていないので、
変換に際してはクォートする必要があります。
""
を追加する理由は後述します。
時には、任意の文字列を出力したいことがあるかもしれません。 不具合を起こす方法はたくさんありますが、 どんな複雑なものも扱えるような方法は少ししかありません。
STRING= foo bar < > * `date` $$HOME ' " EXT_LIST= string=${STRING:Q} x=second\ item all: echo ${STRING} # 1 echo "${STRING}" # 2 echo "${STRING:Q}" # 3 echo ${STRING:Q} # 4 echo x${STRING:Q} | sed 1s,.,, # 5 printf "%s\\n" ${STRING:Q}"" # 6 env ${EXT_LIST} /bin/sh -c 'echo "$$string"; echo "$$x"'
例 1 は、シェルで構文エラーを起こします。 各文字がそのままコピーされるだけだからです。
例 2 も構文エラーを起こします。また、${STRING}
の末尾の "
文字を除いた場合は、date(1) が実行されてしまいます。
また、$HOME
シェル変数も評価されるでしょう。
例 3 は、echo(1) コマンドの実装によって、 各空白文字の前にバックスラッシュが出力されたり、 されなかったりします。
例 4 は、最初の文字がダッシュでない文字列はすべて適切に処理します。 文字列の最初の文字がダッシュの場合、結果がどうなるかは echo(1) コマンドの実装に依存します。 入力される文字列の最初の文字がダッシュにならないことを保証できる限りは、この形式は適切です。
例 5 は、たとえ文字列がダッシュで始まっていたとしても、 適切に処理します。
例 6 も、あらゆる文字列を適切に処理できますし、 それ自体に問題のあるパイプを使わないので、 より軽い方法です。
EXT_LIST
はクォートする必要はありません。
なぜなら、リストに要素を追加した時に、
すでにクォートされているからです。
内部リストはシェルに渡されないものなので、 例示はありません。
変数が不適切にクォートされたソースは、多くありえます。 本節では、よく知られている例をいくつか掲げます。
リストの値を使うときは常に、
値の冒頭や末尾にある空白がどうなるかを考えてください。
リストが整形式のシェルの式である場合、それぞれの語から冒頭や末尾の空白を取り除くために、
:M*
修飾子を使うことができます。
:M
演算子は、最初にその引数をシェルの規約に従って分割してから、
シェルのグロブ式 *
にマッチする語 (つまり全部)
すべてからなるリストを新たに作ります。
これが必要となる状況は、CPPFLAGS
のような変数を
CONFIGURE_ARGS
に追加する場合です。
configure スクリプトが別の configure スクリプトから呼び出される場合、
呼び出された側のスクリプトは変数から冒頭と末尾の空白を取り除き、
それを別の configure スクリプトに渡します。しかし、この両 configure
スクリプトは、(子の) CPPFLAGS
変数が
親の CPPFLAGS
と同じものであると見込んでいます。
これが、CPPFLAGS
の値を適切に切り取ったものを
渡したほうがよい理由です。そして、以下に掲げるのは、その方法です。
CPPFLAGS= # empty CPPFLAGS+= -Wundef -DPREFIX=\"${PREFIX:Q}\" CPPFLAGS+= ${MY_CPPFLAGS} CONFIGURE_ARGS+= CPPFLAGS=${CPPFLAGS:M*:Q} all: echo x${CPPFLAGS:Q}x # 前後に空白がつく echo x${CONFIGURE_ARGS}x # 適切に切り取られる
上の例にはバグがひとつあります:
${PREFIX}
は適切にクォートされたシェルの式ですが、
シェルの後には C コンパイラーがあり、こちらでも適切に
(今度は C の文法で) クォートされている必要があります。
このため、上で例示したものは、${PREFIX}
の値にバックスラッシュや二重引用符が含まれていない場合に限って、
正しいものになります。これらの文字を含めたい場合、
C の文字列リテラルとして扱われる値をすべてクォートするために、
もう一つの層を追加する必要があります。
:Q
演算子はシェル用のクォートしかできないので、
C コンパイラー用のクォートには使えません。
値が空になりうる場合は、
:Q
演算子が妙な結果を出すことがあります。
以下に 2 種類のまったく異なる事例を掲げますが、
どちらも同じ細工をすることで解決できます。
EMPTY= # empty empty_test: for i in a ${EMPTY:Q} c; do \ echo "$$i"; \ done for_test: .for i in a:\ a:\test.txt echo ${i:Q} echo "foo" .endfor
一つ目の例では、3 行が表示されると思うかもしれませんが、
そのうちの 2 行しか表示されません。これは、
${EMPTY:Q}
が空の文字列に展開され、
シェルからは見えなくなるからです。回避策は、
${EMPTY:Q}""
と書くことです。このパターンは、
${TEST} -z ${VAR:Q}
や ${TEST}
-f ${FNAME:Q}
のように、しばしば見られます
(いずれも、間違いです)。
二つ目の例では、表示されるのは 4 行ではなく 3 行です。
最初に表示される行は a:\ echo foo
のようになります。
これは、値 a:\
のバックスラッシュを
make(1) が継続行として処理し、2 行目が 1 行目の
echo(1) コマンドの引数になってしまうためです。
これを防ぐには、${i:Q}""
と書きます。
pkgsrc の bmake プログラムは、以下のような代入を適切に処理することができません。
_othervar_
が ``-'' 文字を含んでいる場合、
以下のコードを実行すると、閉じ中括弧のひとつが
${VAR}
に含まれてしまいます。
VAR:= ${VAR:N${_othervar_:C/-//}}
もっと複雑なコードの断片と回避策については、
regress/make-quoting
パッケージのテストケース
bug1
をご覧ください。