プログラミング/make

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動
Wikipedia
Wikipedia
ウィキペディアmake (UNIX)の記事があります。

makeはソースコードから実行形式のプログラムやライブラリを自動的にビルドするビルド自動化ツールです。 統合開発環境や言語固有のコンパイラー機能でもビルドプロセスを管理できますが、make はプログラムのビルド以外にも、あるファイルが変更されると自動的に他のファイルから更新されなければならないようなプロジェクトの管理にも使用することができます。 特にUnixやUnix系OSではmakeが広く使われています。

はじめに[編集]

makeは何度も書き直され、同じファイルフォーマットと基本的なアルゴリズム原理を使用し、独自の非標準的な拡張を行ったスクラッチから書き起こされた亜種も多数存在します。

  • BSD-make
  • GNU-make

の2つが多く使われているので、本書では BSD-make(しばしば bmake)とGNU-make(しばしば gmake)に共通する構文・機能ついて説明します(余裕があれば違いについても説明します)。

分割コンパイル[編集]

make の説明に入る前に、分割コンパイルについてザックリ説明します。

プログラミング言語には、コンパイラーによってソースコードから実行形式のプログラムをコンパイルするコンパイル型言語と、ソースコードをインタープリターが逐次実行するインタープリタ-型の2つに分類されます。

分割コンパイルは、コンパイラー型のプログラミング言語で複数のソースコードから1つの実行形式のプログラムを作る方式のことで、1つ1つのソースコードを小さくしたり関連した機能だけをまとめる目的で行なわれます(分割統治法の1つだと考えられます)。

C言語の分割コンパイルの例[編集]

実際に3つのファイルに分割したC言語で書かれたプログラムの例を見てみましょう。

Main.c
// Main.c:
#include </stdio.h>
#include "imath.h"

int main(void) {
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            print("ipow(%d, %d) = %d\n", i, j, ipow(i, j));
}
imath.h
// imath.h:
extern int ipow(int x, unsigned y);
imath.c
// imath.c:
#include "imath.h"

int ipow(int x, unsigned y) {
    if (y == 0)
        return 1;
    if (y == 1)
        return x;
    return x * ipow(x, y - 1);
}
コマンドラインでのビルド作業の様子
% mkdir imath
% cd imath/
% cat > Main.c
// Main.c:
#include <stdio.h>
#include "imath.h"

int main(void) {
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            print("ipow(%d, %d) = %d\n", i, j, ipow(i, j));
}
% cat > imath.h
// imath.h:
extern int ipow(int x, unsigned y);
% cat > imath.c
// imath.c:
#include "imath.h"

int ipow(int x, unsigned y) {
    if (y == 0)
        return 1;
    if (y == 1)
        return x;
    return x * ipow(x, y - 1);
}
% cc -c imath.c
% cc -c Main.c
% cc -o imath Main.o imath.o
% ./imath 
ipow(0, 0) = 1
ipow(0, 1) = 0
ipow(0, 2) = 0
ipow(1, 0) = 1
ipow(1, 1) = 1
ipow(1, 2) = 1
ipow(2, 0) = 1
ipow(2, 1) = 2
ipow(2, 2) = 4
  1. imath.c をコンパイルし imath.o を得ています。
  2. Main.c をコンパイルし Main.o を得ています。
  3. Main.o と imath.o (とランタイムライブラリー)をリンクして実行形式 imath を得ています。

このプログラムは、0の0乗を扱っています。 0**0を1としていますが宗派によっては 0**0を0とする場合もあります。

imath.c をその仕様に変更してみましょう。

imath.c
// imath.c:
#include "imath.h"

int ipow(int x, unsigned y) {
    if (x == 0)
        return x;
    if (y == 0)
        return 1;
    if (y == 1)
        return x;
    return x * ipow(x, y - 1);
}

実行してみましょう。

% ./imath 
ipow(0, 0) = 1
ipow(0, 1) = 0
ipow(0, 2) = 0
ipow(1, 0) = 1
ipow(1, 1) = 1
ipow(1, 2) = 1
ipow(2, 0) = 1
ipow(2, 1) = 2
ipow(2, 2) = 4

あれ、変わりません。ああ、書換えてからコンパイルをしてませんね。

% cc -c imath.c
% ./imath 
ipow(0, 0) = 1
ipow(0, 1) = 0
ipow(0, 2) = 0
ipow(1, 0) = 1
ipow(1, 1) = 1
ipow(1, 2) = 1
ipow(2, 0) = 1
ipow(2, 1) = 2
ipow(2, 2) = 4

?、まだ変わりません。リンクも必要ですね。

% cc -c imath.c
% cc -o imath Main.o imath.o
% ./imath 
ipow(0, 0) = 0
ipow(0, 1) = 0
ipow(0, 2) = 0
ipow(1, 0) = 1
ipow(1, 1) = 1
ipow(1, 2) = 1
ipow(2, 0) = 1
ipow(2, 1) = 2
ipow(2, 2) = 4
やっと変更が反映されました。
このように、ソースコードの変更によって部分的なビルドが必要になります。
Main.oは変更の影響を受けないので再コンパイルしていませんね。

この例は単純なので、全てのソースコードの再コンパイルの要否を容易に把握できますが、小さなツールのソースコードでもファイルは十数から数十に及ぶので把握が困難になりがちです。 このような時のために Makefile を用意します。

make を使ったビルド[編集]

Makefile[編集]

imath をビルドするための素朴な Makefile を書いてみます。

Makefile
# Makefile:

imath: Main.o imath.o
        cc -o imath  Main.o imath.o

Main.o: Main.c imath.h
        cc -c Main.c

imath.o: imath.c imath.h
        cc -c imath.c

使ってみます。

% make imath
`imath' is up to date.
% rm *.o
% make imake
make: don't know how to make imake. Stop

make: stopped in /usr/home/user1/imath
% make imath
cc -c Main.c
cc -c imath.c
cc -o imath  Main.o imath.o
% ./imath 
ipow(0, 0) = 0
ipow(0, 1) = 0
ipow(0, 2) = 0
ipow(1, 0) = 1
ipow(1, 1) = 1
ipow(1, 2) = 1
ipow(2, 0) = 1
ipow(2, 1) = 2
ipow(2, 2) = 4
% make
`imath' is up to date.
% _
  1. make は make ターゲット名 の形式で呼出します
  2. 依存関係のある Main.o imath.o のタイムスタンプがターゲット imath のタイムスタンプより古いので何もしません
  3. Main.o と imath.o を消してみます。
  4. 再び、make
  5. 綴を imake と間違えました
  6.  
  7.  make は停止しました
  8. 正しい名前で make を起動
    依存関係imath: Main.o imath.oから
    依存関係Main.o: Main.c imath.hが適応されアクションcc -c Main.cが実行されます
    依存関係imath.o: imath.c imath.hが適応されアクションcc -c imath.cが実行されます
    Main.o imath.oの用意ができたのでcc -o imath Main.o imath.oが実行されます

Makefileの基本構文[編集]

ターゲット
依存関係の : の左側、ファイルを書くとソースのタイムスタンプをもとにアクションに要否を make が判断します
ソース
依存関係の : の右側、複数可(空白区切り)
アクション
依存関係の次からの行、行頭に水平タブが1つ以上必要です。
imath: Main.o imath.o
        cc -o imath  Main.o imath.o
ターゲット
imath
ソース
Main.o imath.o
アクション
cc -o imath Main.o imath.o
となります。

コメント[編集]

Makefile にはコメントを書くことができます。

行の中に # が出てきたら、そこから行末までがコメントです。

組込みルール[編集]

make には既にルールが組込まれていて、例えば、.c から .o を得るルールが組込まれているので

# Makefile:

imath: Main.o imath.o
        cc -o imath  Main.o imath.o

Main.o: Main.c imath.h
#       cc -c Main.c

imath.o: imath.c imath.h
#       cc -c imath.c

としてもアクションをコメントにしても

% rm *.o
% make
cc    -c -o Main.o Main.c
cc    -c -o imath.o imath.c
cc -o imath  Main.o imath.o
と組込みルールが適用されます。
cc -c -o Main.o Main.c
と組込みルールでは生成されるファイルを明示しているところが違います。

ディフォルトターゲット[編集]

Makefileで最初に出てくるターゲットがmakeを引数を渡されなかったときにビルドされるターゲットで、allを使うのが一般的です

# Makefile:

all: imath

imath: Main.o imath.o
        cc -o imath  Main.o imath.o

Main.o: Main.c imath.h
imath.o: imath.c imath.h

ファイル以外のターゲット[編集]

ターゲットは、all の様にファイルでないものでも構いません。その場合はタイムスタンプの比較はできないので必ず実行されます。 clean がよく定義されます。

# Makefile:

all: imath

clean:
        rm imath Main.o imath.o

imath: Main.o imath.o
        cc -o imath Main.o imath.o

Main.o: Main.c imath.h
imath.o: imath.c imath.h

.PHONYを使ったダミーターゲットの記述[編集]

ファイル以外をターゲットにするのは便利ですが、一つ問題があります

$ touch clean
$ make clean
make: Nothing to be done for 'clean'.
clean と言う名前のファイルが有ると、make は「既に clean が存在し、依存関係もないので特にすることはない」と判断してしまいます。
# Makefile:
.PHONY: all clean

all: imath

clean:
        rm imath Main.o imath.o

imath: Main.o imath.o
        cc -o imath Main.o imath.o

Main.o: Main.c imath.h
imath.o: imath.c imath.h
ファイルでないターゲットを .PHONY: の右に列挙します。
$ make clean
rm imath Main.o imath.o

変数[編集]

Makefile で何度も参照される事柄は、変数に定義して複数箇所で参照できます。

# Makefile:
.PHONY: all clean

EXEC=imath
SRCS=Main.c imath.c
OBJS=Main.o imath.o

all: $(EXEC)

clean:
        rm $(EXEC) $(OBJS)

$(EXEC): $(OBJS)
        cc -o $(EXEC) $(OBJS)

Main.o: Main.c imath.h
imath.o: imath.c imath.h

[TODO:ディフォルトターゲット・暗黙のルール・組込みルール・変数・マクロ・条件実行…bmakeとgmakeの双方で動くMakefileを書くコツ]

脚註[編集]