make/makefileは,大きいプログラムのどの断片が再コンパイルされる必要があるかという事と,それらを再コンパイルする発行コマンドを自動的に決定するユーティリティ/設定ファイルです.
コンピュータサイエンスの基本的な考え方のひとつに冗長性をなくして効率化する構成を追及するというのがあります.
情報理論(bitの理論),HTMLとブラウザ,高級言語と機械語など,ひとつの仕様(表現)から環境や状況にあわせて実装(記述)をつくりだす構成が好まれます.
ひとつのファイルでさまざまなコマンドを制御するmake/makefileを制することはコンピュータサイエンスを制することになります.
Makefile は以下のようなルールからなる.
コマンドラインから
ターゲット:依存するファイル コマンド
とすることで,「ターゲット」が存在しない,あるいはターゲットよりも新し い「依存するファイル」場合にコマンドが実行されます.また,コマンド行の 始まりは必ずタブというのも基本です. したがってtest1 が main.c と hello.c からコンパイルされる場合は以下の ようにかきます.
make (ターゲット)
これでコマンドラインから以下のように打ち込めばコンパイルできます.
test1: main.c hello.c gcc -o test1 main.c hello.c
またもう一度 make test1 としても「ターゲット」が存在し, 「ターゲット」が「依存するファイル」よりも新しくないので
% make test1 gcc -o test1 main.c hello.c
のようにコマンド行が実行されず,コンパイルされません.
% make test1 make: `test1' is up to date.
しかし,touch hello.c として hello.c を更新すると,
のように再度コンパイルが実行されます.
% touch hello.c % make test1 gcc -o test1 main.c hello.c
touchコマンドは man touch とすると
と説明されているコマンドです.
DERIPTION Update the access and modification times of each FILE to the current time.
上の例ではhello.cが更新された場合でもmain.cのコンパイルが行われてしまいます.
これは非効率です.コンピュータサイエンティストは非効率を嫌わなければなりません.
これを回避するためには以下のように main.c から main2.o を作るルールと,
hello.c から hello2.o を作るルールをMakefileに書き,
test2 は hello2.o と main2.o から作られるようにします.
これでmake test2とすることでコンパイルできます.
main2.o: main.c gcc -o main2.o -c main.c hello2.o: hello.c gcc -o hello2.o -c hello.c test2: main2.o hello2.o gcc -o test2 main2.o hello2.o
ここでtouch hello.cとすると,hello.cをコンパイルしhello2.o をつくり, これと以前コンパイルしたmain2.oからtest2をコンパイルする.
% make test2 gcc -o main2.o -c main.c gcc -o hello2.o -c hello.c gcc -o test2 main2.o hello2.o
% touch hello.c % make test2 gcc -o hello2.o -c hello.c gcc -o test2 main2.o hello2.o
ルールを書くときに同じことを何回も書くのはめんどくさいと思うのが正しいコンピュータサイエンティストである.したがって,makefileに書くべき文字数を減らすために<自動変数がある.
$@ がターゲットのファイル名,$^は依存するファイル名であるため, このmakefileは上の例と同じことになる. ほかには
test3: main3.o hello3.o gcc -o $@ $^ main3.o: main.c gcc -o $@ -c $^ hello3.o: hello.c gcc -o $@ -c $^
$@ : ターゲットファイル名
$% : ターゲットがアーカイブメンバだったときのターゲットメンバ名
$< : 最初の依存するファイルの名前
$? : ターゲットより新しいすべての依存するファイル名
$^ : すべての依存するファイルの名前
$+ : Makefileと同じ順番の依存するファイルの名前
$* : サフィックスを除いたターゲットの名前
がある.
また,C言語では必ず.cから.oファイルが作られる,ということを利用し,
これをルール化したのがサフィックスルールである.サフィックスとは拡張子のことです.
.c.o: というターゲットは,.oというファイルが必要になれば,これを.cからつくる
というルールである.自動変数である $< をつかっている.
.c.o: gcc -c $< test4: main.o hello.o gcc -o $@ $^ main.o: hello.h hello.o: hello.h
makefileの中では変数が利用できます.ここではmain.c hello.cからターゲットとなる プログラムをコンパイルし作りますが,変数を使うことでターゲットを作るために新しいソースファイルが必要になった際の手間が省けます.
ここでは,SRC = main.c hello.c で SRC という変数に main.c hello.c を代入しています.
SRC = main.c hello.c OBJ = $(SRC:%.c=%.o) test5: $(OBJ) gcc -o $@ $(OBJ)
makefileには変数に代入された文字列を操作するための関数が用意されています.たとえば以下のmakefileでは,$(patsubst pattern,replacement,text)という関数を利用し,textからpatternに一致するものをreplacementに置換しています.%はワイルドカードとして働きます.
関数の例としては
%6.o : %.c gcc -o $@ -c $< OBJ6 = $(patsubst %.c,%6.o,$(SRC)) test6: $(OBJ6) gcc -o $@ $(OBJ6)
関数のなかで最も強力なのがshell関数である.shellコマンドを呼び出すことができる.以下のmakefileでは UNAME = $(shell whoami) は whoami コマンドの結果を UNAME 変数に代入している.
また makefile のコマンド行はshellコマンドが利用できる上記のように書けば UNAME 変数の名前のディレクトリがなければ mkdir $(UNAME) でディレクトリを作っている. if 文の前に@をつけることで,make 実行時のこの行を表示しない.
UNAME = $(shell whoami) %7.o : %.c gcc -o $@ -c $< OBJ7 = $(patsubst %.c,%7.o,$(SRC)) test7: $(OBJ7) @if [ ! -d $(UNAME) ]; then \ echo ";; mkdir $(UNAME)"; mkdir $(UNAME); \ fi gcc -o $(UNAME)/$@ $^
以下のようにすることで,main.c から $(UNAME)ディレクトリ (ここではAdministrator/ ディレクトリ)に Administrator/main.o を作っている.このようなmakefileを作ることで,ひとつのソースファイルからOS毎にコンパイルする,あるいはユーザ毎にコンパイルすることが可能になる.
OBJ8 = $(patsubst %.c,$(UNAME)/%.o,$(SRC)) $(OBJ8) : $(UNAME)/%.o: %.c gcc -o $@ -c $< $(UNAME)/test8 : $(OBJ8) gcc -o $@ $(OBJ8) test8: $(UNAME)/test8
JSKでは以下のようなifeq 書式やmakedepend などが利用されている.
ifeq ($(UNAME), Administrator) depend: makedepend $(SRC) -f - -Y -p$(UNAME)/ ./ > Makefile.deps endif
以下のようなルールを書けばカレントディレクトリのアーカイブを作ることができる.
tgz: clean rm -fr $(UNAME) tar -C ../ -cvzf main.tgz ./make tar -tvzf main.tgz
いかのようなcleanルールをつくってmake cleanとしたときに不必要なファイルを消せるようにしよう.
ここで,touch clean とすると,cleanというファイルが作られる. しかしmakefileのルールから clean というターゲットファイルが存在していると make clean としても以下のように clean というターゲットは最新であるエラーが出てします.
clean: @rm -rf *.exe *.o *~ *.bak *.deps *.tgz $(UNAME)
ここで,擬似ターゲットである .PHONY ルールを使う. この指定が行われたターゲットは,上記のルールが適用される必ず コマンドが実行される.
% touch clean % make clean make: `clean' is up to date.
.PHONY : clean
k-okada@jsk.t.u-tokyo.ac.jp $Id: index.html,v 1.1.1.1 2003/08/13 05:34:24 k-okada Exp $