前回までの説明で、makeとMakefileの使い方を紹介した。ファイルの依存関係を処理してコマンドを実行するユーティリティであるmakeと、その依存関係を記述するファイルであるMakefileだ。これらはC言語の開発に適したツールであり、必要最低限の使い方は理解いただいたと思う。今回は、Visual Studio Codeからmakeを使うように設定を変更する方法を説明する。

Visual Studio Code、tasks.jsonを書き換える

Visual Studio CodeでCのソースコードをコンパイルするために、 次のようなtasks.jsonファイルを作成した。このファイルでは直接clangコマンドを実行し、C言語のソースコードをコンパイルするように設定を行っている。

Cコンパイラclangを直接実行するtasks.jsonファイル

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Clang",
            "type": "process",
            "command": "clang.exe",
            "args": [
                "-g",
                "${file}",
                "-o",
                "${fileDirname}/csv2tsv.exe"
            ],
            "problemMatcher": [],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

しかし、C言語を使った実際の開発では、多くの.cファイルや.hファイルを組み合わせてソフトウェアを構築していく。上記のtasks.jsonファイルでは役不足なのだ。

そこで、本稿では複数のファイルを依存関係を加味しつつもビルドする方法として、makeを使う方法を学んできた。Visual Studio Codeからもこのmakeを使うように変更することで、依存関係を加味したビルドを実行できるようになる。具体的には、tasks.jsonファイルを次のように書き換える。

makeを実行するtasks.jsonファイル

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Clang",
            "type": "process",
            "command": "make",
            "args": [
                "build"
            ],
            "problemMatcher": [],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

変更点は、commandとしてmakeを指定するようになったこと、commandの引数としてbuildを指定するように書き換えたことだ。要するに、Visual Studio Codeで「Terminal」→「Run Build Task...」を選択、または、「Ctrl」+「Shift」+「B」を押したときに、「make build」というコマンドが実行されるようにした、ということである。

実際にtasks.jsonを書き換えてVisual Studio Codeでビルドを実行すると、次のようになる。

  • Visual Studio Code:「Terminal」→「Run Build Task...」または、「Ctrl」+「Shift」+「B」

    Visual Studio Code:「Terminal」→「Run Build Task...」または、「Ctrl」+「Shift」+「B」

Visual Studio Codeに統合されているターミナルで「make build」が実行されていることがわかる。

ここで何も変更しないまま、もう一度「Terminal」→「Run Build Task...」または、「Ctrl」+「Shift」+「V」を押してビルドを実行してみよう。次のような結果が得られると思う。

  • Visual Studio Code:「Terminal」→「Run Build Task...」または、「Ctrl」+「Shift」+「B」

    Visual Studio Code:「Terminal」→「Run Build Task...」または、「Ctrl」+「Shift」+「B」

何も変更していないので、ビルドする必要がない。このため、makeコマンドは「buildに対して行うべき事はありません」といったメッセージを出力してコンパイル処理は行っていない。makeコマンドがちゃんと判定して処理が不要だと判断したためだ。makeのおいしい機能がちゃんと働いていることがわかる。

Makefileをもうちょっと整える

前回使ったMakefileは次のようなものだった。

前回使ったMakefile

OBJS=   csv2tsv.o util.o
CMD=    csv2tsv.exe

CFLAGS+=-g

CC= clang

build: $(CMD)

$(CMD): $(OBJS)
    $(CC) $(CFLAGS) -o $(CMD) $(OBJS)

csv2tsv.o: csv2tsv.c
    $(CC) $(CFLAGS) -c csv2tsv.c -o csv2tsv.o

util.o: util.c
    $(CC) $(CFLAGS) -c util.c -o util.o

clean:
    rm -f "$(CMD)"
    rm -f *.o
    rm -f *.ilk
    rm -f *.pdb

なお、このMakefileではmakeもmake buildも同じ処理になる。makeコマンドはターゲットが指定されなかった場合、Makefileで一番最初に記述されているターゲットを処理しようとする。つまり、上記Makefileの場合はbuildが最初のターゲットだから、makeもmake buildも同じだ。あいまいさを回避するために、ターゲットは明示的に書いて使ったほうがよいだろう。

このMakefileは、Makefileの依存関係ルールなどを説明するためにかなりシンプルにまとめてある。実際には、makeの持っている機能を使ってもう少しコンパクトな書き方をすることが多い。例えば、上記のMakefileは次のように書き換えることができる。実際には、こんな感じの書き方のMakefileを使うことが多いと思う。

書き換えたMakefile

CMD=    csv2tsv.exe
SRCS=   csv2tsv.c util.c
OBJS=   $(SRCS:.c=.o)

CFLAGS+=-g

CC= clang

build: $(CMD)

$(CMD): $(OBJS)
    $(CC) $(CFLAGS) -o $(CMD) $(OBJS)

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

clean:
    rm -f "$(CMD)"
    rm -f *.o
    rm -f *.ilk
    rm -f *.pdb

何が違うの、と思うかもしれない。下のMakefileのほうが、今後の開発作業で発生する変更に対して書き換えが少なくて済む。Makefileは最終的に複雑になることもあり、できるだけ見通しのよい状態にしておきたい。必要上、ある程度複雑になってしまうのは仕方がないが、これくらいの書き換えとその解釈はできるようになっておいたほうがよい。書き換えの内容は次回に説明する。

なぜmakeとMakefileか

開発プラットフォームと実行プラットフォームがWindowsのみであれば、Windowsに依存したビルド技術を使っていて大丈夫だ。しかし、実際に動作させるプラットフォームはWindowsではなくLinuxであるとか、組み込み機器やアプライアンスである場合は、ビルドに使うツールもマルチプラットフォームでなければならない。

その点、makeとMakefileはマルチプラットフォームであり、すでに多くの開発者によって使われている。makeとMakefileでビルドできるようにしておくと、組み込み機器向けの開発やアプライアンス向けの開発でも応用が利く。しかも、このツールはC言語の開発に限定されるツールではないので、一度使えるようになると使い回しも可能だ。

makeとMakefileに慣れていない、または、今回が初めてであれば、このタイミングでじっくりと慣れてもらえればと思う。このツールはちゃんと時間をかけて学び、身に着けた分だけ役に立ってくれる。

参考