🔥

Makefile#2: Rule in Makefile

2020/09/22に公開

Makefile に書く内容は9割がこれのため。

自動変数についても参照すべし。

明示的ルール

とくに捻りのない、素直な方法。
ターゲットは一つ以上持てるので、こんなのも可能

vpath.o variable.o: make.h config.h getopt.h gettext.h dep.h

これは以下の同じ

vpath.o: make.h config.h getopt.h gettext.h dep.h
variable.o: make.h config.h getopt.h gettext.h dep.h

あと、必須項目は別けてかくことも出来る。makeはターゲットを見つけ次第必須項目を 追加 していく。

vpath.o: make.h config.h
vpath.o: getopt.h gettext.h dep.h

ワイルドカード

~, *, ?, [...], [^...] などのワイルドカードも使える。
ターゲット、必須項目、コマンド行とそれぞれ使えるが、コマンド行のワイルドカードはshellによる展開になる。

  • ~

    ホームディレクトリ

    ~takio

    と書くと指定ユーザーのホームディレクトリとなる

  • *

    0文字以上のなんでも

  • ?

    1文字以上のなんでも

  • [...]

    文字集合

    [a-z]

    とか

  • [^...]

    [...]の否定(補集合)

こんな感じで使えるけど、大雑把すぎるので注意が必要
というか非推奨

prog: *.c
    $(CC) -o $@ $^

.PHONY (擬似ターゲット)

通常ターゲットと同名のファイル名がある場合、そのファイルが更新不要であればコマンド行は実行されない。

一方で all, clean, とかファイルではないけどターゲットのラベルみたいに指定したい時に、 .PHONY を使用する。

.PHONY: clean
clean:
    rm -f *.o lexer.c

上記のように指定すれば、'clean' という名のファイルが存在していても影響がない。

  • 一般的な擬似ターゲット

    ターゲット 機能
    all アプリケーションを構築するすべての作業を行う
    clean ソースから作られたバイナリを削除する
    distclean 元の配布物に含まれて田舎立つベテの生成物を削除する
    TAGS tags作成
    info Texinfoのソースより GNU info ファイルを作成する
    check このアプリケーションに関する全てのテストを実行

VPATH, vpath

こんな感じのソースツリーとすると

.
|-- include
|   |-- counter.h
|   `-- lexer.h
|-- Makefile
`-- src
    |-- counter.c
    |-- count_words.c
    `-- lexer.l

VPATH

makefile の位置でmakeを実行するには、各ファイルへパスを通さないとダメだけど、 VPATH を使うと良い感じになる。

VPATH = src

これで counter.c, count_words.c へパスが通るが、インクルードパスは CPPFLAGS とか使わないとダメらしい。

CPPFLAGS = -I include

もちろんコマンド行はシェルに渡されるので、この変数は無効

vpath

VPATH 変数はルール全てに適応されてしまって、同じファイルとかあると困ったことに。。。

で、 vpath を使えば欠点を補えるが

vpath %.c src
vpath %.l src
vpath %.h include

ちと単純すぎて使えそうに無いかも。。。

パターンルール

組み込みルール

GNU make には組み込みルールってのがあって内容に関しては、次のコマンドラインオプションで参照できる。

$ make --print-data-base

出力の内容はパターンルールで構成されている。
こんな感じ

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

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

%.c: %.l
#  commands to execute (built-in):
	@$(RM) $@ 
	$(LEX.l) $< > $@

次のような冗長なルールに対して、組み込みルールを適応すると冗長性が解消される。

  • before

    count_words: count_words.o counter.o lexer.o -lfl
        gcc $^ -o $@
    
    count_words.o: count_words.c
        gcc -c $<
    
    counter.o: counter.c
        gcc -c $<
    
    lexer.o: lexer.c
        gcc -c $<
    
    lexer.c: lexer.l
        flex -t $< > $@
    
  • after

    VPATH = src include
    CPPFLAGS = -I include
    
    count_words: counter.o lexer.o -lfl
    count_words.o: counter.h
    counter.o: counter.h lexer.h
    lexer.o: lexer.h
    

最初の実行ファイルの依存関係として下記が合致し参照される。結果 count_words.o が認識される。

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

下記のパタンルールで .c が .o へと変換される。

Makefile内のルールにコマンド行を記述していないのがミソで、書いてしまうと暗黙のルール(Implict rule)が適応されない。

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

さらに .l に関して .c への変換は次の通り

%.c: %.l
#  commands to execute (built-in):
	@$(RM) $@ 
	$(LEX.l) $< > $@

Implict ruleで生成されたファイルはmakeで認識されていて、ターゲット完成後削除される。

ちなみに、どんなルールが適応されてるかは、下記を実行すると見れる。

$ make --print-data-base

--just-print オプションを付けると、実行の予行内容も見れる。

$ make --just-print --print-data-base
$ make -n -p    # 短縮形

% (パターン)

% を使うことでパタンを指定できる。ただし文字列中に1回のみ使用できる。

%,v
s%.o
wrapper_%

静的パターンルール

特定のターゲットに対して提供されるルール

こんな感じ

$(OBJECTS): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@

$(OBJECTS) 変数でリストしたファイルに対してのみ、 "%.o: %.c" ルールが適応される。
$(OBJECTS) 変数内の *.o が %.o のパタンに合致しターゲットとして認識され、 %.c として必須項目へ設定される。

サフィックスルール

一般的なmakeでもサポートする文法。

  • ダブルサフィックスルール

    .c.o:
        $(COMPILE.c) $(OUTPUT_OPTION)A $<
    

    これは GNU make のパターンルールだと下記と同じ

    %.o: %.c
        $(COMPILE.c) $(OUTPUT_OPTION)A $<
    
  • シングルサフィックスルール

    .p:
        $(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@
    

    同じく GNU make だと

    %: %.p
        $(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@
    

このサフィックスルールを適応するには .SUFFIXES に設定されてないとダメっぽい。一部だが、組み込みルールはこんな設定

.SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l

独自に追加したければ、

.SUFFIXES: .pdf .html

と書いておけば追加になる。

拡張子が期待と違う設定になってて困る場合は、クリアしておく
(--no-builtin-rules or -r でもOK)

.SUFFIXES:

とまぁ、文法的には GNU make の方が明快なので、知識程度に覚えておく

暗黙ルール (Implict rule)

makefileに指定するターゲットに対しての コマンド行を書かなければ 、暗黙ルール (Implict rule) が適応される。

また組み込みルールが何かの弊害になる場合削除もできる。
例えばLispの拡張子が .l の場合、flex出力を .c に変換する機能と衝突する。
下記のようにルールを取り除けばOK

%.o: %.l
%.c: %.l

コマンド行の伴わないパタンはルール削除を示す。

例えばCの単一ファイルの場合

関係のある組み込みルールは

%.c:

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

たとえば、 main.c のみのプロトタイプ・プログラムを作った場合、makefileを作成せずにこのようにmake出来る。

$ make main
cc     main.c   -o main

また、組み込みルールにて勝手に作成された中間ファイルはターゲット完成後に削除される。

カスタマイズ

組み込みルールは便利だが、カスタマイズの方法はしっかり把握すべし。

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

このようにデフォルトでは変数が設定されてる。なのでこれに準ずれば楽チン

変数名 内容
CC %.c とかに使われるコンパイラ
CFLAGS 基本的にはコンパイラオプション
CPPFLAGS プリプロセッサ(PP) 向けのオプション
TARGET_ARCH アーキテクチャ指定

カスタマイズ性確保のために上書き設定も良いとされてる。

COMPILE.c = $(CC) $(CFLAGS) $(INCLUDES) $(CPPFLAGS) $(TARGET_ARCH) -c
INCLUDES = -I project/include

特殊ターゲット

.PHONYのようなmakeのデフォルト動作を変更するターゲット。

特殊ターゲット: 必須項目

12種類らしいが、大別すると3種類になるらしい。

  • makeの挙動を変更するためのもの
  • 帯域フラグとして、対象の必須項目を無視するためのもの
  • .SUFFIXES

依存関係

gccには依存関係の生成の為のコマンドラインオプションがある

$ echo "#include <stdio.h>" > stdio.c
$ gcc -M stdio.c
stdio.o: stdio.c /usr/include/stdc-predef.h /usr/include/stdio.h \
 /usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
 /usr/include/x86_64-linux-gnu/bits/wordsize.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
 /usr/lib/gcc/x86_64-linux-gnu/4.8/include/stddef.h \
 /usr/include/x86_64-linux-gnu/bits/types.h \
 /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \
 /usr/include/_G_config.h /usr/include/wchar.h \
 /usr/lib/gcc/x86_64-linux-gnu/4.8/include/stdarg.h \
 /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
 /usr/include/x86_64-linux-gnu/bits/sys_errlist.h

上記のままで使えそうだが、gccのこの出力を中間ファイル .d で扱ったとして、このファイルを含めた依存関係は

stdio.o stdio.d : stdio.c /usr/include/stdc-predef.h /usr/include/stdio.h \
  ~省略~

なのでmakefileへ取り込む際のコマンドスクリプトの書き方にコツが要る。

%.d: %.c
	$(CC) -M $(CPPFLAGS) $< > $@.$$$$;			\
	sed 's,\($*\)\.o[ :]*,\1.o $@: ,g' < $@.$$$$ > $@;	\
	rm -f $@.$$$$

sedはLinuxコマンドで置き換えとかするやつ。 $$ はサブシェルのプロセス番号に置き換わるらしい。なのでコマンドスクリプトは一綴になっている。

($).o[ :]

これは検索部で、ターゲット名の本体部を正規表現で抽出している。

\1.o $@:

こっちは置換部。正規表現で抽出したターゲットほ本体部を .o と .d で置き換えている。

今までのやつに適応すると。。。

  • Makefile

    VPATH = src include
    CPPFLAGS = -I include
    
    SOURCES =	count_words.c	\
    		lexer.c		\
    		counter.c
    
    count_words: counter.o lexer.o
    
    include $(subst .c,.d,$(SOURCES))
    
    %.d: %.c
    	$(CC) -M $(CPPFLAGS) $< > $@.$$$$;			\
    	sed 's,\($*\)\.o[ :]*,\1.o $@: ,g' < $@.$$$$ > $@;	\
    	rm -f $@.$$$$
    
  • result

    $ make -n
    Makefile:13: count_words.d: No such file or directory
    Makefile:13: lexer.d: No such file or directory
    Makefile:13: counter.d: No such file or directory
    cc -M -I include src/counter.c > counter.d.$$;				\
        sed 's,\(counter\)\.o[ :]*,\1.o counter.d: ,g' < counter.d.$$ > counter.d;	\
        rm -f counter.d.$$
    lex  -t src/lexer.l > lexer.c
     ~省略~
    
  • counter.d

    counter.o counter.d: src/counter.c /usr/include/stdc-predef.h include/lexer.h \
     include/counter.h
    

ライブラリ管理

  • ar (アーカイバ)の基本。

    $ ar rv libcounter.a counter.o lexer.o
    

    rv はコマンドラインオプションで r(=replace), v(=verbose) かと

  • gcc でのライブラリの扱い

    -lc でも libc.a のように指定しても取り込んでくれる
    -l を使うとパスを検索してくれる。

ターゲットとしてライブラリの設定

普通に考えるとこう書くはず

libcounter.a: counter.o lexer.o
	$(AR) $(ARFLAGS) $a $^

ちなみに、組み込み設定では

# default
ARFLAGS = rv
# default
AR = ar

$^ は必須項目を全てリストするので、 $? を使うと少しスマート

libcounter.a: counter.o lexer.o
	$(AR) $(ARFLAGS) $a $?

これに便利な組み込みルールが設定されている たぶんこれ。。。

(%): %
#  commands to execute (built-in):
	$(AR) $(ARFLAGS) $@ $<

これを使うとこう書くことになる。

libcounter.a: libcounter.a(lexer.o counter.o)

libcounter.a(%): % と認識される(のか?)、で結果として

$@ = libcounter.a
$< =lexer.o or counter.o

となって、 AR が実行される。

必須項目にライブラリを設定

ファイル名でも -l を使ってもOK。

-lを使った場合、自動変数($^, $?)へは探索後の絶対パスへ置き換えて設定される。ただしターゲットへの指定には -l は使えず、ルールが構築できない。。。

  • Makefile

    count_words: counter.o lexer.o -lcounter
    libcounter.a: libcounter.a(lexer.o counter.o)
    
  • shell

    $ make
    make: *** No rule to make target `-lcounter', needed by `count_words'.  Stop.
    

二重コロンルール

通常のルールは一ターゲットにコマンドスクリプトは一括りに統一されるが。

file-list:: generate-list-script
	chmod +x $<
	generate-list-script $(files) > file-list

file-list:: $(files)
	generate-list-script $(files) > file-list

こんな感じで書くと同一ターゲットの別必須項目毎に違う処理を記述出来る。 あんまり使い道ないけど

Discussion