目的: C++を使う競プロで競技中になるべくコマンドラインを触らないようにして楽をするためのMake入門
makeとは
- makeはコンパイル自動化ツールである。(依存関係を含む複雑なコンパイルや、別々の環境用にコンパイル方法を指定したいときに使う)
- makeはUNIX標準ツールとして用意されている。今回はGNU Makeを扱う。
- 困ったときは、 https://www.gnu.org/software/make/manual/ を見ればOK。
挙動
make
: Makefileというファイル内のallというコマンドが実行される。
make -f sample.mk sample1
: Makefileを指定して、その中のsample1というコマンドを実行
おそらく、競プロで使うならMakefileをフルパス指定しておいて、それをproconとかでaliasしておく感じになりそう
- makeを二回実行→up to dateと表示されるので、おそらく前回のコンパイル元のソースコードを記憶している…?→タイムスタンプを比較しているらしい。それによって、変更のあるものだけ再コンパイルするようになっている。
文法
targets: prerequisites
recipe
...
上のように、ターゲット(生成されるもの)と依存関係にあるものとそれらに対する操作という3つで構成されている。recipeの前にはtabを入れる。
変数名の慣習
暗黙の規則で、C, C++に対しては以下のように変数を設定する。
- CC : Cコンパイラ
- CFLAGS : Cコンパイラのフラグ
- CXX : C++コンパイラ
- CXXFLAGS : C++コンパイラのフラグ
暗黙の規則と変数は make -p
オプションで表示できる
例
test.mk
# variable
CC = gcc
OPTIMIZE = -O2
CFLAGS = $(OPTIMIZE) -Wall
# command
all: test
test: test.c
$(CC) $(CFLAGS) -o $@ $<
例の解説
make -f test.mk
で実行されるのはgcc -O2 -Wall -o test test.c
になる。- まず、上のコマンドではデフォルトでallが呼ばれる。
- allはtestを呼ぶ
testを見ていく
- test: test.cより、targetはtest、依存関係にあるのはtest.c
$(CC)
$(CFLAGS)
は変数を展開しているだけなのでそのままgcc -O2 -Wall
$@
はtargetのfilenameを指す。ここではtest$<
はprerequisitesの一つ目を指す。ここではtest.c
記号については こちら を参照
環境
- WSL ubuntu18.04
要件定義
- C++で、コンパイルを自動化したい(-Wallとかの警告をつけたい)
- https://kimiyuki.net/blog/2017/01/19/pr-online-judge-tools/ と一緒に使いたい(ojについては )
- https://kimiyuki.net/blog/2015/09/25/competitive-programming-coding/ でコーディングの注意点が分かる。コンパイラの警告オプションも参考になる
AtCoderでは、以下のように実行される(C++14 (GCC 5.4.1))
g++ -std=gnu++1y -O2 -I/opt/boost/gcc/include -L/opt/boost/gcc/lib -o a.out Main.cpp
とりあえずこれに準拠する
g++ -std=gnu++1y -O2 -Wall -o a a.cpp
最終的には、こんな感じにしたい
デバッグ用に分ける?
g++ -std=gnu++1y -Wall -g -fsanitize=undefined -D_GLIBCXX_DEBUG a.cpp
-Wall
は警告を多めに出す-g
はデバッグ情報追加-fsanitize=undefined
は未定義動作の検出-D_GLIBCXX_DEBUG
はデバッグモード
このへんはfasnitizeならこのへんを見る。
あと本筋ではないが慣れてきたらvalgrind使いたい
以上により、Makefileは以下のようにしてproconのルートに置くことにした
procon.mk
# variable
CXX = g++
CXXFLAGS = -std=gnu++1y -Wall
CXXFLAGS_RELEASE = -O2
CXXFLAGS_DEBUG = -g -fsanitize=undefined -D_GLIBCXX_DEBUG
targets = $(patsubst %.cpp, %, $(filter %.cpp, $(shell ls -al)))
# command
$(targets): %: %.cpp
$(CXX) $(CXXFLAGS) $(CXXFLAGS_RELEASE) -o $@ $<
all:
$(targets)
debug: $(targets).cpp
$(CXX) $(CXXFLAGS) $(CXXFLAGS_DEBUG) $<
~/.bash_aliases
alias chan='make -f ~/procon/procon.mk'
使い方
- ディレクトリ構造は以下の通り
/procon
|-procon.mk
|-/atcoder
|-/abc119
|-/a
|-a.cpp
|-a
|-/test
|-/sample-1.in
|-/b
|-/c
|-/d
- 以下のように使う
cd ~/procon/contests/atcoder/abc119/a
chan # 実行ファイル作成
./a < input.txt # 個人的に作ったケースを試したい場合
oj test -c ./a # kimiyukiさんのoj
chan debug # debug mode
- 別の問題に移るときディレクトリ移動がめんどくさい(nextとかでa→bに移る?どうせ何かしら文字を打つ必要があるならそこは仕方ないような気もする)
- このディレクトリ移動については、GUIでなんとかできそう
- cleanコマンドの実行(各ディレクトリごとはめんどうなので、
chan delete-all
とかでmv -iv
噛ませてprocon配下の実行ファイルすべて消し去るようにしたい。ここは気を遣う。)
参考
- http://system.blog.uuum.jp/entry/make 歴史にも触れられている。