GNU make について

Lab の課題で Unix の Makefile について学習するという課題が出たので GNU make についてまとめていこうと思います。    
## make とは
>プログラムのビルド作業を自動化するツール。コンパイル、リンク、インストール等のルールを記述したテキストファイル(makefile)に従って、これらの作業を自動的に行う。

[Wikipedia](https://ja.wikipedia.org/wiki/Make)より
自分のイメージとしてはコンパイルやクリーンなどを自動化できるツールというようなイメージです。  

## make の仕方
Makefile というテキストファイルに作業内容を記述します。
具体例と共に触れて行きましょう。  
以下のような構成のディレクトリ foo があったとします。 
 
```shell
foo$ ls
main.c
bar.c bar.h
foobar.c foobar.h
```

main.c は bar.c と foobar.c から構成されていて、
bar.c は bar.h に、foobar.c は foobar.h に依存しています。

はじめに Makefile を作成します。

```shell
foo$ touch Makefile
```
作成した Makefile を適当なエディタで開きます。

とりあえず以下の内容を書き込みます。
この時、インデントは tab でおこなってください。

```shell:Makefile
1  CC = gcc
2  TARGET = main
3  OBJS = bar.o foobar.o
4 
5  all: $(OBJS)
6  	   $(CC) main.c -o $(TARGET) $(OBJS)
7 
8  .c.o:
9  	   $(CC) -c $<
10 
11 bar.o: bar.h
12 foobar.o: foobar.h
```

### Makefile の解説
1 ~ 3 行目では変数宣言を行っています。
1 行目では CC という変数に gcc を代入しています。
2 行目で 変数 TARGET に生成される実行ファイルの名前を代入しています。
3 行目では 変数 OBJS に実行ファイル作成時に使用するオブジェクトファイルの名前を代入しています。
5 行目では all は慣用名で、 $(OBJS) に含まれる全てのターゲットを構築します。 
実際に make と打った際に実行されるのがこのブロックです。 
6 行目には具体的な処理を書いており、変数展開をすると以下のプログラムが実行されます。

```shell
gcc main.c -o main bar.o foobar.o
```

8, 9 行目では オブジェクトファイル ( .o ファイル) は必ず .c ファイルから生成されるというルールがあることを利用して、.o ファイルが必要になった際に .c ファイルから .o ファイルを自動で生成しています。
11, 12 行目では .o ファイルが依存している .h ファイルを宣言しています。

### 実行結果　
```shell

foo$ make
gcc -c bar.c
gcc -c foobar.c
gcc main.c -o main bar.o foobar.o
 
foo$ ls
Makefile
main.c main
bar.c foobar.c
bar.h foobar.h
bar.o foobar.o
 
foo$ ./main
・・・
・・・
```

実行ファイルや .o ファイルが生成されていることが確認できると思います。  

## make clean をしよう
make には ターゲットと呼ばれるものを追加することで様々なプログラムを以下のように実行することができます。 
 
```shell
make $(target)
```

ターゲットには前述の all の他に clean や install などがあります。  
all は特別で、下のどちらで打っても同じ結果になります。

```shell
make
make all
```

clean について少し触れていきましょう  

### make clean ですること
clean は基本的に make 時に作った不要なファイルなど (例えば .o ファイルなど) を削除するためのものです。
以下のように新しくターゲットを追加することができます。
ここで TARGET や OBJS は前述の Makefile で定義したものと同じです。

```shell:Makefile
1 clean:
2	  rm -rf $(TARGET)
3	  rm -rf $(OBJS)
```

これは以下のように実行できます。

```shell
foo$ make clean
rm -rf main
rm -rf bar.o foobar.o
```

## make pkg をしよう
今度は ディレクトリをパッケージ化してみましょう。
ターゲット名は pkg とし、ファイルを tar.gz 形式で圧縮します。

### 方針
はじめにディレクトリを作ります。  (ディレクトリ名はなんでも良いです。)
そのディレクトリにソースファイルを全てコピーします。
そのディレクトリを tar.gz 形式で圧縮します。
その後作成したディレクトリを削除します。
これを Makefile に書くと以下のようになります。

```shell:Makefile
1 FILE = Makefile main.c bar.c bar.h foobar.c foobar.h
2 DIR = test
3 
4 pkg:
5 	  if [ -d $(DIR) ]; then rm -rf $(DIR); fi
6 	  mkdir $(DIR)
7 	  cp $(FILE) ./$(DIR)
8	  tar zcvf $(DIR).tar.gz $(DIR)
9	  rm -rf $(DIR)
```

1, 2 行目は変数宣言を行なっています。
5 行目では test という名前のディレクトリが存在していた場合にそのディレクトリを削除しています。
6 行目で test ディレクトリを作成し、
7 行目で FILE に宣言されたファイルを作成した test ディレクトリにコピーしています。
8 行目で test ディレクトリを圧縮し、tar ファイルを生成します。
9 行目では不要になった test ディレクトリを削除しています。

### 実行結果
```shell
foo$ make pkg
if [ -d test ]; then rm -rf test; fi
mkdir test
cp main.c Makefile bar.c bar.h foobar.c foobar.h ./makelab1
tar zcvf test.tar.gz test
a test
a test/bar.h
a test/foobar.h
a test/main.c
a test/Makefile
a test/bar.c
a test/foobar.c
rm -rf test
 
foo$ ls
Makefile
main.c
bar.c bar.h
foobar.c foobar.h
test.tar.gz
 
foo$ tar -zxvf test.tar.gz
x test/
x test/bar.h
x test/foobar.h
x test/main.c
x test/Makefile
x test/bar.c
x test/foobar.c
 
foo$ ls
Makefile
main.c
bar.c bar.h
foobar.c foobar.h
test
```

tar ファイルが生成されていること、解凍すると test ディレクトリが生成されていることが確認できると思います。

## 終わり
gnu make について解説をしてきました。
gnu make は使いこなせるようになると非常に便利です。
ぜひ使ってみてください。

一応例で使った Makefile を載せておきます。
  
```shell:Makefile
CC = gcc
TARGET = main
OBJS = bar.o foobar.o
FILE = Makefile main.c bar.c bar.h foobar.c foobar.h
DIR = test
 
all: $(OBJS)
    $(CC) main.c -o $(TARGET) $(OBJS)

.c.o:
    $(CC) -c $<

bar.o: bar.h
foobar.o: foobar.h

clean:
    rm -rf $(TARGET)
    rm -rf $(OBJS)

pkg:
    if [ -d $(DIR) ]; then rm -rf $(DIR); fi
    mkdir $(DIR)
    cp $(FILE) ./$(DIR)
    tar zcvf $(DIR).tar.gz $(DIR)
    rm -rf $(DIR)
```