Chapter 12. Makefile におけるプログラミング

Table of Contents

12.1. 警告
12.2. Makefile 変数
12.2.1. 命名規約
12.3. コードの断片
12.3.1. リストに要素を追加する
12.3.2. 内部リストを外部リストに変換する
12.3.3. シェルコマンドに値を渡す
12.3.4. クォートの指針
12.3.5. BSD Make のバグの回避方法

pkgsrc は、多くの Makefile の断片からなっており、 この各断片が、pkgsrc システムの各部分を明確に形成しています。 pkgsrc のような大規模なシステムのプログラミング言語として make(1) システムを使う場合、コードを適切かつわかりやすい状態に保つために、 いくらかの規律が必要です。

Makefile プログラミングの基本的な構成要素は、 変数 (実はマクロ) とシェルコマンドです。 シェルコマンドは、awk(1) プログラムのような複雑なものになることもあります。 各シェルコマンドを意図どおりに動かすため、変数を使うときは、 すべての変数を適切にクォートすることが必要です。

本章では、Makefile で頻出するいくつかのパターンと、 それらに伴う落とし穴を説明します。

12.1. 警告

  • ルールのターゲットとしてファイルを作る場合、 常に、データをまず一時ファイルに書き込んでから、 最後にファイル名を変えるようにしてください。 そうしておかないと、ファイルの生成の途中にエラーが起きた場合、 利用者が 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) のように) 失敗した場合には、 削除はおこなわれません

12.2. Makefile 変数

Makefile 変数は文字列を値として持ち、 文字列は 5 種類の演算子 ``='', ``+='', ``?='', ``:='', ``!='' を使って操作することができます。演算子については make(1) マニュアルページに説明があります。

Makefile の変数が解釈される際、 ハッシュ文字 ``#'' とバックスラッシュ文字 ``\'' は特別扱いされます。 バックスラッシュに改行が続く場合、当該バックスラッシュの直前にあるあらゆる空白・ 当該バックスラッシュ・改行・改行の直後にあるあらゆる空白は、 ひとつのスペースに置き換えられます。 バックスラッシュ文字とその直後に続くハッシュ文字は、 ひとつのハッシュ文字に置き換えられます。 以上の場合以外は、バックスラッシュはそのまま渡されます。 変数への代入の際は、ハッシュ文字 (その前にバックスラッシュがないもの) はコメントの開始となり、そこから論理行の最後までがコメントとなります。

註: このようなアルゴリズムで解釈されるせいで、 バックスラッシュ一文字を値として持つ変数を作るには、 ``!='' 演算子を使う方法しかありません。たとえば以下のようにします: BACKSLASH!=echo "\\".

以上は、変数の定義に関する説明です。このほか、変数に関してできることは、 変数を評価することです。変数が評価されるのは、変数が ``:='' または ``!='' 演算子の右辺にある場合と、変数がシェルコマンドの一部となっている場合 (コマンドが実行される直前に評価される) です。これら以外の場合、 make(1) は遅延評価をおこないます。つまり、 変数は他の処理がすべてすんだ後に評価されます。 このほか、マニュアルページに記載されている「修飾子」も、 変数を評価します。

修飾子のなかには、文字列を語に分割してから、分割した語に対して操作をするものがあります。 それ以外の修飾子は、文字列全体に対して操作をします。 文字列が語に分割される場合、その分割は、 sh(1) の解釈と同様の方式でおこなわれます。

例外のない規則はありません— .for ループはシェルのクォートの規約には従わず、 空白の並びで分離します。

変数には、取り扱い方が異なる複数の種類の変数があります。 文字列 (strings) と、二種類のリストです。

  • 文字列 (strings) には、 任意の文字を含めることができます。とはいえ、 使うのは印字可能文字だけにしておくのがよいでしょう。 例としては PREFIXCOMMENT があります。

  • 内部リスト (internal lists) は、 シェルコマンドに決して渡されることのないリストです。 内部リストの要素は空白で区切られます。このため、 要素自体に空白を含めることはできません。空白以外の文字はすべて使うことができます。 内部リストは .for ループ内で使うことができます。 例としては DEPENDSBUILD_DEPENDS があります。

  • 外部リスト (external lists) は、 シェルコマンドに渡すことのできるリストです。外部リストの要素には、 空白を含む任意の文字を含めることができます。このことが理由で、 外部リストは .for ループ内では使うことができません。 例としては DISTFILESMASTER_SITES があります。

12.2.1. 命名規約

  • 下線で始まる変数名はすべて、 pkgsrc の基盤が使うために予約されています。 そのような変数名はパッケージの Makefile では使ってはいけません。

  • .for ループでは、 反復変数の変数名は小文字にします。

  • リスト変数はすべて、PKG_OPTIONSDISTFILES のように、 「複数形」の名前にします。

12.3. コードの断片

本節では、読者がコードを書く際に使うことになるコードの断片を いくつか説明します。適当なコードがここに載っていない場合は、 あなたのコードをテストして、ここに追加してください。

12.3.1. リストに要素を追加する

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) は、 その文字列をクォートする必要があります。それ以外の場合は、 クォートを追加してはいけません。内部リストと外部リストは、 その各要素がどちらのリストでも適切に処理されることが確実な場合をのぞき、 統合してはいけません。

12.3.2. 内部リストを外部リストに変換する

EXT_LIST=       # empty
.for i in ${INT_LIST}
EXT_LIST+=      ${i:Q}""
.endfor

このコードは、内部リスト INT_LIST を外部リスト EXT_LIST に変換します。内部リストの要素はクォートされていないので、 変換に際してはクォートする必要があります。 "" を追加する理由は後述します。

12.3.3. シェルコマンドに値を渡す

時には、任意の文字列を出力したいことがあるかもしれません。 不具合を起こす方法はたくさんありますが、 どんな複雑なものも扱えるような方法は少ししかありません。

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 はクォートする必要はありません。 なぜなら、リストに要素を追加した時に、 すでにクォートされているからです。

内部リストはシェルに渡されないものなので、 例示はありません。

12.3.4. クォートの指針

変数が不適切にクォートされたソースは、多くありえます。 本節では、よく知られている例をいくつか掲げます。

  • リストの値を使うときは常に、 値の冒頭や末尾にある空白がどうなるかを考えてください。 リストが整形式のシェルの式である場合、それぞれの語から冒頭や末尾の空白を取り除くために、 :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}"" と書きます。

12.3.5. BSD Make のバグの回避方法

pkgsrc の bmake プログラムは、以下のような代入を適切に処理することができません。 _othervar_ が ``-'' 文字を含んでいる場合、 以下のコードを実行すると、閉じ中括弧のひとつが ${VAR} に含まれてしまいます。

VAR:=   ${VAR:N${_othervar_:C/-//}}

もっと複雑なコードの断片と回避策については、 regress/make-quoting パッケージのテストケース bug1 をご覧ください。