PC入門/make-Makefile

make (Makefile) について解説する.基本,これはc言語のファイルをコンパイルするために使われる場面が多いから,コンパイルのためのコマンドだと思うかもしれないが,そうではなくてもっと素晴らしいものだ.これは,Makefile という専用のファイルに定義されたファイルの依存関係とその依存関係に関係するファイルの更新時刻を調べ,更新するべきファイルを判定し,更新のためにユーザが記述したコマンドを順序よく実行してくれるという優れたコマンドなのである.Linux には,開発環境ツールを入れると入ってくる.たぶん,sudo apt install makeとか,sudo pacman -S makeとか打てばたやすく入るだろう.詳しくは,make install とでも調べてくれ.

make は実行された階層にあるMakefile を読み込む.Makefile に書かれた依存関係を元に,コマンドを実行する.例えば,a.txt を作るのに b.txtとa1.txt が必要で,b.txt を作るのに c.txtとb1.txt, c.txt のためにd.txt が必要な場合,次の通りMakefileでこの依存関係をかける.普通冒頭で述べたとおり C/C++言語のコンパイルよく使われるmakeであるが,C言語に習熟していない人を想定し,かんたんのためにテキストファイルの生成を題材にしている.cat はファイルを表示するプログラムで,A > x はAの表示内容を x に書き込み(ない場合は新規作成), >> x はAの表示内容を xへ追記(ない場合は新規作成)するコマンドである.便利だし良く使われるので覚えよう.

a.txt: b.txt a1.txt
    cat b.txt >> a.txt
    cat a1.txt >> a.txt
b.txt: c.txt  b1.txt
    cat c.txt >> b.txt
    cat b1.txt >> b.txt
c.txt: d.txt
    cat d.txt > c.txt # 最初は上書き.

x: y と書かれている場所は,xが生成したいファイル,y が xに必要なファイルを意味する.ファイルを生成するための実際のコマンドは,pythonのようにTab 文字で字下げをして下に書く.上記の例を見たほうが早いだろう.上記テキストをMakefileというファイル名で保存し,同じ階層でmakeと実行した結果は次の通りになるはずである.

[shibata@127 maket]$ make
make: *** No rule to make target 'd.txt', needed by 'c.txt'.  Stop.

コマンドは失敗する.上記依存関係のうち,もっとも根本的に必要な d.txtが無いからだ.d.txt に「ああ」とでも書いて保存し,make, ls と順に実行すると次のとおりになる.ls はファイルが生成されたことを確認するために実行している.

[shibata@127 maket]$ make
cat d.txt > c.txt # 最初は上書き.
make: *** No rule to make target 'b1.txt', needed by 'b.txt'.  Stop.
[shibata@127 maket]$ ls
c.txt  d.txt  Makefile

ls の結果から,c.txt は無事に生成されていることがわかる.Makefile の最後のルールが実行されたからだ.しかし,b.txtを生成するコマンドは,b1.txtが存在しないために実行できない,とエラーが出ている.b1.txt が必要なのだ.b1.txtに「いい」とでも書いて保存しついでにa.txtの生成にもひつようだろうa1.txtに「やぁ」とでも書いて保存してみよう.これでmake, ls, cat a.txtの順に実行した結果は次のとおりである.

[shibata@127 maket]$ make
cat b.txt >> a.txt
cat a1.txt >> a.txt
[shibata@127 maket]$ ls
a1.txt  a.txt  b1.txt  b.txt  c.txt  d.txt  Makefile
[shibata@127 maket]$ cat a.txt
ええいいやあ

make は実行したコマンドを表示してくれるのだが,上記をみてのとおり,依存関係を解決するように,順にコマンドが実行されていることがわかる.また,a.txtが生成され,中身が,期待される「ええいいやあ」になっている気がする.

依存関係の本領を見せよう.この中で,b1.txt だけを「いいい」に変えてみよう.このように編集して,make, cat a.txt の順に実行すると次のとおりになる.

[shibata@127 maket]$ make
cat c.txt > b.txt
cat b1.txt >> b.txt
cat b.txt > a.txt
cat a1.txt >> a.txt
[shibata@127 maket]$ cat a.txt
ええいいいやあ

わかるだろうか.「い」が一つ増えているのはもちろんだが,実行されているコマンドは,b1.txtの内容を反映するに必要で十分なものに収まっている.c.txt が d.txtから再生成されてはいない.c.txtを再生成する必要が無いことは,定義した依存関係からわかるが,このようにmakeは不要な再生性を抑制してくる.今ここで示した例ではtxtファイルの結合のみを扱っているがこれが実応用上で行われるプログラムのコンパイルでは不要なファイルの再生成を行えば実行時間上大きな問題となる.この問題を解決してくれるのが,make および Makefileなのだ.

C++での応用を一つ示そう.以下のファイルを用意してもらいたい.

上ファイル群は,説明にある通り,main.cpp を構成するC++言語のファイルと,これらの依存関係を make へ伝えるための Makefileである.main.cppは以下のようになっていて,a.hpp, b.hppに宣言された関数と対応する a.cpp, b.cpp に書かれた関数の定義が必要なプログラムである.

#include "a.hpp"
    #include "b.hpp"
    #include 
    
    using namespace std;
    int main(){
        cout << "A: " << testA() << " B: " << testB() << "\n";
        return 0;
}

main.cpp をコンパイルするためには,先にa.cpp, b.cppをコンパイルして機械語のファイル(a.o, b.o)を生成しなくてはならない.実行コマンドは次のとおりになる.プログラムの実行まで行っている.

[shibata@127 maket]$ g++ -c a.cpp -o a.o
[shibata@127 maket]$ g++ -c b.cpp -o b.o
[shibata@127 maket]$ g++ main.cpp a.o b.o -o main 
[shibata@127 maket]$ ./main
A: 777 B: 888

a.cpp と b.cpp を先にコンパイルしなければなず,この例だとそれほどではないが普通プロジェクトではこれらのようなサブファイルは数百に上るため,手作業で実行していては切がない.また,すべて実行するのは何度も述べている通り計算資源の無駄である.ここで,Makefileのように Makefileを同じ階層に用意して,make を実行すると,以下のように,自動で依存関係を解決したうえでコマンドを実行してくれる.以下の出力例では最終生成物の main の実行結果も示してある.

[shibata@127 maket]$ make
g++ -c a.cpp -o a.o # -c は main 関数が無い cpp をコンパイルするのに必要
g++ -c b.cpp -o b.o # -c は main 関数が無い cpp をコンパイルするのに必要
g++ main.cpp a.o b.o -o main 
[shibata@127 maket]$ ./main
A: 777 B: 888

ここで,いくらか各ファイルへ編集を試してみると良い.たとえば,main.cpp を編集したとき,b.cpp, a.cppは再度コンパイルされることは無くmain.cppのみがコンパイルされるはずだ.ちなみに,C言語のこういったプロジェクトでは,私もそんなに詳しくないのだが,a.hppなどのヘッダは main.cppの依存関係に記載しておいたほうが良い.a.cpp をコンパイルするためだけにa.hppが使われる,とは限らず,a.hppの内容を直接main.cppで使うことがあるからだ.留意しておくと良い.make は,定義されていない依存関係まで解決してくれるほど面倒見は良くないので,しっかり自分で定義しよう.

私は,make をC++言語の他には,latexのコンパイルに使っている.

他,慣れてきたら,Wildcardとか覚えて,同じ拡張子のファイルへ一括で同じコマンドを適用するとか,調べてみると良い.これができるようになると多少便利である.