読者です 読者をやめる 読者になる 読者になる

作業ノート

様々なまとめ、雑感など

makefileを作る

会社で管理しているシステムにC言語で作られたWebシステムがある。OracleのOCIを使ってDBに接続していろいろな処理を行う。10年ほど運用しているらしい。

このシステム、buildするときはshellスクリプトを実行するのだが、shellスクリプト内でgccコマンドを実行している。そのため、例えば一つのファイルを更新し、反映したくても、そのたびにシステム全体をbuildする。

ファイルの数はさほど多くはないが、環境が古いためにbuildに時間がかかる。これはでは、つもりつもって時間を結構ロスすることにもなる。

これを解決したくmakeについて調べたので、まとめてみた。

GNU Make 第3版

GNU Make 第3版

makefile

以下は2つのファイル(hello.c, math.c)から実行ファイル(hello math)を作るためのmakefile。

CC := gcc

TARGETS := hello math

SRCS := hello.c math.c
OBJS := $(SRCS:.c=.o) # 置換参照: $(variable:search=replace)

CFLAGS := -O2 -g
LDLIBS := -lm

all: $(TARGETS)

$(TARGETS): %: %.o

.PHONY: clean
clean:
    rm -f $(OBJS) $(TARGETS)

ルール = ターゲット + 必須項目 + コマンド

target1 ... : prereq1 ...
    command1
    ...

ターゲット(build対象)、必須項目(buildに必要なもの)、コマンド(build方法)を記述したものがルールであり、これをmakefileで定義する。

makeコマンドは、makefileで定義したルールを適用する。

ルール適用時には対象ファイルのタイムスタンプも見る。たとえ適用できるルールがあっても、ターゲットファイルのタイムスタンプが必須項目の全てのファイルのタイムスタンプより新しいなら、そのルールは適用されない。

擬似ターゲット

all: $(TARGETS)

...

.PHONY: clean
clean:
    rm -f $(OBJS) $(TARGETS)

ターゲットは通常、buildするために必要なファイルを指定する。

上記の様に、最初のルール(all)や環境のリセット(clean)などは実体がない。これらは擬似ターゲットと呼ばれ、.PHONYによってそれであることを明示的に示す。

allはmakeの暗黙的な擬似ターゲットで、.PHONYで指定する必要はない。

なお、.PHONY自体は特殊ターゲットと呼ばれるターゲットの一つである。

変数

CC := gcc

変数名は大文字、小文字両方扱うことができ、同名でも別として扱われる。変数が大文字なのは慣習的なもの。

代入

変数に値を代入するのは、大きく分けて=:=。違いは、代入する値に変数がある場合。

=は、定義した変数が参照されるたびに、代入した値の変数が評価される。定義した変数が評価されるとき、代入した変数も評価されるので、その変数の値が変わり、その後に定義した変数を参照すれば、その値も変わる。

:=は、変数を定義した時点で代入する値の変数が評価され、その結果が定義した変数に代入される。定義した時点で値が決まるので、定義した変数は定数になる。

代入は他に、追記+=、条件付き代入?=がある。

自動変数

makeにより自動的に設定される変数もある。

変数名 役割
$@ ターゲット名
$% ターゲットメンバー名
$< 最初の必須項目
$? ターゲットよりも新しい必須項目全て
$^ 全ての必須項目 項目は重複しない
$+ 全ての必須項目 項目は重複する
$* ターゲット名 ただし、suffixがない

いずれも適用するルールによって値が変わるもの。

パターンルール

ルールは上述のように明示的に定義するものだけではなく、特定のパターンに対して適用可能なルールが定義できる。これをパターンルールと呼ぶ。

静的パターンルール

$(TARGETS): %: %.o

$(TARGETS)のターゲットリストに対して、%: %.oというパターンルールを適用する、というルール。これは静的パターンルールと呼ばれる。

パターンルール

上述の%: %.oの部分もパターンルールの一つ。しかしこのパターンルール、makefileには記述してない。

元々makeには、いくつかのパターンルールを暗黙的に定義している。makefileに明示的に記述されていなくてもmakeが持つパターンルールと一致すれば、そのルールが適用される。これを暗黙ルールと呼ぶ。

暗黙ルールは、makeのバージョンによって定義されているものが異なるが

$ make -p

で、その内容が確認できる。手元の環境(3.81)で上述のmakefileを実行すると、以下の2つの暗黙ルールが適用された。

COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
OUTPUT_OPTION = -o $@

%.o: %.c
#  commands to execute (built-in):
        $(COMPILE.c) $(OUTPUT_OPTION) $<

これはCのファイルからオブジェクトファイルを作るためのルール。(hello.c -> hello.o)

LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)

%: %.o
#  commands to execute (built-in):
        $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

これはオブジェクトファイルから実行ファイルを作るためのルール。(hello.o -> hello)

上述のmakefileでは CFLAGSLDLIBSを定義しているが、この値は上記のルールの実行時に適用される。

ちなみに、LOADLIBES LDLIBS という変数があるが、これらは意味合い的には同じで、リンク時のオプションを定義するもの。変数が2つあるのは互換性のためらしく、どちらか一方の変数のみ定義すればいいらしい。

サフィックスルール

今回は使用しなかったが、サフィックスルールというのもある。例えば、

.c.o:
#  Implicit rule search has not been done.
#  Modification time never checked.
#  File has not been updated.
#  commands to execute (built-in):
    $(COMPILE.c) $(OUTPUT_OPTION) $<

である。

サフィックスルールもいくつかは暗黙ルールとして定義されている。ただし、このパターンルールは古い書き方のようなので、互換性のためにあるとみなして本当に必要なときのみ使用することにする。

参考