コンテキストに依って自動的に生成される変数がある

プログラミング言語の多くが「変数」という機能を備えている。そして、変数にはプログラミング言語が最初から定義しているものや、コンテキストによって自動的に生成されるものがある。呼び方はさまざまだが、後者は「自動変数」と呼ばれることが多い。

Makefileにも自動変数があり、これを使った書き方がよくされる。いずれ詳細は説明するが、何らかのパターンを指定してルールを書くことが結構頻繁にあるため、自動変数を使わないとそもそもの記述ができないのだ。

「仕事の内容を整理する」という目的において自動変数を使うことはあまりないのだが、知っておいて損をするものではない。既存の多くのMakefileが自動変数を多用しているので、読めるようにしておいて悪いことはない。

ただし、Makefileの自動変数は理解するのに少々苦労するかもしれない。特に、プログラミングの経験がない場合にはわかりづらい点も多いだろう。難しすぎるようなら、「そんな機能があるのだな」程度に把握しておいてもらえればOKだ。

まず、次に代表的な自動変数をまとめておく。

自動変数 内容
$@ ターゲット名やターゲットのファイルパス。複数のファイルパスが存在する場合(パターンルールという指定が使われるとターゲットの対象となるファイルパスが複数存在することになる)には、対象となっているファイルパスのうちその文脈で使われるファイルパスが展開される
$% ターゲットがアーカイブメンバーの場合にターゲットの名前、それ以外の場合には空になる
$< 最初の依存先の名前(暗黙のルールも含む)
$^ 全ての依存先の名前(スペース区切り)
$+ 全ての依存先の名前(スペース区切り、Makefileにおけるリスト順に従って重複もあり)
$? ターゲットよりも新しい全ての依存先の名前(スペース区切り)
$| オーダーオンリー前提条件の名前
$∗ 暗黙のルールが一致する拡張子を含まないパス

この説明だけでどんな変数かわかるようなら、今回は読まなくて大丈夫だ。すでにMakefileのことをよく理解している。

これら自動変数は主にパスを保持することになるわけだが、パスは大抵ディレクトリとファイルの名前で構成されており、GNUのMakefileでは次のような書き方でディレクトリのみやファイルのみを取り出すことができるようになっている。

自動変数の派生使用例 内容
$(@D) ターゲットのファイルパスにおけるディレクトリ
$(@F) ターゲットのファイルパスにおけるファイル
$(%D) アーカイブメンバのディレクトリ
$(%F) アーカイブメンバのファイル
$(<D) 最初の依存先のディレクトリ
$(<F) 最初の依存先のファイル
$(^D) 全ての依存先のディレクトリ
$(^F) 全ての依存先のファイル
$(?D) ターゲットよりも新しい全ての依存先のディレクトリ
$(?F) ターゲットよりも新しい全ての依存先のファイル
$(∗D) 拡張子を含まないパスのディレクトリ
$(∗F) 拡張子を含まないパスのファイル

この方法で抽出したディレクトリは末尾にスラッシュを含まない。ファイルの方は$(notdir $変数)の表記と同じになる。「dir」や「notdir」という関数を使っても似たようなことができるので、どちらを使うかは好みの問題だ。

これら自動変数は基本的に1文字で構成されており、正直意味がわかりにくい。慣れの問題もあるのだが、しばらく使っていないと何を意味していたか忘れてしまいがちだ。これは覚えておけなくても仕方ないかなと思う。

次に、特に使うことが多い自動変数の動きを取り上げる。自動変数は説明を読むよりも、動きを追っていった方が理解しやすい。

$@ - ターゲットに展開

正確に説明しようとするとかなり複雑になるのだが、要するにターゲット名に展開されるが「$@」だ。

次のようなMakefileを用意して実行してみよう。

source1.o: source1.c
    echo $@

実行すると次のようになる。

% make
echo source1.o
source1.o
% 

詳しいことはターゲットの説明をするときに取り上げるが、Makefileでは「パターンルール」と呼ばれる汎用的な記述が可能で、1段階抽象度を上げたターゲットが書けるようになっている。記述しなければならないターゲットが100とか200とかになると手が追いつかないので、それらを抽象化してまとめて1つのターゲットに書けるというものだ。

次のMakefileはその一つで、「*.c」というファイルを「*.o」というファイルに変換するためのターゲット、という記述だ。今はよくわからなくて大丈夫だ。この書き方については、ターゲットについて説明するときに説明する。

OBJS=   source1.o source2.o source3.o source4.o source5.o

test: $(OBJS)

%.o: %.c
    echo $@

このMakefileを実行すると次のようになる。

% make
echo source1.o
source1.o
echo source2.o
source2.o
echo source3.o
source3.o
echo source4.o
source4.o
echo source5.o
source5.o
% 

「%.o: %.c」というパターンルールはさまざまなファイルに適応する。この場合の「$@」というのは、そのパターンに一致したものの一つとなる。どのターゲットになるかは、文脈に依存する。

次のパターンルールをご覧いただきたい。

%.o: %.c
    echo $@

上記は、実際には次のように書いたのと同じだと考えるとわかりやすいと思う。

source1.o: source1.c
    echo $@

source2.o: source2.c
    echo $@

source3.o: source3.c
    echo $@

source4.o: source4.c
    echo $@

source5.o: source5.c
    echo $@

「@」というのは標的や宛先を指し示す場合に使われることが多い記号だ。このため、$@という書き方がターゲットを意味している、と考えると、何となく覚えられるのではないだろうか。

$< - 依存するものひとつに展開

$@とは逆に、そのターゲットが必要とするターゲットやファイルに展開されるのが「$<」だ。

次のMakefileを見てみよう。

OBJS=   source1.o source2.o source3.o source4.o source5.o

test: $(OBJS)

%.o: %.c
    echo $<

実行すると次のようになる。

% make
echo source1.c
source1.c
echo source2.c
source2.c
echo source3.c
source3.c
echo source4.c
source4.c
echo source5.c
source5.c
% 

今度は次のMakefileだ。

OBJS=   source1.o source2.o source3.o source4.o source5.o

test: $(OBJS) $(OBJS)
    echo $<

%.o: %.c

実行すると次のようになる。

% make
echo source1.o
source1.o
% 

「$<」の特徴は、依存しているものでも最初の1つにしか展開されない点にある。「$@」と「$<」で対になって使われることが多い自動変数なのだが、それは依存先が1つの場合にちゃんと機能するのだ。依存先が複数存在する場合にはこの書き方はうまくいかないので注意しよう。

$^ - 依存するもの全部に展開

$<とは違い、依存するもの全部に展開するのが「$^」だ。次のMakefileを見てみよう。

OBJS=   source1.o source2.o source3.o source4.o source5.o

test: $(OBJS) $(OBJS)
    echo $^

%.o: %.c

実行すると次のようになる。

% make
echo source1.o source2.o source3.o source4.o source5.o
source1.o source2.o source3.o source4.o source5.o
% 

依存先が全て展開されていることがわかる。

ただし、重複した指定は自動的に1つにまとめられていることもわかる。「$^」による展開は重複指定は整理してユニークな依存先に展開される。

$+ - 依存するもの全部に展開(重複はそのまま)

「$^」と同じように全ての依存先に展開されるのが「$+」だが、こちらは重複していても整理することなく全てそのまま展開する。次のMakefileを見てみよう。

OBJS=   source1.o source2.o source3.o source4.o source5.o

test: $(OBJS) $(OBJS)
    echo $+

%.o: %.c

実行すると次のようになる。

% make
echo source1.o source2.o source3.o source4.o source5.o source1.o source2.o source3.o source4.o source5.o
source1.o source2.o source3.o source4.o source5.o source1.o source2.o source3.o source4.o source5.o
% 

重複した指定もそのまま展開されていることがわかる。「$^」や「$+」は特定のケースで必要になる指定なのだが、筆者の肌感覚的にそれほど利用頻度は高くない。こういった指定ができる、ということを覚えておくくらいでよいかもしれない。

$(@D)と$(@F)

ここで取り上げた自動変数はファイルパスが入っていることが多いので、そのパスからディレクトリだけ取り出したり、ファイル名だけ取り出したりという使い方もよく行われている。書き方として「$(@D)」でディレクトリ名、「$(@F)」でファイル名となる。この書き方をした場合、ディレクトリ名の末尾にはスラッシュが含まれないという特徴がある。

$(@D)と$(@F)の使用例は次の通りだ。

DIR=    ./../stuffs
OBJS=   $(DIR)/source1.o $(DIR)/source2.o $(DIR)/source3.o $(DIR)/source4.o $(DIR)/source5.o

test: $(OBJS) $(OBJS)
    echo dir: $(@D)
    echo file: $(@F)

$(DIR)/%.o: %.c

実行すると次のようになる。

% make
echo dir: .
dir: .
echo file: test
file: test
% 

展開するディレクトリがない場合にはカレントディレクトリを意味する「.」が使われる。

$(<D)と$(<F)

$(<D)と$(<F)のサンプルは次の通りだ。

DIR=    ./../stuffs
OBJS=   $(DIR)/source1.o $(DIR)/source2.o $(DIR)/source3.o $(DIR)/source4.o $(DIR)/source5.o

test: $(OBJS) $(OBJS)
    echo dir: $(<D)
    echo file: $(<F)

$(DIR)/%.o: %.c

実行すると次のようになる。

% make
echo dir: ../stuffs
dir: ../stuffs
echo file: source1.o
file: source1.o
% 

ディレクトリとファイルがそれぞれ展開されていることがわかる。

$(^D)と$(^F)

$(^D)と$(^F)のように複数の候補が展開される場合には、展開されるそれぞれに対してディレクトリやファイルの取り出しが行われる。サンプルは次の通りだ。

DIR=    ./../stuffs
OBJS=   $(DIR)/source1.o $(DIR)/source2.o $(DIR)/source3.o $(DIR)/source4.o $(DIR)/source5.o

test: $(OBJS) $(OBJS)
    echo dir: $(^D)
    echo file: $(^F)

$(DIR)/%.o: %.c

実行すると次のようになる。

% make
echo dir: ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs
dir: ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs
echo file: source1.o source2.o source3.o source4.o source5.o
file: source1.o source2.o source3.o source4.o source5.o
% 

展開される一つ一つがディレクトリ、およびファイルとして取り出されていることがわかるだろう。

$(+D)と$(+F)

$(+D)と$(+F)のサンプルは次の通りだ。

DIR=    ./../stuffs
OBJS=   $(DIR)/source1.o $(DIR)/source2.o $(DIR)/source3.o $(DIR)/source4.o $(DIR)/source5.o

test: $(OBJS) $(OBJS)
    echo dir: $(+D)
    echo file: $(+F)

$(DIR)/%.o: %.c

実行すると次のようになる。

% make
echo dir: ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs
dir: ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs ../stuffs
echo file: source1.o source2.o source3.o source4.o source5.o source1.o source2.o source3.o source4.o source5.o
file: source1.o source2.o source3.o source4.o source5.o source1.o source2.o source3.o source4.o source5.o
% 
  • 実行サンプル

    実行サンプル

重複が整理されずに出力されていることがわかる。

特に覚えておきたい自動変数

1文字変数はなかなか覚えにくい。ここではまず、次の自動変数を覚えておいていただきたい

自動変数 覚えておきたいこと
$@ ターゲット
$< ターゲットが依存するもの(最初の一つ)
$^ ターゲットが依存するもの(全部。重複なし)

少なくとも「$@」と「$<」は覚えておきたいところだ。この2つを覚えておけば、読めるMakefileの範囲が広がるはずだ。

参考