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 信息#

# 可以不添加下面两个变量
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       #指定入口makefileファイル

コマンドの印刷#

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, --questionターゲットが存在するかどうかを確認し、戻りコードが結果を示します(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 "受け取ったVAR = $(VAR)"

例の実行結果:

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

make: Entering directory `subdir'
受け取った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    # frobを配置するディレクトリ

-----------------+=  変数値を追加します。元の変数が:=で定義されている場合、+=は即時代入です。変数が=で定義されている場合、+=は再帰的代入です。
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.cからfoo.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[ :]*$は 0 個以上の文字に一致し、*は前の要素が0 回または多回出現することを示します。キャプチャグループ\(\)$*を入力すると、文字列が\(\)にキャプチャされます;\.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/     # 2番目のサブモジュール
│   └── 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)

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