Neunomizuの日記

俺だけが俺だけじゃない

競プロ用にMakefileを作った

競プロ用にMakefileを作った

モジュール化が大事

プログラマーたるものできる限りいらない作業を減らして効率化したいですよね?

今回は競プロでコンパイラ(C++を使っているので)する際にもう少し簡単にできるようにMakefileを作ってみました.

Makefileとは?

Wikipediaによれば,

make(メイク)は、プログラムのビルド作業を自動化するツール。コンパイル、リンク、インストール等のルールを記述したテキストファイル (makefile) に従って、これらの作業を自動的に行う。

だそうです.簡単に言えばterminal上でmakeと打つと色々勝手にしてくれるのだそうです.

実際に書いたファイル

以下が実際に書いたMakefileです.

参考にしたのはこのサイトです. この改変したコードに使って,Makefileに対する理解を深めたいと思います.

CXX := g++ # コンパイラをg++に指定
CXXFLAGS := -Wall -o # コンパイルオプションを指定
SUFFIXES := .cpp # ソースの拡張子を指定
SOURCES = $(wildcard *$(SUFFIXES)) # コンパイルするソースの指定
TARGETS = $(SOURCES: .cpp=) # 全てのファイルでコンパイルを実行

.PHONY:all # 仮想ファイルallを作る
all: $(TARGETS) # 仮想ファイルを作り,TARGETSを実行する
# $@: ターゲットファイル名
# $<: 最初に依存するファイル名
.cpp:
    $(CXX) $(CXXFLAGS) $@ $< # コンパイルオプションを使って,コンパイルを実行

変数の宣言部分の解説

これを見ただけで理解できる人はUNIXコマンドに慣れている人だと思います.

まず,この部分から

CXX := g++ # コンパイラをg++に指定
CXXFLAGS := -Wall -o # コンパイルオプションを指定
SUFFIXES := .cpp # ソースの拡張子を指定
SOURCES = $(wildcard *$(SUFFIXES)) # コンパイルするソースの指定
TARGETS = $(SOURCES: .cpp=) # 全てのファイルでコンパイルを実行

2種類の変数

Makefileにも変数があります. 書き方は簡単で以下の通りです. var := hogeもしくはvar = hoge

上を見て「:==って何が違うんだ?」と思うのは自然でしょう. 前者は単純展開変数,後者は再帰展開変数に使います.

どういうことかといいますと,

:=の右辺は宣言時に評価されます. =の右辺はその変数が使われるたびごとに評価されます(宣言されたときには評価されません).

これで普通のプログラミング言語みたい代入をしているけど,右の式の評価に違いがあるんだなということはわかっていただけたと思います.

ユーザー定義関数(要はこれも変数)

次に$が気になったと思います.

これはユーザー定義関数といいます.実を言うと自分も使い方がよくわかっていないのですが,今回のコードでは以下のように使われています.

SOURCES = $(wildcard *$(SUFFIXES)) # コンパイルするソースの指定

ここでは$$(関数 関数を実行するファイル)と使われています.(wildardの説明は下でします.今のところはある関数に右のファイルが通されたという理解でお願いします) その後,=SOURCESに代入しています.

TARGETS = $(SOURCES: .cpp=) # 全てのファイルでコンパイルを実行

これは下で述べますが,SOURCESに対して.cppという処理を行っています 対象となる全てのSOURCESの拡張子を.cpp=の右辺に何も書かないことでバイナリファイルに変換しています.

ワイルドカード

上で後回しにしたwildcardの説明をします.

これはワイルドカード表記を使えるようにする関数です.

ワイルドカードとはWikipedia)によると

コンピュータなどの関連において、ワイルドカードは、検索などグロブの際に指定するパターンに使用する特殊文字の種類で、どんな対象文字、ないし文字列にもマッチするもののことである。カードゲームのワイルドカードに由来する呼称。

です.

つまり,文字列を検索する際に使う便利な表現方法です.

ワイルドカードでは*は「0文字以上のある文字列」という意味であり, (wildcard *$(SUFFIXES))SUFFIXES := .cppと合わせて (wildcar *.cpp)という考えることができます.

日本語にすると".cpp"で終わる文字列という意味です. 競プロはC++で書くので,全てのC++ファイルが対象となります.

正規表現

ワイルドカードと類似したものに,正規表現があります. コンパイラを書くのに使い,計算機科学には欠かせないものなので興味がある人はこっちも調べてみてください.

(サルるでもわかるから安心してください)

実行部分の解説

.PHONY:all # 仮想ファイルallを作る
all: $(TARGETS) # 仮想ファイルを作り,TARGETSを実行する
# $@: ターゲットファイル名
# $<: 最初に依存するファイル名
.cpp:
    $(CXX) $(CXXFLAGS) $@ $< # コンパイルオプションを使って,コンパイルを実行

次に実行している部分の説明です.

疑似ターゲット

Makefileを実行してディレクトリ上にファイルを作りたくないときに,.PHONYを使います.

本来Makefileでは実際に存在するファイルを前提に処理が行われますが,それが嫌なときに.PHONYを使うのです.

これを疑似ターゲットといいます.実際に存在するファイルに対して行う場合はターゲットといいます.

また,これを使うと実際に存在するファイルに邪魔されず処理ができます.

ここでは"all"という疑似ターゲットを作っています.

Makefileの基礎文法

次は簡単です.

Makefileの基礎文法は

作るファイル(ターゲット or 疑似ターゲット): 必要なファイル
    コマンドでの処理

となっています.

all: $(TARGETS) # 仮想ファイルを作り,TARGETSを実行する

ここではall(疑似ターゲット)を"TARGETS"から作っています.そして,"TARGETS"の処理は上で述べたように,全てのファイルで,.cppを実行することです.

.cpp:
    $(CXX) $(CXXFLAGS) $@ $< # コンパイルオプションを使って,コンパイルを実行

.cppは下で書かれているように-Wallコンパイルオプションとして,g++コンパイルされています(変数に代入されていることを想定して).

$@$<も変数です.左はターゲットファイル名を指定し,右は依存するファイル名をしていしています.

ここでは,全ての.cppファイルを対象としているので,例えばsample.cppというファイルがあれば,sampleというバイナリファイルにコンパイルされます.

処理の振り返り

一連の流れをまとめると

  1. 変数の宣言
  2. 疑似ターゲットを作って処理をする
  3. 処理ごとに変数が宣言される

という感じです.

実際の使い方

上と同じようにsample.cppを実行すると考えましょう.このときは

$ make
$ ./sample

とすると実行することができます.

よかったら自分だけのMakefileを使って簡単コンパイル生活を楽しんでください〜〜

ちなみに

このMakefileによる不利益が生じて一切の責任は取りません←言いたかった