これまで,みなさんが作ってきたソフトウェアは概ねひとつのソースプログラムを実行する場合が多かったのではないでしょうか.でも,世の中の多くのソフトウェアは簡単なものでも複数のファイルやデータの組み合わせから構成されます.数千〜数万のファイルやデータから構成される複雑なソフトウェアも珍しくはありません.

また,ひとつのソフトウェアが複数のモジュールやプログラムから構成される場合もあります.

このように,複数のファイルやデータを組合せて,さまざまなプログラムを構成するときに,その組合せの手順書を記述したものが Makefile です.

Makefile の書式は基本的には以下のような雛形に沿った記述を列挙したものです.

目的ファイル:   依存ファイル1   依存ファイル2 ...
        ビルドコマンド1
        ビルドコマンド2
        ...

この雛形は,

一旦,Makefile を書けば,その使い方は簡単です.単に make コマンドを実行するだけです.make コマンドは Makefile の記述を読み,そこに記述されたファイル群の更新日時を検査して,依存関係に沿って更新が必要なファイルを検出したら,該当するビルドコマンドを実行して,ファイルの更新を試みます.

サンプルプロジェクト lx01 に含まれる Makefile の内容について見てみましょう.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# lx01/src/Makefile

all: simple simple.s Simple.class

simple: simple.c
    clang -o simple simple.c

simple.s: simple.c
    clang -O0 -S simple.c

Simple.class: simple.scala
    scalac simple.scala

clean:
    rm -f simple simple.s *.class

run-c:
    ./simple

run-s:
    scala Simple

run-j:
    java -classpath `brew --prefix`/opt/scala/libexec/lib/scala-library.jar:. Simple

まず,わかりやすいところで5-6行目を見てみましょう.ここには,以下のことが記述されています.

この記述があるので make simple というように make コマンドに目的ファイルの simple を渡すと,makesimple を作成するために必要最小限の作業を実施します.現時点で simple が存在していなかったり,その最終更新日時が simple.c より古い場合には make simple によって,新しい simple がビルドされます.すでに simple が存在していて,その最終更新日時が simple.c の最終更新日時よりも新しい場合には,make simple は何もしません.

simple を作成するために simple.c をプログラムする作業は,(1) テキストエディタ等で simple.c を修正し,(2) make simple を実行してビルドし,(3) simple を実行してテストする作業の繰り返しが普通です.Makefile を利用することで,複雑なコンパイラのオプションを毎回,記述する手間から解放されます.

8-9 行目には,同じく simple.c という名前のファイルから simple.s という別のファイルを作成する方法が記述されています.simple.ssimple.c に相当するアセンブリコードのファイルの名前です.

11-12 行目にはscalac (Scala言語のコンパイラ)を利用して,Simple.class という scala の実行可能形式のファイルを simple.scala からコンパイルする方法が記述されている.

3 行目の記述 all: simple simple.s Simple.class は依存性だけが列挙されており,ビルドコマンドの記述はない.この記述は all をビルドするためには,まずは simplesimple.sSimple.class が必要だと指定している.すでに見たようにこれらのファイルにはビルドの記述があるので,makeall のビルドの手始めにこれら三つのファイルを作成しようとする.all にはビルドコマンドは指定されていないので,三つのファイルができたならば,その時点で all のビルドは成功したことになる.all のような記述は,複数の目的ファイルをまとめて作成する規則の記述に便利である.この例の場合 make all を実行すれば三つのファイルのビルドが実施される.

make が引数なしで呼ばれた場合は Makefile のなかの最初の規則を実行することになっている.このため,単に make を実行することは上の Makefile の場合は make all を実行することにあたる.ここまでの規則たちは simple.csimple.scala を元とするビルド規則である.すでに述べたように make は各ファイルの最終更新日時に応じて必要最小限の作業のみを実施する.このため simple.c の作業を実施しているときも simple.scala の作業を実施しているときでも,単に make コマンドを実行すれば,最後に更新したソースプログラムに依存するファイルのビルドのみが実施される.このことは大きなソフトウェア開発において重要である.なぜなら,数千にも及ぶファイルのなかでたったひとつのファイルが更新されたばかりに,すべてのプログラムの再コンパイルを実施するとしたら,コンパイル作業に膨大な時間を要し,ソフトウェア開発が滞るからだ.1

14-15 行目のclean: で始まる規則は依存性がない.しかし,clean という名前のファイルは存在しないので make clean を実行すれば,そのビルドコマンドは常に実行される.clean 規則のビルドコマンド rm -f simple simple.s *.class はファイルを削除するrmコマンドを利用して,5-12行目の規則が生成するファイル群を削除する.それらのファイルが存在しない場合,rm コマンドはエラーを出力するが-fオプションによって,エラーメッセージの出力を抑制している.

他人にソフトウェアを受け渡すときは,普通は必要最小限の構成としてプログラムのみを送り,コンパイル生成物を送るべきではない.このような場合,一旦 make clean するだけで,フォルダの内容をきれいに整えることができるので便利である.

Makefile はプログラムの実行のためのコマンドを記述する目的でもしばしば利用される.たとえば,23-25行目には,scalacの生成物である Simple.class を Java のインタプリタを用いて実行する方法を記述している.このような長いコマンドを正確に記憶することも,それを入力することも困難である.このような複雑な実行文もMakefileの規則として記述すれば単にmake run-jを実行するだけで実行することができる.

なお,本ウェブページの作成にあたっても,Markdown形式と呼ばれる簡素な記述から,煩瑣なHTML形式にpandocコマンドを利用して変換しているのだが,この思いの外に複雑な作業もMakefileを用いて管理している.本格的は Makefile 記述の実際を学ぶ一助になれば幸いである.


  1. かつて,ソフトウェアのビルドに長大な時間がかかったときには,たとえ make を利用してもビルド作業に何十分もかかった.ビルドの合間に剣玉をしていたために当時のIBM東京基礎研の研究員はん剣玉上手だった.