Help us understand the problem. What is going on with this article?

Make覚書

More than 3 years have passed since last update.

make覚書

最近makeを使うことになったので、make関する覚書を作成しています。

環境

私の作業場所で扱う計算機が下記の様に異なりますが、
特に区別せずに作業しています、ご了承ください。

  • Debian8 bash GNU Make 4.0
  • macOS-X bash GNU Make 3.81

make

makeはMakefileにルールを記述しておき、煩雑なコンパイル作業等を自動化するコマンドです。
一連の作業の中で変更の無いファイルに対してコンパイルを飛ばしたりしてくれるので便利です。

基本的なルール

Makefileはターゲット(target)、必須項目(prereq)、コマンド(comands)から成り、
必須項目のファイルがターゲットよりも新しい場合にコマンドが実行されます。
ちなみにインデントはTABでないと駄目です。

target: prereq1 prereq2
    commands

↓Makefileの簡単な例

foo: foo.c
    gcc foo.c -o foo

上記のMakefileとfoo.cが保存されているディレクトリで
makeと打てばコンパイルされます。

make targetで対象とするターゲットを指定できます。
指定しない場合は一番上のターゲットが対象になります。

処理の出力

make -nでmakeがどのような処理を行うのかを出力できます。
思った通りに動いてなさそうで困ったときに便利です。

Makefile無くても動く

組み込まれているパターンルールにより条件が整っていれば、
ターゲットを指定することによってMakefileが無くても動かすことができます。

$ make foo

擬似ターゲット

ターゲットに実在しない名前を設定して、何かしらの意味をもたせたものです。
ターゲットが存在しないので、必須項目の新旧問わずに実行されます。

target memo
all 全ての作業
install インストール
clean バイナリ削除
deisclean 元の配布物以外の生成物を削除
TAGS エディタが使用するtagsを作成する
info GNU infoを作成する
check テスト実行

自動変数

Makefile内で使用する自動変数です。
特定のタイミングで展開されます。

var memo
\$@ ターゲットファイル名
\$% ターゲットがアーカイブメンバだったときのターゲットメンバ名
\$< 最初の依存するファイルの名前
\$? ターゲットより新しいすべての依存するファイル名
\$^ すべての依存するファイルの名前
\$+ Makefileと同じ順番の依存するファイルの名前
\$* サフィックスを除いたターゲットの名前

パターンルール

Makefileに記述しなくても暗黙的に動作する組み込みルールが存在し、
make --print-data-baseで確認することができます。
パターンルール内の%はワイルドカードの様な働きをします。
↓.mdから.htmlへのパターンルール(例)

%.html: %.md
    pandoc $< -c $(CSS) -o $@

静的パターンルール

ターゲットの横にパターンルールを記述すると
そのターゲット生成にのみに適用するルールが書けます。

$(TARGET): %.o: %.c

基本的な変数

symbol memo
CURDIR カレントディレクトリ
MAKEFILE_LIST makeが読み込んだファイルのリスト
.VARIABLES 変数のリスト

暗黙のルールで使われている変数

make --print-data-baseするとよくわかります。

symbol memo default
CXX C++コンパイルコマンド g++
CXXFLAGS C++コンパイルオプション
CPPFLAGS プリプロセッサ用オプション
TARGET_ARCH
LDFLAGS リンカ用オプション
COMPILE.cc C++コンパイル実行 \$(CXX) \$(CXXFLAGS) \$(CPPFLAGS) \$(TARGET_ARCH) -c
COMPILE.C C++コンパイル実行 \$(COMILE.cc)
COMPILE.cpp C++コンパイル実行 \$(COMILE.cc)
LINK.cc C++リンク実行 \$(CXX) \$(CXXFLAGS) \$(CPPFLAGS) \$(LDFLAGS) \$(TARGET_ARCH)
LINK.C C++リンク実行 \$(LINK.cc)
LINK.cpp C++リンク実行 \$(LINK.cc)
LINK.o オブジェクのリンク \$(CC) \$(LDFLAGS) \$(TARGET_ARCH)
OUTPUT_OPTION -o $@

特殊ターゲット

必須項目側に特殊な意味を持たせるターゲット。

target memo
.PHONY 疑似ターゲット指定
.INTERMEDIATE 中間ファイル指定(make終了後自動削除)
.SECONDARY 中間ファイル指定(自動削除しない)
.PRECIOUS make中断時に勝手に削除しない
.DELETE_ON_ERROR make中断時に削除する

※通常makeはシグナルで中断された場合にのみターゲットを削除するらしい。

自動的な依存関係の生成

gcc -M main.cでそのファイルの依存しているヘッダの一覧がでます。
makeで使用可能な記述をしてくれるので扱いやすいです。

>$ gcc -I include -M main.c
main.o: main.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/features.h /usr/include/i386-linux-gnu/sys/cdefs.h \
/usr/include/i386-linux-gnu/bits/wordsize.h \
/usr/include/i386-linux-gnu/gnu/stubs.h \
/usr/include/i386-linux-gnu/gnu/stubs-32.h \
/usr/lib/gcc/i586-linux-gnu/4.9/include/stddef.h \
/usr/include/i386-linux-gnu/bits/types.h \
/usr/include/i386-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i586-linux-gnu/4.9/include/stdarg.h \
/usr/include/i386-linux-gnu/bits/stdio_lim.h \
/usr/include/i386-linux-gnu/bits/sys_errlist.h include/main.h

後はこれをうまくMakefileに組み込めればOK。
動きとしては↓の様なイメージですが、make dependを忘れる可能性が高いです。

depend: hoge.c piyo.c fuga.c
    $(CC) -M $(CPPFLAGS) $^ > $@

include depend

そこでdependに指定しているファイルを.dとして,.dをターゲットに持った処理をincludeしようと考えます。
まず、%.d: %.cのルールを追記して.cが変更されたときに.dを生成するようにします。

SOURCES=hoge.c piyo.c fuga.c
include $(subst .c,.d,$(SOURCES))

%.d: %.c
    $(CC) -M $(INCLUDES) $(CPPFLAGS) $< > $@.tmp
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.tmp > $@
    rm -f $@.tmp

※substは置換。

マニュアルでは↓になっているらしいです。
(なぜPIDを使ってユニークなファイル名にしなくてはいけないのかは良くわかりませんでした)

%.d: %.c
$(CC) -M $(INCLUDES) $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$

ライブラリを-l形式でターゲットと必須項目に使えない

Makefileでライブラリをターゲットとして指定する場合に、
そのファイルを別のターゲットの必須項目に-l 形式(-lflとか)で指定できないらしいです。

二重コロン

あるターゲットに対して必須項目によって実行する手順が異なるときに使うらしいです。

file-list:: generate-list-script
    chmod +x $<
    generate-list-script $(files) > file-list
file-list:: $(files)
    generate-list-script $(files) > file-list

代入演算子

Makefileで使われる代入演算子。
動作や展開されるタイミングが異なります。

ope memo
:= 右辺を直ぐに展開
= 変数が使われる時に展開
\?= 変数が値を持っていない場合にのみ代入
+= 変数に追加

マクロ

改行を含めた変数のように扱うと重宝します。
変数と同じように展開されます。

define create-depend
    @echo Creating $@...
    $(CC) -M $(INCLUDES) $(CPPFLAGS) $< > $@.tmp
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.tmp > $@
    rm -f $@.tmp
endef

%.d: %.c
    $(create-depend)

引数

マクロへの引数は、
$(call macro-name, param1, ...)で与え、
マクロ側では$nで受け取ることができます。
※変数はスペースを含んで渡してしまうので注意が必要です。

define func1
    @echo Creating $1
endef

%.d: %.c
    $(call func1, hoge)

組み込み関数

組み込み関数の代表的なものを以下にあげます。

func memo
\$(filter pattern, text) textをスペースで区切られたリストとしてpatternとマッチしたものを返す
\$(filter-out pattern, text) filterの逆
\$(findstring string, text) stringとマッチした時にstringを返す
\$(subst search-string,replace-string,text) 置換
\$(patsubst search-pattern,replace-pattern,text) 置換(パターンなので%が使える)
\$(words text) text内の単語数を返す
\$(word n, text) text内のn番目の単語を返します
\$(firstword text) text内の最初の単語を返します
\$(wordlist start_num, end_num, text) start_numからend_numまでの単語列を返す
\$(sort list) 重複を取り除き並び替える
\$(shell commad) コマンドを実行して標準出力を返す(標準エラー出力は返さない)
\$(wildcard pattern) パターンを展開して返す
\$(dir list) listのファイル名を取り除きディレクトリ部分だけにして返す
\$(notdir name) nameのファイル名だけを返す
\$(suffix name) nameのサフィックスのリストを返す
\$(basename name) ファイルからサフィックスすを取り除いたものを返す
\$(addsuffix suffix,name) nameの各単語に対してsuffixを付加する
\$(addprefix prefix,name) nameの各単語に対してprefixを付加する
\$(join prefix-list,suffix-list) prefixとsuffixをひとつづつ結合してリストを返す

※patsubstは$(variable:search=replace)でも実現可能。

関数の実行制御

if

if文として下記の記述ができます。
$(if condition,then-part,else-part)
else-partなどにエラーを出力させる命令を書きたい場合は、
下記の記述でメッセージと行番号を出力させることができます。
$(if condition,then-part,$(error text))
又は
$(if condition,then-part,$(warning text))
※errorはmakeを停止させますが、warnignは停止させません。

foreach

$(foreach variable,list,body)
list内の単語を一つづつvariableに代入してbodyを実行します。

展開される順番

おおきく2段階に分かれています。
この結果、マクロの宣言位置などの順序は実行に影響を及ぼさずに済んでいるようです。

  1. 変数とルールをmake内部のDBに格納し、依存関係グラフを作成

    • includeの読み込み
    • 代入演算子左辺の展開
    • :=右辺の展開
    • +=右辺の展開(再帰的でない単純な変数の場合)
    • マクロ名の展開
    • ターゲットと必須項目の展開
  2. 依存関係グラフからターゲットを特定し各コマンドを実行

    • 各右辺展開
    • マクロ本体展開
    • コマンドの展開

変数

ターゲット固有の変数

特定のターゲットに対して変数の内容を変更したい場合は下記のように対応できます。

hoge.o: CPPFLAGS += add-some-rules
hoge.o: hoge.h

コマンドラインでの変数定義

$ make CFLAGS=-gのように実行時に変数を定義てきます。
Makefile内の内容よりも優先されますが、Makefile内でoverride命令を使うことによってMakefile内の内容を優先させることもできます。
override CFLAGS=-g

環境変数

makeの開始時にmake変数として定義されます。
優先順位は低いのでコマンドラインやMakefile内の記述で上書きできます。
make実行時に--environment-overrideを使うと環境変数を優先にできます。

また、makeから別のmakeを起動した際、環境変を引き継ぎます。なので渡したい変数がある場合はexportを使います。
(この時、変数名に-が入っていても問題なく渡せます。)

export var = hoge

条件分岐

Makefile内でif条件の形式で下記の分岐が利用できます。

  • ifdef variable-name
  • ifndef variable-name
  • ifeq test
  • ifeq test

※variable-nameは\$表記しないで使います。
※testは"a" "b"(a, b)の形で使います。
※カンマ直後のスペース以外は無視されないので( a, a )のような書き方には注意が必要で、stripなどを使えば安心できます。
※コマンド部分で条件分岐を記載する際、条件行のインデントにTABを使わないように注意が必要です。

Shigets
素人ですが、十分な目玉の恩恵にあずかり、また何かしら貢献できればと思います。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした