banner
Zein

Zein

x_id

make最良の実践マニュアル

# 擬似目標
PHONY := __build
__build:             #空操作
 
# 必要な変数をクリア
obj-y :=            #コンパイルするターゲットファイルのリスト
subdir-y :=          #サブディレクトリのリスト
EXTRA_CFLAGS :=      #追加のコンパイルオプション
 
# 同じディレクトリのMakefileを含める
include Makefile
 
# 現在のMakefileがコンパイルする必要のあるサブディレクトリのディレクトリ名を取得
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y))   : c/ d/
# __subdir-y  : c d
# subdir-y    : c d
__subdir-y  := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y  += $(__subdir-y)
 
# 各サブディレクトリのターゲットファイルリストを生成:コンパイルするサブディレクトリ内のターゲットファイルはすべてdir/built-in.oにパッケージ化される
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
 
# obj-yをフィルタリングし、現在のディレクトリ内でプログラムに組み込む必要のあるファイル名を取得し、ターゲットとして書き込む
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y))
# ヘッダーファイル.hを変更した後、再度makeを実行すると再コンパイルできる(重要)
dep_files := $(foreach f,$(cur_objs),.$(f).d)
# すべての依存ファイルを含める
dep_files := $(wildcard $(dep_files))
ifneq ($(dep_files),)        #空でない(つまり依存ファイルが存在する)場合、次の操作を実行
  include $(dep_files)  #Makeツールはこれらの依存ファイルを読み込み、Makefileがソースファイルとヘッダーファイル間の正しい依存関係を知ることを保証し、ヘッダーファイルが変更されたときに影響を受けるソースファイルを再コンパイルできる
endif


PHONY += $(subdir-y)
# 最初のターゲット
__build : $(subdir-y) built-in.o
# サブディレクトリの内容を優先的にコンパイルする;-Cでサブディレクトリに切り替えて$(TOPDIR)/Makefile.buildを実行
$(subdir-y):
  make -C $@ -f $(TOPDIR)/Makefile.build 
 
# subdirのbuilt-in.oとcur_objsをリンクして総合的なbuilt-in.oターゲットを作成
built-in.o : $(cur_objs) $(subdir_objs)
  $(LD) -r -o $@ $^

dep_file = [email protected]
 
# cur_objsターゲットを生成
%.o : %.c
  $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
  
.PHONY : $(PHONY)

サブディレクトリの makefile、obj-y 情報を追加するためのもの#

# 以下の2つの変数を追加する必要はありません
EXTRA_CFLAGS  := 
CFLAGS_view.o := 

obj-y += $(patsubst %.c,%.o,$(shell ls *.c))
# obj-y += subdir/

自動化ビルドツールは以下を実現する必要があります:
1)プロジェクトがまだコンパイルされていない場合、すべてのファイルをコンパイルし、リンクする
2)プロジェクトのいくつかのファイルが変更された場合、変更されたファイルのみをコンパイルし、ターゲットプログラムにリンクする
3)プロジェクトのヘッダーファイルが変更された場合、これらのヘッダーファイルを参照している c ファイルをコンパイルし、ターゲットプログラムにリンクする
*)いくつかのシェルコマンドをパッケージ化し、プロジェクト管理を便利にする(たとえば、パッケージ化、バックアップ、中間ファイルのクリーンアップ)

make の実行#

make の起動#

デフォルトのスクリプトの優先順位:GNUmakefile > makefile > Makefile

make -f custom.mk       #エントリーメイクファイルを指定する

コマンドの印刷#

make がコマンドを実行する際、コマンドを印刷します;コマンドの前に@文字を置くと、印刷されません;
たとえばecho コンパイル中のXXXモジュール、ターミナルに表示:
echo コンパイル中のXXXモジュール コンパイル中のXXXモジュール
もし@echo コンパイル中のXXXモジュールであれば、出力されません

make -s           #コマンドをサイレント実行

コマンド実行のシェル環境#

make コマンドはデフォルトで環境変数 SHELL で指定されたシェルを使用して実行されます。ユーザーがSHELLを指定しない場合、Unix シェルのデフォルトは $(SHELL)=/bin/sh;Windows 環境では、makeは自動的に適切なコマンドインタプリタを探します(たとえばcmd.exeまたは指定された Unix スタイルのシェル(bashなど))

-------------------前のコマンドの結果を次のコマンドに適用するには、これらの2つのコマンドをセミコロンで区切ります。改行して書くと、前のコマンドの効果は保持されません
.RECIPEPREFIX = >
exec:
>  cd /home/hchen; pwd
# /home/hchenを印刷

.RECIPEPREFIX = >
exec:
>  cd /home/hchen
>  pwd
# /homeを印刷

make コマンドの失敗処理#

————————エラーを無視し、makeの実行を中止しない
clean:
    -rm -f *.o           #コマンドの前にマイナスを追加

.IGNORE: clean           #特別なターゲット.IGNOREはcleanルールのエラーを無視し、makeの実行を中止しないことを指定します
clean:
    rm -f *.o

make -i                  # -iまたは--ignore-errorsオプションを使用
make --ignore-errors

————————現在の失敗したルールをスキップし、他のルールを続行し、makeの実行を中止しない
make -k
make --keep-going

make のルールを確認する#

make -p                # すべてのルールと変数を出力します。
make -n                # 実行されるルールとコマンドを印刷しますが、実行しません
make --debug=verbose   #
make -t                # UNIXのtouchに相当し、ターゲットの変更日を最新にします。仮コンパイルに相当し、ターゲットをコンパイル済みの状態にします
make -q                # targetが存在するかを確認し、戻り値が結果を示します(0=更新が必要、2=エラー)。
make -W <file>         # 一般的にソースファイル(または依存ファイル)を指定し、Makeはルールに基づいてこのファイルに依存するコマンドを実行します。一般的に-nと一緒に使用して、このファイルに発生したルールコマンドを確認します

make の終了#

Make の終了コード:
1)0:実行成功。
2)1:実行中にエラーが発生。
3)2-qオプションが有効な場合、ターゲットは更新する必要がない

make のパラメータ#

オプション機能
-b, -m他のバージョンとの互換性に関する警告を無視します。
-B, --always-makeすべてのターゲットを強制的に再コンパイルします。
-C <dir>指定されたディレクトリに切り替えて Makefile を実行します。
--debug[=<options>]デバッグ情報を出力します。<options>には allbasicverboseimplicitjobsmakefileが含まれます。
-d--debug=allと同等です
-e, --environment-overrides環境変数が Makefile 内で定義された変数を上書きします。
-f=<file>Makefile ファイルを指定します。
-hヘルプ情報を表示します
-i, --ignore-errorsすべてのエラーを無視します。
-I <dir>インポート可能な makefile の検索パスを指定します。複数の "-I" パラメータを使用して複数のディレクトリを指定できます
-j [<jobsnum>]最大並行実行タスク数を指定します。
-k, --keep-going失敗したターゲットを無視し、他のターゲットの処理を続行します。
-l <load>許可される最大負荷値を指定し、超過すると新しいタスクを一時停止します。
-n実行されるルールとコマンドを印刷しますが、実行しません
-o <file>指定された<file>を再生成しません。ターゲットの依存ファイルが新しい場合でも。
-p, --print-data-baseすべてのルールと変数を出力します。
-q, --questiontarget が存在するかを確認し、戻り値が結果を示します(0 = 更新が必要、2 = エラー)。
-rすべての組み込みルールを無効にします。
-Rすべての組み込み変数を無効にします。
-s, --silentコマンド出力を禁止します。
-S, --no-keep-going-kオプションの効果を無効にします
-tUNIX の touch に相当し、ターゲットの変更日を最新にします。仮コンパイルに相当し、ターゲットをコンパイル済みの状態にします
-v, --versionバージョン情報を表示します。
-w, --print-directory現在のディレクトリとネストされた呼び出し情報を表示します。
--no-print-directory"-w" オプションを禁止します
-W <file>一般的にソースファイル(または依存ファイル)を指定し、Make はルールに基づいてこのファイルに依存するコマンドを実行します。一般的に - n と一緒に使用して、このファイルに発生したルールコマンドを確認します
--warn-undefined-variables未定義の変数に警告します。

ルール#

1 つのビルドルールは依存関係とコマンドで構成されます:
target:生成するターゲットファイル、実行可能ファイル、またはラベル
prerequisites:ターゲットファイルが依存するソースファイルまたは中間ファイル
command:呼び出されるシェルコマンド;通常はビルドツールコマンド;必ずタブTabで始める必要があります;または command が依存関係と同じ行にあり、;で区切ります

makeコマンドを実行すると、Make は:
1)現在のディレクトリ内の Makefile または makefile ファイルを探します。パスを指定することもできます make -f ~/makefile
2)Makefile 内の最初のターゲットを探します;ターゲットを指定することもできますmake target
3)ターゲットの prerequisites(.c/.o)が更新する必要があるかを確認します。prerequisites がターゲットより新しい場合、またはターゲットが存在しない場合、command を実行します
4)prerequisites (.o) は、ターゲットが他のファイル(.c)に依存している場合もあります。Make は.oファイルの依存関係を再帰的に追跡し、最終的なソースファイル.cがコンパイルされるまで追跡します。
5)コンパイルエラーやファイルの欠如がある場合、Make はエラーを報告し、実行を中止します;コマンドの前に +-を追加すると、ファイルが見つからない警告を無視し、実行を続行します

例: file.cファイルが変更された場合、 file.oは再コンパイルされ、editは再リンクされます

target: prerequisites; command; command
    command
    command

ネストされた make の実行#

大規模なプロジェクトでは、コードを複数のモジュールまたはサブディレクトリに分割し、各サブディレクトリが独立したMakefileを維持することが一般的なプロジェクト管理方法です。この方法により、各モジュールのコンパイルルールが明確になり、Makefileの保守が簡素化されます。

使用されるシェル変数:
MAKE:GNU makeの特別な変数で、make自身を再帰的に呼び出すために使用されます;シェルでmake -j4を実行すると仮定すると、総制御 Makefile には$(MAKE) -C subdirがあります;これは subdir でmake -j4 -C subdirを実行するのと同等です
MAKEFLAGS :システムレベルの変数で、下位のMakefileに自動的に渡されます。空に設定することで渡すのを防ぐことができます(MAKEFLAGS=
MAKELEVEL : システム変数で、現在の Make の実行のネストレベルを記録します。ネストされたmake呼び出しがある場合、MAKELEVELは現在のレベルを示します。

サブディレクトリが subdir と呼ばれる場合、このディレクトリ内に Makefile ファイルがあり、このディレクトリ内のファイルのコンパイルルールを指示します。

総制御 Makefile#

.RECIPEPREFIX = >
#現在のmakeが最外層で実行されている場合(MAKELEVEL= 0);システム関連の変数を定義します、
ifeq (0,${MAKELEVEL})
    cur-dir   := $(shell pwd)
    whoami    := $(shell whoami)  #現在のユーザー
    host-type := $(shell arch)    #アーキテクチャ
    MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif

.PHONY: all subsystem

all: subsystem   #allはsubsystemに依存します 

subsystem:
>  $(MAKE) -C subdir MAKEFLAGS= VAR=value     # -C subdirは指定されたディレクトリでmakeを実行することを示します
#または>  cd subdir && $(MAKE)

subdir/Makefile#

.PHONY: all

all:
    echo "Received VAR = $(VAR)"

例の実行結果:

-wまたは--print-directoryはネストされたMakefileのデバッグに役立ちます。現在の作業ディレクトリ情報を出力します。-Cパラメータを使用すると、-wが自動的に有効になります。-sまたは--silentパラメータを含む場合、-wは無効になります。

make: Entering directory `subdir'
Received VAR = value
make: Leaving directory `subdir'

コマンドパッケージ#

define <コマンドパッケージ名>
<command1>
<command2>
...
endef

-------例
.RECIPEPREFIX = >

define run-yacc
yacc $(firstword $^)    # $(firstword $^)は$^の中の最初の依存ファイルを取得します。
mv y.tab.c $@           # 生成されたy.tab.cファイルをfoo.cに名前変更します
endef

foo.c : foo.y
>  $(run-yacc)

変数#

変数は文字列を格納するために使用され、**C/C++** のマクロに似ています;スクリプトの保守性を向上させ、重複コードを避けることができます;例:objects変数を維持するだけで、ルールを変更する必要はありません;

命名規則:文字、数字、アンダースコア (_) を含むことができ、数字で始めることができます。大文字と小文字は区別されます;システム変数は通常すべて大文字で、CCCFLAGSのようにします。ユーザー定義の変数はキャメルケース命名を推奨します(例:MyFlags
宣言:VAR = value
参照:$(VAR)

#コマンドプレフィックスを>に変更し、デフォルトのタブ(tab)ではなく
.RECIPEPREFIX = >
objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o

#最初のターゲット
edit: $(objects)
    cc -o edit $(objects)

# 各ターゲットファイルのルール
main.o: main.c defs.h
    cc -c main.c
kbd.o: kbd.c defs.h command.h
    cc -c kbd.c
# 他の.oファイルのルール...

自動変数#

意味
$@ターゲット集合から順にターゲットを取得し、コマンドに適用します
$<依存集合から順に依存ファイルを取得し、コマンドに適用します
$^すべての依存ファイルの集合(重複なし)
$+すべての依存ファイルの集合(重複あり)
$?ターゲットより新しいすべての依存ファイルtarget: dependency1 dependency2 command $? 例えばdependency1targetより新しい場合、$?dependency1になります
$*形式ルールのターゲット集合から順にターゲットを取得し、拡張子を除いた残りのパス部分を取得しますtarget: %.c command $* もしターゲットがfile.cであれば、$*fileになります。
$%ターゲットが静的ライブラリの場合のみ、ライブラリ内のメンバーを順に取得しますlibfoo.a(bar.o): bar.o ar r $@ $% もしターゲットが静的ライブラリlibfoo.aであれば、$%はライブラリ内のメンバーbar.oになります。
$(@D)ターゲット集合から順にターゲットのディレクトリ部分を取得し、コマンドに適用しますtarget = dir/subdir/foo.o $(target): @echo $(@D) # dir/subdir
$(@F)ターゲット集合から順にターゲットのファイル名部分を取得し、コマンドに適用しますtarget = dir/subdir/foo.o $(target): @echo $(@F) # foo.o
$(<D)依存集合から順に依存ファイルのディレクトリ部分を取得し、コマンドに適用しますtarget: dir/file.c command $(<D) もし最初の依存ファイルがdir/file.cであれば、$(<D)dirになります。
$(<F)依存集合から順に依存ファイルのファイル名部分を取得し、コマンドに適用しますtarget: dir/file.c command $(<F) もし最初の依存ファイルがdir/file.cであれば、$(<F)file.cになります。
$(^D)すべての依存ファイルのディレクトリ部分の集合(重複なし)target: dir/file1.c dir/file2.c command $(^D) もし依存ファイルがdir/file1.cdir/file2.cであれば、$(^D)dirになります。
$(^F)すべての依存ファイルのファイル名部分の集合(重複なし)target: dir/file1.c dir/file2.c command $(^F) もし依存ファイルがdir/file1.cdir/file2.cであれば、$(^F)file1.c file2.cになります。
$(+D)すべての依存ファイルのディレクトリ部分の集合(重複あり)target: dir/file1.c dir/file2.c dir/file1.c command $(+D) もし依存ファイルがdir/file1.c dir/file2.c dir/file1.cであれば、$(+D)dir dir dirになります。
$(+F)すべての依存ファイルのファイル部分の集合(重複あり)target: dir/file1.c dir/file2.c dir/file1.c command $(+F) もし依存ファイルがdir/file1.c dir/file2.c dir/file1.cであれば、$(+F)file1.c file2.c file1.cになります。
$(?D)更新された依存ファイルのディレクトリ部分target: file1.c file2.c command $(?D) もしfile1.cが更新された場合、$(?D)file1になります。
$(?F)更新された依存ファイルのファイル部分target: file1.c file2.c command $(?F) もしfile1.cが更新された場合、$(?F)file1.cになります。
$(*D)形式ルールのターゲット集合から順にターゲットを取得し、拡張子とファイル名部分を除去します%.o: %.c @echo $(*D)
$(*F)形式ルールのターゲット集合から順にターゲットを取得し、拡張子とディレクトリ部分を除去します%.o: %.c @echo $(*F)

静的モードルール:
1)<targets ...> ターゲット集合、ワイルドカードを使用できます
2)ターゲット集合ファイル形式、<targets ...> から一致する新しいターゲット集合をマッチングします
3)<prereq-patterns ...> 依存ファイル集合形式、新しいターゲット集合の依存集合をマッチングします

.RECIPEPREFIX = >
bigoutput littleoutput: text.g
>  generate text.g -$(subst output,,$@) > $@              # パラメータ置換:$(subst output,,$@)は$@の中のoutputを空文字列に置き換えます

#上と同等
.RECIPEPREFIX = >
bigoutput: text.g
>  generate text.g -big > bigoutput

littleoutput: text.g
>  generate text.g -little > littleoutput

-------------------------------------静的モードルール
.RECIPEPREFIX = >
<targets ...> : <target-pattern> : <prereq-patterns ...>
>  <commands>

#例
.RECIPEPREFIX = >
objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c                    # %.o形式のターゲットファイルをマッチングし、$@から順に取得します;ターゲット集合の依存ファイル形式は%.cで、$<から順に取得します
>  $(CC) -c $(CFLAGS) $< -o $@

#上と同等
foo.o: foo.c
>  $(CC) -c $(CFLAGS) foo.c -o foo.o

bar.o: bar.c
>  $(CC) -c $(CFLAGS) bar.c -o bar.o



コマンドライン環境変数#

Makefile 内部で定義された変数の他に、makefile は $(var) の形式で 1)os環境変数と 2)make オプションで渡されたコマンドライン環境変数を参照できます;

同名の変数の優先順位:コマンドラインで渡された変数の値 >Makefile内で定義された変数の値 > os 環境変数
1)
Makefile内で定義された変数の値の優先順位を上げる:override変数プレフィックス
2)os 環境変数の優先順位を上げる:
make -e**
**
例:
make CFLAGS="-O2 -Wall"CFLAGS** 環境変数を渡します;export CC=gccは os 環境変数 CC を渡します

# Makefile
all:
    echo $(CFLAGS)      #渡されました-O2 -Wall
    echo $(CC)

$$bash内で現在のプロセスの PID(プロセス ID)を表します
$$$$は現在のプロセスの PID の最後の 4 桁を挿入します。通常は一意の一時ファイル名を生成するために使用されます

makefile 間での変数の渡し方#

export                       # すべての変数を渡します
export variable += value;    # 変数variableを下位のMakefileに渡します
unexport variable := value;  # 変数variableを下位のMakefileに渡さないようにします

代入#

変数の値は他の変数の値に依存できます

-----------------=  再帰的代入:依存する変数の値を再帰的に展開します;この代入方法は柔軟で、変数の定義を遅延させることができますが、無限再帰代入を書くことができ、依存する変数が再度代入されるたびに再計算が必要です
all:
    echo $(foo)
    
foo = $(bar)
bar = $(ugh)
ugh = Huh?

-----------------:=  即時代入:代入時に右側の式を展開し、結果を変数に格納します。再帰的代入よりも性能が高く、安全です
x := foo
y := $(x) bar    # foo bar;即時に展開されたため、次の文はyの値に影響を与えません
x := later

-----------------?=  条件代入:未定義の変数に対してのみ代入が有効;定義済みの変数には何もしません(元の定義を上書きしません)
FOO ?= bar

-----------------# コメントとしてだけでなく、変数定義の終了位置を示すためにも使用できます
dir := /foo/bar    # frobsを置くディレクトリ

-----------------+=  変数値を追加します。元の変数が:=で定義されている場合、+=は即時代入となります;変数が=で定義されている場合、+=は再帰的代入となります
objects = main.o foo.o bar.o utils.o
objects += another.o             #相当するのはobjects = $(objects) another.o

-----------------makefile内で定義された特定の変数に最高の優先順位を指定し、makeオプションで渡されたコマンドライン変数によって上書きされないようにします
override <variable> := <value>
override <variable> += <more text>
override <variable> = <value>

-----------------複数行の変数定義:コマンドパッケージと同様
define <variable>
<value>
endef

変数値の置換#

-----------------単純置換
${var:a=b}   # 変数varの中でaで終わる部分をbに置き換えます

foo := a.o b.o c.o
bar := $(foo:.o=.c) 

-----------------パターン置換
$(var:%.suffix1=%.suffix2)   # 変数varの中で%.suffix1を%.suffix2に置き換えます

foo := a.o b.o c.o
bar := $(foo:%.o=%.c)

ターゲット固有の変数#

変数は特定のターゲットとその依存にのみ有効であり、他のターゲットの設定に影響を与えないようにします

-----------------------------------------変数式は特定のターゲット<target>とその依存にのみ有効です
<target> : <variable-assignment>              # 変数式は特定のターゲット<target>とその依存にのみ有効です
<target> : override <variable-assignment>
#例:全体のCFLAGSの値が何であれ、progターゲットとその関連ターゲット(prog.o、foo.o、bar.o)はCFLAGS = -gを使用します
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
    $(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o : prog.c
    $(CC) $(CFLAGS) prog.c

foo.o : foo.c
    $(CC) $(CFLAGS) foo.c

bar.o : bar.c
    $(CC) $(CFLAGS) bar.c

---------------------------変数式は特定の形式ターゲットにのみ有効です
<pattern ...>; : <variable-assignment>;
<pattern ...>; : override <variable-assignment>;
%.o : CFLAGS = -O2           #変数式は.oで終わるターゲットにのみ有効です


動的変数名#

dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)        #$(dir)_sourcesは$(dir)ディレクトリ内のすべてのcファイルを定義します
define $(dir)_print                             #動的にコマンドパッケージを定義することさえできます
lpr $($(dir)_sources)                           #$(dir)_sourcesの値を印刷します
endef

特殊値のエスケープ#

$$Makefile 内では、単一の$は変数参照に使用されます。任意の$$$文字のエスケープと見なされます

-----------------空白を含む変数を定義する
nullstring :=
space := $(nullstring)

ファイルパスの検索#

シェルワイルドカード:
*(任意の長さの文字にマッチ)
? (1 文字にマッチ)
~ (ホームディレクトリまたは環境変数)

特別な変数VPATHを指定しない場合、make は現在のディレクトリ内で依存ファイルとターゲットファイルを探します。この変数を定義すると、make は現在のディレクトリでファイルが見つからない場合、指定されたディレクトリでファイルを探します

VPATH = src:../headers       # 現在のディレクトリを探し、次にsrcと../headersを探します

vpath <pattern> <directories>
vpath %.h ../headers     #現在のディレクトリで見つからない場合、../headersディレクトリ内のすべての.hで終わるファイルを検索します
vpath %.c foo            #.cファイルは"foo""blish""bar"ディレクトリの順に検索されます
vpath %   blish
vpath %.c bar

objects := $(wildcard *.o)                        # *.oワイルドカードを展開し、すべての.oファイルをリストします

$(patsubst %.c,%.o,$(wildcard *.c))               # %.c文字列を%.o文字列に置き換え、すべての.cファイルに対応する.oファイルをリストします

objects := $(patsubst %.c,%.o,$(wildcard *.c))    # すべての.cおよび.oファイルをコンパイルおよびリンクします
foo : $(objects)
>  cc -o foo $(objects)

隠れたルール#

プロジェクトのリンク段階のルールを記述するだけで、ソースファイルからターゲットファイルへの構築ルールは、1)組み込みルールライブラリ、ユーザー定義の 2)パターンルールおよび 3)サフィックスルールに基づいて自動的に推論されます;

隠れたルールの優先順位:ルールは組み込みの順序で一致し、優先順位が高いルールが低いルールを上書きします;makeはデフォルトの隠れたルールをオーバーロードすることを許可します;一般的にはパターンルールの形式でオーバーロードされます。

以下は一般的な組み込み自動推論ルールです:

ターゲット前提条件コマンド
<n>.o<n>.c$(CC) -c $(CFLAGS) $(CPPFLAGS)
<n>.o<n>.cc / <n>.C$(CXX) -c $(CXXFLAGS) $(CPPFLAGS)
<n><n>.o$(CC) $(LDFLAGS) <n>.o $(LDLIBS)
<n>.c<n>.y$(YACC) $(YFLAGS)
<n>.c<n>.l$(LEX) $(LFLAGS)

およびデフォルト変数:

コマンド変数説明デフォルト値
AR関数ライブラリパッケージプログラムar
ASアセンブリ言語コンパイラas
CCC 言語コンパイラcc
CXXC++ 言語コンパイラg++
CORCS ファイルからファイルを拡張するプログラムco
CPPC プログラムのプリプロセッサ$(CC) -E
FCFortran および Ratfor コンパイラf77
LEXLex 構文解析器(C または Ratfor 用)lex
PCPascal コンパイラpc
YACCYacc 構文解析器(C プログラム用)yacc
MAKEINFOTexinfo ファイルを Info 形式に変換するプログラムmakeinfo
パラメータ変数説明デフォルト値
ARFLAGSAR コマンドのパラメータrv
ASFLAGSアセンブラのパラメータ
CFLAGSC 言語コンパイラのパラメータ
CXXFLAGSC++ 言語コンパイラのパラメータ
COFLAGSRCS コマンドのパラメータ
CPPFLAGSC プリプロセッサのパラメータ
FFLAGSFortran コンパイラのパラメータ
LDFLAGSリンカのパラメータ
LFLAGSLex 構文解析器のパラメータ
PFLAGSPascal コンパイラのパラメータ
RFLAGSRatfor コンパイラのパラメータ
YFLAGSYacc 構文解析器のパラメータ

自動推論構築ルール#

GNUmake は特定の prerequisites から target への構築ルールを自動的に推論できます。たとえば、C プロジェクトの各x.oファイルには必ず同名のx.c依存があり、必ずcc -c x.cがあります;

自動推論にはルールの一致優先順位の問題があります:たとえば、明示的にfoo.o : foo.pを指定した場合、ディレクトリ内にfoo.cが存在する場合、make は自動的にfoo.cfoo.oを構築するルールを推論します;make -rまたはmake --no-builtin-rulesはすべての組み込み隠れたルールを無効にします。明示的なルールを追加することで隠れたルールを上書きできます。

.RECIPEPREFIX = >
objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o

edit: $(objects)
    cc -o edit $(objects)

# 自動推論ルール、makeはこれらのルールを推論します
main.o: defs.h
kbd.o: defs.h command.h
command.o: defs.h command.h
# 他のルール...

別のスタイル:実際には、同じ.hファイルのターゲットをマージして表示することです;私は好きではありません

.RECIPEPREFIX = >
objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o

edit: $(objects)
    cc -o edit $(objects)

# 複数のファイルの依存をマージ
$(objects): defs.h
kbd.o command.o files.o: command.h
display.o insert.o search.o files.o: buffer.h

.PHONY: clean
clean:
    rm edit $(objects)

パターンルール#

一般的な形式を使用してターゲットと依存ファイルの関係を記述します。ターゲットまたは依存ファイルの中で%を使用して任意の文字またはファイル名の一部を表します

#例:パターンルールを使用して組み込みの.cから.oへの構築ルールをオーバーロードします
%.o : %.c
    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

%.o : %.s    # 組み込みの隠れたルールを無効にします。後ろにコマンドを書かない限り

サフィックスルール#

互換性のある古いルールを保持することは推奨されません。こんなプロジェクトに出会ったら再度見てください

他の Makefile を参照する#

他の Makefile ファイルのルールと変数を現在の Makefile に読み込みます;includeでファイルを含めると、Make は以下のいくつかの場所でファイルを探します:
1) 現在のディレクトリ
2) -Iで指定されたディレクトリ
3) デフォルトのシステムディレクトリ(例:/usr/include

-include other.make *.mk $(othermake)         #ファイル名でもワイルドカードでも可能

sinclude other.make *.mk $(othermake)         #同上

擬似目標#

ターゲットがファイルではなく、make の後に続く操作ラベルを示します。シェルコマンドをパッケージ化するために使用されます;擬似目標はファイルと同名にできないため、これを保証するために.PHONYを明示的に宣言します。これにより、同名のファイルが存在しても擬似目標が実行されます;

擬似目標は他の擬似目標に依存でき、階層構造を形成します。深さ優先で操作を実行します;

GNU の慣例:
1)all: デフォルトのターゲットで、すべての内容をコンパイルします。
2)clean: すべての生成ファイルを削除します。ターゲットファイルや圧縮ファイルを含みます。
3)install: コンパイルされたプログラムをシステムパスにインストールします。
4)print: 最後のビルド以来変更されたソースファイルをリストします。
5)tar: ソースプログラムと関連ファイルをパッケージ化し、.tarファイルを生成します。
6)dist: tar ファイルを圧縮し、.tar.gzファイルを生成します。
7)TAGS: プレースホルダルールで、ctagsまたはetagsを統合してコードインデックスを生成できます。
8)check/test: テストを実行します。通常はユニットテストフレームワークやスクリプトにリンクできます。

.RECIPEPREFIX = >
.PHONY: cleanall cleanobj cleandiff

cleanall: cleanobj cleandiff
>  rm -f program

cleanobj:
>  rm -f *.o

cleandiff:
>  rm -f *.diff


自動生成依存関係#

make は依存関係に基づいてどの.c.h ファイルが変更されたかを判断し、再コンパイルが必要です;自動生成依存関係は、大規模プロジェクトの手動管理の苦痛を回避します;
gcc -MM file.cは依存関係を生成します:file.o: file.c defs.h gcc -M main.cは依存関係を生成します(標準ライブラリのヘッダーファイルを含む):main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \ /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
GNU 組織は、各name.cファイルに対してname.dの Makefile を生成することを推奨しています。.dは対応する.cの依存関係を保持します。make は自動的に更新または生成された.dファイルを含め、主 Makefile に自動的に各ファイルの依存関係を生成します
sed 's,pattern,replacement,g':文字列置換コマンド
1)pattern :一致させる内容;\($*\)\.o[ :]*$はゼロ個または複数の文字に一致し、*は前の要素がゼロ回または多回出現することを示します。キャプチャグループ\(\)内に$*を入力すると、文字列が\(\)にキャプチャされます;\.o.oに一致します;[ :]*は複数の :**** に一致します。
2)replacement :正規表現の一致を置き換える内容;
\1.o $@ :\1は最初のキャプチャグループを示し、$@**** はMakefile ルール内のターゲットmain.o main.d :に置き換えられます;したがって、.dファイルも自動的に更新および生成されます。
3)g :すべての一致する場所を置き換えることを示します。

.RECIPEPREFIX = >

sources = foo.c bar.c

%.d: %.c
>  @set -e; rm -f $@; \                                     # @set -eはスクリプトがエラーに遭遇した場合(コマンドの戻り値がゼロ以外)に即座に終了するように設定します;rm -f $@は既存の中間ファイルを削除します
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \                    # 由%.c逐一依存関係を構築し、一時ファイル$@.$$$$にリダイレクトします。これは%.d.$$$$に相当します
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \   # < $@.$$$$はファイル%.d.$$$$から入力を読み取ります;置き換えられた内容は%.dに出力されます;このステップは子.dを自動的に更新するためです
    rm -f $@.$$$$
    
include $(sources:.c=.d)            #  変数$(sources)のすべての.cの文字列を.dに置き換え、その依存関係を含めます


中間ターゲット処理#

.INTERMEDIATE: mid         #midファイルを中間ターゲットとして明示的に宣言します。makeはそれを最終ターゲットとして処理しません。midファイルが生成された後、自動的に削除されません
.SECONDARY: sec            #secファイルは中間ターゲットですが、makeは最終ターゲットが生成された後に削除しません
.PRECIOUS: %.o             # すべての.oファイルを保持するようにmakeに指示します。中間ターゲットであっても

条件式#

<conditional-directive>
<text-if-true>             #
else
<text-if-false>
endif

-----------------ifeq と ifneq
.RECIPEPREFIX = >
foo = bar

ifeq ($(foo),bar)
>  echo "fooはbarです"
endif

ifneq ($(foo),baz)
>  echo "fooはbazではありません"
endif

-----------------ifdef と ifndef
.RECIPEPREFIX = >
foo = bar

ifdef foo
>  echo "fooは定義されています"
else
>  echo "fooは定義されていません"
endif

関数#

関数呼び出し構文#

$(<function> <arguments>)
${<function> <arguments>}

#<function>は関数名;<arguments>は引数で、カンマで区切ります;関数名と引数の間は空白で区切ります

文字列処理#

$(subst <from>,<to>,<text>)   # 文字列置換:文字列<text>の中の<from>を<to>に置き換えます;戻り値を返します
#例: $(subst ee,EE,feet on the street) 

$(patsubst <pattern>,<replacement>,<text>)   # 文字列置換:文字列<text>の中の<pattern>を<replacement>に置き換えます;戻り値を返します
#例: $(patsubst %.c,%.o,x.c.c bar.c) 

$(strip <string>)   # 文字列の先頭と末尾の空白を削除します
#例: $(strip  a b c  ) 

$(findstring <sub_text>,<text>)   # 文字列<text>の中で部分文字列<sub_text>を探します
#例: $(findstring a,a b c) # 戻り値:a

$(filter <pattern...>,<text>)   # <text>の中から<pattern...>に一致する部分をフィルタリングします
#例: $(filter %.c %.s,bar.c baz.s ugh.h) 

$(filter-out <pattern...>,<text>)   # <text>の中から<pattern...>に一致しない部分をフィルタリングします
#例: $(filter-out main1.o main2.o,main1.o foo.o main2.o) 

$(sort <list>)   # ソートして重複を削除します
#例: $(sort foo bar lose foo) 

$(word <n>,<text>)   # <text>の中から第n単語を抽出します
#例: $(word 2,foo bar baz)  # barを取得しました

$(wordlist <start>,<end>,<text>)   # 単語範囲を抽出します
#例: $(wordlist 2,3,foo bar baz)  # 戻り値:bar baz

$(words <text>)   # 単語数をカウントします
#例: $(words foo bar baz)    # 戻り値:3

$(firstword <text>)   #  最初の単語を取得します
#例: $(firstword foo bar) 

ファイル名操作#

$(dir <path...>)            #ディレクトリ部分を抽出します
#例: $(dir src/foo.c hacks) # 戻り値:src/ ./

$(notdir <path...>)      #非ディレクトリ部分を抽出します
#例: $(notdir src/foo.c hacks)      # 戻り値:foo.c hacks

$(suffix <names...>)      # ファイルのサフィックスを抽出します
#例: $(suffix src/foo.c src-1.0/bar.c hacks) # 戻り値:.c .c

$(basename <names...>)      # ファイル名のプレフィックスを抽出します
#例: $(basename src/foo.c src-1.0/bar.c hacks)    # 戻り値:src/foo src-1.0/bar hacks

$(addsuffix <suffix>,<names...>)      # <names...>のファイルにサフィックス<suffix>を追加します
#例: $(addsuffix .c,foo bar)          # 戻り値:foo.c bar.c

$(addprefix <prefix>,<names...>)      # プレフィックスを追加します
#例: $(addprefix src/,foo bar)        # 戻り値:src/foo src/bar

$(join <list1>,<list2>)      # 2つのリストを結合します
#例: $(join aaa bbb,111 222 333)     # 戻り値:aaa111 bbb222 333

高度な関数#

$(foreach <var>,<list>,<text>)            # リストをループ処理します:<list>から順に単語を取り出し、<var>に代入し、式<text>を計算し、最終的に各計算結果を空白で連結した文字列を返します
#例: files := $(foreach n,a b c d,$(n).o)     # 戻り値:a.o b.o c.o d.o

$(if <condition>,<then-part>,<else-part>)      #条件判断  
#例: $(if 1,yes,no)     #yes

$(call <expression>,<parm1>,<parm2>,...)      #ユーザー定義関数の呼び出し:プレースホルダ$(1)、$(2)などでパラメータを示し、<expression>を計算して戻り値を返します
#例: 
reverse = $(2) $(1)
foo = $(call reverse,a,b) 
# 戻り値:b a

$(origin <variable>)      # 変数の由来を確認します
#戻り値:
undefined:    未定義
default:      デフォルト定義
environment:  環境変数
file:         Makefile定義
command line: コマンドライン定義
override:     override定義
automatic:    自動変数

$(shell <command>)      #シェルコマンドを実行します
#例: files := $(shell echo *.c) 

$(error <text...>)      # makeの実行を終了し、指定されたエラーメッセージを表示します
#例: 変数が空であるかどうかを確認します
MY_VAR :=

ifeq ($(MY_VAR),)
$(error MY_VARは未定義または空です)
endif

$(warning <text...>)      #警告を出力しますが、ビルドプロセスを中断しません
#例: デバッグ情報を出力します
MY_VAR :=

ifeq ($(MY_VAR),)
$(warning MY_VARは未定義または空です)
endif

関数ライブラリファイルの更新#

-j 並行ビルドライブラリは ar パッケージに影響を与える可能性があります

#例:すべての.oファイルをfoolib関数ライブラリにパッケージ化します
foolib(*.o) : *.o
    ar cr foolib *.o

テンプレート#

tree#

<PROJECT_NAME >/
├── build/          # コンパイル生成されたターゲットファイル、依存ファイルなどを格納
├── bin/            # 最終生成された実行可能ファイルを格納
├── inc/            # ヘッダーファイルを格納
├── lib/            # ライブラリファイルを格納
├── src/            # ソースコードファイル (.c)を格納
├── tests/          # テストコードを格納
├── submodule1/     # 最初のサブモジュール
│   └── Makefile    # サブモジュールのMakefile
├── submodule2/     # 第二のサブモジュール
│   └── Makefile    # サブモジュールのMakefile
├── Makefile        # トップレベルのMakefile
└── <その他のファイル>      # 他の文書、設定ファイルなどを含む可能性があります

総制御 makefile#

生成されたターゲットファイルを削除し、再コンパイルできるようにします。より堅牢な方法は、.PHONYを使用して宣言し、削除コマンドの前に-を追加することです。これにより、一部のファイルが存在しなくてもエラーが発生しません:

.RECIPEPREFIX = >
# -------------------------------
# 一般設定
# -------------------------------
PROJECT_NAME := 
VERSION := 1.0.0

INC_DIR := inc
SRC_DIR := src
LIB_DIR := lib
BIN_DIR := bin
TEST_DIR := tests
BUILD_DIR := build
SUBMODULES := submodule1 submodule2
INSTALL_DIR := /usr/local/bin
TAR_FILE := $(PROJECT_NAME)-$(VERSION).tar.gz

# -------------------------------
# コンパイラとフラグ
# -------------------------------
CC := cc
CXX := g++
AR := ar
AS := as
CO := co
CPP := $(CC) -E
LEX := lex
YACC := yacc
MAKEINFO := makeinfo
      
CFLAGS := -Wall -Wextra -Og
LIBS := 
ARFLAGS := rv
ASFLAGS := 
CXXFLAGS := 
COFLAGS := 
CPPFLAGS := 
LDFLAGS := 
LFLAGS := 
YFLAGS :=

# -------------------------------
# 非独立ビルドでサブモジュールのMakefileを読み込む(つまり、サブモジュールを静的ライブラリおよびソースコードとして使用する);各モジュールが独立してビルドされる場合は必要ありません
# -------------------------------
#-include $(foreach submodule, $(SUBMODULES), $(submodule)/Makefile)

# -------------------------------
# トップレベルファイル
# -------------------------------
SRCS := $(shell find $(SRC_DIR) -name '*.c')
OBJS := $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS))
DEPS := $(OBJS:.o=.d)

# -------------------------------
# ターゲット
# -------------------------------
.PHONY: all clean install print tar dist TAGS test $(SUBMODULES) #関連ツールがインストールされているかどうかを確認するターゲットも追加すべきです

all: $(PROJECT_NAME)

# メインターゲット実行可能ファイルをビルド
$(PROJECT_NAME): $(OBJS) $(SUBMODULES)
> $(CC) $(OBJS) -o $(BIN_DIR)/$@ $(LDFLAGS) $(LIBS)

# サブモジュールをビルド:サブモジュールをループし、そこに入ってmakeを実行します;Makefileが見つからない場合は、スキップを印刷します
$(SUBMODULES):
> @$(foreach submodule, $(SUBMODULES), \
    $(MAKE) -C $(submodule) || echo "Skipping $(submodule)";)

# 自動的に依存ファイルを生成します
-include $(DEPS)

# クリーンターゲット:ビルドアーティファクトを削除します
clean:
> rm -rf $(BUILD_DIR) $(BIN_DIR) $(LIB_DIR) $(TAR_FILE)
> $(foreach submodule, $(SUBMODULES), $(MAKE) -C $(submodule) clean || echo "Skipping $(submodule)";)

# インストールターゲット:プログラムをインストールします
install: all
> install -m 755 $(BIN_DIR)/$(PROJECT_NAME) $(INSTALL_DIR)

# 印刷ターゲット:変更されたソースファイルをリストします
print:
> @echo "最後のビルド以来変更されたソースファイル:"
> @find $(SRC_DIR) -type f -newer $(BIN_DIR)/$(PROJECT_NAME) -print || echo "変更されたソースファイルはありません。"

# タールボールターゲット:ソースタールボールを作成します
tar:
> tar -cvzf $(TAR_FILE) $(SRC_DIR) $(INC_DIR) $(LIB_DIR) Makefile $(SUBMODULES)

# distターゲット:タールボールを圧縮します
dist: tar
> gzip $(TAR_FILE)

# TAGSターゲット:ctagsまたはetagsを生成します
TAGS:
> ctags -R $(SRC_DIR) $(INC_DIR) $(SUBMODULES)

# テストターゲット:ユニットテストを実行します(別のテストスイートを想定)
test:
> @echo "テストを実行中..."
> $(MAKE) -C $(TEST_DIR)

# -------------------------------
# コンパイルルール
# -------------------------------
# オブジェクトファイルを作成するルール。ここでの妙は.c.hが更新されると.oが更新される;.oが更新されるとこのルールが参照され、.dも更新される
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
> @mkdir -p $(@D)
> $(CC) $(CFLAGS) -MD -MP -c $< -o $@

# すべての依存ファイルを含める
-include $(DEPS)


独立ビルドサブモジュール makefile#



非独立ビルドサブモジュール makefile#

.RECIPEPREFIX = >

# サブモジュール設定
MODULE_NAME := submodule1
SRC_DIR := src
INC_DIR := inc
BUILD_DIR := build
LIB_DIR := ../lib

CC := cc
CFLAGS := -Wall -Wextra -Og -I$(INC_DIR)
AR := ar
ARFLAGS := rv

SRCS := $(shell find $(SRC_DIR) -name '*.c')
OBJS := $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRCS))
LIB := $(LIB_DIR)/lib$(MODULE_NAME).a
DEPS := $(OBJS:.o=.d)

.PHONY: all clean

# デフォルトターゲット
all: $(LIB)

# 静的ライブラリを生成
$(LIB): $(OBJS)
> @mkdir -p $(LIB_DIR)
> $(AR) $(ARFLAGS) $@ $^

# コンパイルルール
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
> @mkdir -p $(@D)
> $(CC) $(CFLAGS) -MMD -MP -c $< -o $@

# クリーンルール
clean:
> rm -rf $(BUILD_DIR) $(LIB)

# 自動的に依存ファイルを含める
-include $(DEPS)

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。