第46回
makeによるマルチモジュール開発の合理化

makeとmakefile

makeは上述したソースファイルの依存関係にもとづいて、コンパイルとリンクの処理を合理的に実行します。

makeの基本動作

作成するプログラムによってソースファイルが異なるため、当然のことながら作成するプログラムごとに依存関係は異なります。そのため、makeは依存関係を記述した"makefile"を読み込み、それにもとづいてコンパイラやリンカを起動します。

開発者は、ファイルの依存関係を記述したmakefileを作成しなければなりません。makefileの基本的な記述方法は、以下のとおりです。
生成されるファイル : 元になるファイルのリスト
  ファイルを生成するためのコマンドライン

「生成されるファイル」を「ターゲット」、「元になるファイル」を「ソース」と呼びます ※2

makeは上のような記述にもとづいて、「元になるファイルのリスト」が変更されていれば「ファイルを生成するためのコマンドライン」を実行してターゲットを生成し、変更されていなければ何も行いません。

「ターゲット(target)」は「標的・目標」という意味です。また、ここで言う「ソース」は .cソースファイルだけに限らず、オブジェクトファイルを含めた生成の元になるファイル全般を指します

更新日付で変更を判断する

たとえば「stconv.h(b)とstconv.c(c)からstconv.obj(a)を生成する」というルールは、makefileに以下のように記述します。
stconv.obj : tconv.h stconv.c
    (a)        (b)      (c)
  LCC -c -j -ostconv.obj stconv.c
                (d)

(a)がターゲット、(b)と(c)がソース、そして(d)の箇所が、上の行で示した依存関係にもとづいて実行されるべきコマンドラインです。このような記述によって、stconv.hまたはstconv.cのどちらかが書き換えられた場合に、stconv.cをコンパイルしてstconv.objを生成する――という処理が実行されます。

あるファイルが書き換えられたかどうかは、:の左側に書かれたターゲットのタイムスタンプ(更新日時)と:の右側に書かれたソースのタイムスタンプとを比較して判断されます。

つまりmakeは、stconv.objがstconv.hまたはstconv.cより古い(先に変更された)場合は続くコマンドラインが実行され、そうでない場合には何も実行されない――という動作を行う訳です。


すべての依存関係を記述する

もちろん、上のような1行だけでは1つのオブジェクトファイルしか生成されません。このような記述を、ソースファイルの依存関係にもとづき、すべてのソースファイルに対して行う必要があります。

stconvの場合なら、リスト1のようなルールを記述することになります。行頭の#記号はコメントを表し、それ以降改行まではmakeに無視されます。

各行の意味は以下のとおりです。

(1a) 最終的に生成されるstconv.exeが3つのオブジェクトファイルから成っていることを記述しています。
(1b) LCCで3つのオブジェクトファイルをリンクしてstconv.exeを生成するためのコマンドラインです。
(2a) stconv.objがstconv.hとstconv.cから生成されることを記述しています。
(2b) LCCでstconv.cをコンパイルしてstconv.objを生成するためのコマンドラインです。
(3a) datinit.objがstconv.hとdatinit.cから生成されることを記述しています。
(3b) LCCでdatinit.cをコンパイルしてdatinit.objを生成するためのコマンドラインです。
(4a) convert.objがstconv.hとconvert.cから生成されることを記述しています。
(4b) LCCでconvert.cをコンパイルしてconvert.objを生成するためのコマンドラインです。

リスト1:stconv.exe を生成するための依存関係を記述したmakefile
    # stconv.exe を生成するためのルール
    stconv.exe : stconv.obj datinit.obj convert.obj  -------- (1a)
      LCC -ostconv.exe stconv.obj datinit.obj convert.obj --- (1b)
    stconv.obj : stconv.h stconv.c  ------------------------- (2a)
        LCC -c -j -ostconv.obj stconv.c --------------------- (2b)
    datinit.obj : stconv.h datinit.c  ----------------------- (3a)
        LCC -c -j -odatinit.obj datinit.c ------------------- (3b)
    convert.obj : stconv.h convert.c  ----------------------- (4a)
        LCC -c -j -oconvert.obj convert.c ------------------- (4b)

★注意

LSI-Cに付属しているmakeでは、makefileの記述に以下のような決まりがあります。

1. #で始まるコメント行と:記号を用いるルール記述行は、行頭または複数個の半角スペースで始める。
2. コマンドラインはタブで始める(タブの後ろに複数個のスペースを記述してもよい)。

コメントやルール記述行の先頭にタブコードがあると、それをコマンドラインと見なして「コマンドまたはファイル名が違います」というエラーメッセージが表示されます。また、コマンドラインの先頭がタブコードで始まっていない場合は、それをルール記述と見なすようでコマンドが正常に実行されません。

具体的なmakefileの書き方は、サンプルに含まれているmakefileを参照してください。

ヘッダファイルへの依存を忘れない

ルールの記述では、各オブジェクトファイルの生成にヘッダファイルstconv.hが必要なことが明記されていますが、それに続くコマンドラインでは .cソースファイルをコンパイルする指示しか記述されていません。

これは、それぞれの .cソースファイルの冒頭で#includeプリプロセッサ指令によってstconv.hが取り込まれているためです。このことを忘れて
datinit.obj : datinit.c
といったように、ルールでstconv.hの記述を抜いてしまうと、stconv.hが書き換えられてもdatinit.cは再コンパイルされなくなります。これによってコンパイルエラーや実行時エラーが発生した場合、どんなに一生懸命datinit.c内を探しても問題となる箇所は見つかりません。

依存関係をしっかり把握して、正しいルールを書くことが大切です。

makefileはソースと同じフォルダに

上記のような生成のルールを記述したファイルを"makefile"という名前で保存してmakeを実行すれば、makeはmakefileを読み込み、そこに記述されたルールにもとづいて合理的なプログラムの生成を行います。

makeは、まずカレントディレクトリからmakefileを探します。したがって、makefileを含むすべてのソースファイルを同じフォルダ(ディレクトリ)に保存し、そこをカレントディレクトリとしてmakeを実行しなければなりません。