少数派の住処

Cygwin とMSYS2 の上で暮らすプログラマの雑日記

make のすすめ

こんばんは、皆さんいかがお過ごしでしょうか。今回が初めての投稿になりますがよろしくお願いします。

さて今回の記事では、makeを用いたビルドの自動化を扱います。

-目次-

  1. ビルドの自動化
  2. makeについて
  3. 自動化の手順
  4. makefile, makeの簡単な解説
  5. 最後に

1. ビルドの自動化

皆さんはプログラムのコンパイルをどうしてますか?Visual StudioXcodeEclipseに代表されるIDEのユーザであれば、ビルド環境を設定してGUIのビルドボタンを押すと思います。一方ターミナルや端末、コマンドラインなどのCLIのユーザであれば、オプションを付けてgcc, clung等のコマンドを実行していると思います。ここで双方のビルド作業を見比べると

  • IDE: 逐次ビルドの設定をすれば毎回のビルドはボタン一つで可能
  • CLI: 毎回のビルド時に開発環境に合わせたオプションを付けてコマンドを実行する

となります。以上のように、ビルドの度に環境を考慮して同じ作業をするため、IDEの方が楽ですよね。でもそれだけの理由でCLIでの開発をあきらめるべきでしょうか。コマンドの実行作業を自動化すればよさそうですね。そこで今回はmakeを使っていこうと思います。


2. Makeについて

Wikipediaによるとmakeとは、makefileというファイルに書かれたコンパイル、リンク、インストール等の手順に従ってプログラムのビルド作業を自動で実行するツールです。シェルスクリプトと同様にコマンドを実行する点は同じですがmakeは以下の点でシェルスクリプトと大きく異なります。

  1. ファイル(ex. 実行ファイル, オブジェクトファイル etc)の生成処理をファイルごとに設定できる。
  2. 生成元のファイルの変更のみを生成物に反映させる。
  3. 同じmakeを使えば環境が違ってもビルドができる。

またmakeは目的のファイルを生成するための処理を実行するだけなので、実行ファイルやオブジェクトファイの生成だけではなくtexファイルからpdfを生成することもできます。

Wikipediaのこの記事によるとmakeにはいくつかの種類がありますが、今回はGUN Makeを使うことにします。


3. 自動化の手順

ここからmakeによるビルドの自動化を行っていきます。

makeの実行環境の準備
  • Unixまたはそれに準ずる環境: おそらく入っていると思われます。なければ入れてください。
  • macOS: Apple StoreからXcodeをダウンロードしてこの記事を参考にXcode Command Line Toolsをインストールする。
  • Windows: 様々な方法がありますが、個人的なおすすめとしてmsys2をインストールするのがよいです。(具体的なやり方は後日別の記事で書きます。)
ディレクトリの用意

以下の様なintroディレクトリを用意します。

intro
├── main.c
└── makefile

main.cの中身を

#include<stdio.h>

int main() {

    printf("Hello, Make!\n");

    return 0;
}

とします。見ての通りmain.cはハローワールドを実行するコードです。

説明のために簡単なディレクトリ構造を示しましたが、makeは複雑なディレクトリ構造下でもビルドを行うことができる機能があります。

makefileの記入

それではmain.cから実行ファイルを生成する作業を自動化してみましょう。とりあえず以下の内容をmakefileに書き込みます。

out: main.c
        gcc main.c -o out

とします。

ここで1つ大事な点があります。それはコマンド行頭の空白部分は必ずタブ文字にして下さい。そうしないとmakeはコマンドが書いてある行をコマンド行として認識しません。

makefileの実行

書き込んだらintroディレクトリ内で'make'コマンドを実行します。実行すると

$ make
gcc main.c -o out

となるはずです。ここでディレクトリの中を見るとout~という実行ファイル(~は環境によって違う)が生成されているとおもいます。実行すると

$ ./out.exe
Hello, Make!

となると思われます。

makeの再実行

ここでもう一度makeを実行してみましょう。すると

$ make
make: 'out' は更新済みです.

の様になると思われます。これはout~の更新時刻がmain.cの更新時刻よりも後ろにあるためです。そのためmakeはmain.cに変更はないと認識して不要な生成処理を実行せずに制御を返しました。そのためout~の更新時刻がmain.cのよりも前にある、またはout~が存在しない場合は生成処理を実行します。

このようにmakeは生成処理の実行を、被生成ファイルとその生成元ファイルの関係から決定します。そのためmakeは不要な生成処理をなるべくしないようにします。

無論out~を消せば生成処理を実行させることができます。

$ rm out~

$ make
gcc main.c -o out

4. makefile, makeの簡単な解説

ここではmakefile, GUN Makeの仕組みについて簡単に解説したいと思います。

makefileの中身

ここで先ほどのmakefileの中身を見返すと

out: main.c
        gcc main.c -o out

のようになっています。

まず一行目では被生成ファイルとその生成元ファイルの名前が表記されています。表記の仕方は

[被生成ファイル名] : [生成元ファイル名]

となっています。この行以降にout~の生成処理が複数行にわたって記述されます。
おさらいですが各行の記述はタブ文字1字から始まります。 ここで見ているmakefileには、out~を生成するオプション付きgccコマンドの一行のみが記述されています。

以上の内容をまとめると

[被生成ファイル名A] : [生成元ファイル名B]
<TAB>[Aの生成処理コマンド1]
<TAB>[Aの生成処理コマンド2]
      ︙
<TAB>[Aの生成処理コマンドn]

となります。一般的に[被生成ファイル名A]をターゲット、[生成元ファイル名B]を依存関係、コマンド行群をコマンドと呼ぶことが多いです。また依存関係には半角空白で区切って複数のファイル名を指定することもできます。

またターゲット, 依存関係には何でも入れて良いので、texファイルからdviファイルを生成するように記述することも可能です。

makeの動作

makeコマンドは実行されると指定されたmakefileを読み込んで実行します。makeはデフォルトで、カレントディレクトリ内からmakefile, Makefile等の名前が付いたファイルを探します。また以下のようにオプションを用いて指定することもできます。

make -f [ファイル名]

ファイルを読み込んだmakeはまず先頭のターゲットの生成を行おうとします。このときターゲットの更新時刻が依存関係のよりも古ければコマンドを実行し、新しければ実行せずに終了します。

今回の場合ではintroディレクトリ内で実行したためmakefile内の最初のout~の生成が最初に実行され、その際にmain.cとout~の更新時刻を比較してコマンドを実行するかを決めます。実行する場合は次の行のコマンドを実行し、実行しない場合は変更が不要であることを画面に出力して終了します。

5. 最後に

以上でGUN Makeによるビルドの自動化の説明が終わります。今回の内容はmakeの入り口に過ぎません。さらに知りたい方がいれば以下のページを見るとよいでしょう。

ここでまお付き合いいただきありがとうございました。