【linux】-Makefile簡要知識+一個通用Makefile
- 2020 年 11 月 11 日
- 筆記
- /label/linux, /label/lzm, /label/Makefile, /study/linux/Makefile, linux, 教程集合, 源碼集合
Makefile
meke命令一般用於編譯程式,而make命令都依賴於 Makefile 文件。
最簡單的Makefile如下:
hello: hello.c
gcc -o hello hello.c
clean:
rm -f hello
註:縮進使用 tab 鍵 , 因為Makefile 會為每個以 tab 鍵開頭的命令創建一個 shell 去執行。
具體規則參考 《GUN Make 使用手冊》
以下為粗略筆記
Makefile規則與示例
為什麼需要Makefile
提高編譯效率。
Makefile樣式
一個簡單的Makefile文件包含一系列”規則”:
目標...:依賴...
<tab>命令
- 命令被執行的兩個簡單條件:
- 目標文件還沒有生成
- 依賴文件比目標文件新
先介紹Makefile的兩個函數
- $(foreach var,list,text)
for each var in list,change it to text。
對list中的每一個元素,取出賦值給var,然後把var改為text所描述的形式。
例子:
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // 最終 dep_files := .a.o.d .b.o.d
- $(wildcard pattern)
把存在的 pattern 文件列出來
例子:
src_files := $(wildcard *.c)
完善Makefile
- 先來一個簡單粗暴、效率低的:
test: main.c hello.c hello.h
gcc -o test main.c hello.c
- 再來一個Makefile,效率高、精鍊,支援自動檢測頭文件
objs := main.o hello.o
test : $(objs)
gcc -o test $^
# 需要判斷是否存在依賴文件
# .main.o.d .hello.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))
# 把依賴文件包含進來
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o : %.c
gcc -Wp,-MD,[email protected] -c -o $@ $<
clean:
rm *.o test -f
distclean:
rm $(dep_files) *.o test -f
了解自動化變數。
通用Makefile的使用
參考linux內核的Makefile來編寫一個通用的Makefile,特點:
- 支援多個目錄、多層目錄、多個文件;
- 支援給所有文件設置編譯選項;
- 支援給目錄設置編譯選項;
- 支援給某個文件單獨設置編譯選項;
- 簡單、好用。
通用的Makefile解釋
零星知識點
make命令的使用
執行make命令時,它會去當前目錄下查找名為Makefile的文件,並根據它的只是去執行操作,生成第一個目標。
也可以用 -f 選項指定文件,如:
make -f Makefile.build
指定文件的名字隨意定義。
可以使用 -C 指定目錄,切換到其他目錄去,如:
make -C a/ abc.def
可以指定目標,不再默認生成第一個目錄,如:
make -C a/ abc.def other_target
即時變數與臨時變數
變數定義語法:
形式 | 說明 |
---|---|
A = xxx | 延時變數 |
B ?= xxx | 延時變數,只有第一次定義時賦值才成功,若曾被定義過,則此賦值無效。 |
C := xxx | 立即變數 |
D += yyy | 如果D在前面是延時變數,那麼現在它還是延時變數 如果D在前面是立即變數,那麼它現在還是立即變數 |
延時變數:
使用時才確定該值。
如:
A = $@
tets:
@echo $A
輸出 A 的值為 test。
即時變數:
定義時立即確定該值。
如:
A := $@
tets:
@echo $A
輸出 A 的值為 空。
變數的導出(export)
export 是供給子目錄的 Makefile 使用的(即 sub-make),同一級的makefile是訪問不到的。
可以通過makefile中的內置變數MAKELEVEL可以查看當前的makefile的level。
Makefile中的shell
Makefile中可以使用shell命令,如:
TOPDIR := $(shell_pwd)
Makefile中放置第一個命令
執行目標時,如果不指定目標,則默認執行第一個目標。
所以,第一個目標的位置很重要。有時候不太方便把第一個目標完整地放在文件的前面,這時可以在文件的前面直接放置目標,在後面再完善它的依賴和命令。比如:
First_target: // 這句話放在前面
........ // 其他程式碼,比如include其它文件得到後的 xxx 變數
First_target : $(xxx) $(xxx)
command
假想目標
Makefile中可能會有這樣的目標:
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
注:如果當前目錄下更好有個名為「clean」的文件,那麼執行「make clean」時就不會執行makefile中目標clean的內容,所以要把clean設置為假想目標即可:
.PHONY : clean
小知識
PHONY 是 phoney,即偽造的意思。PHONY後面的target是偽造的target,不是真實存在的文件。同時,注意make命令中後面的target默認是文件。
常用函數
- $(foreach var, list, text)
for each var in list,change it to text.
直接上例子:
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // dep_files 的最終結果為「**.a.o.d .b.o.d**」
- $(wildcard pattern)
列出 pattern 存在的文件。
例子:
src_files = $(wildcard *.c) // src_files的值為當前目錄下所有 .c 文件。
- $(filter pattern…, text)
把text中符合pattern格式的內容留下來。
obj-y := a.o b.o c/ d/
DIR := $(filter %/, $(obj-y)) //結果為:c/ d/
- $(filter-out pattern…, text)
把text中符合pattern格式的內容刪除掉。
obj-y := a.o b.o c/ d/
DIR := $(filter-out %/, $(obj-y)) // 結果為:a.o b.o
- $(patsubst pattern, replacement, text)
尋找text中符合pattern格式的內容,用replacement代替他們。
subdir-y := a.o b.o c/ d/
subdir := $(patsubst %/, %, $(obj-y)) // 結果為:c d
*通用Makefile設計思想
- 在Makefile文件中確定要編譯的文件、目錄,比如:
obj-y += main.o
obj-y += a/
Makefile 文件總是被 Makefile.build 包含的。
- 在 Makefile.build 中設置編譯規則,有 3 條編譯規則:
- 怎麼編譯子目錄?
- 進入子目錄編譯即可:
- 怎麼編譯子目錄?
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
2. 如何編譯當前目錄中的目標文件?
%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
3. 當前目錄下的 **.o** 和子目錄下的 **built-in.o** 要打包起來:
$(TARGET) : built-in.o
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
思想總結:
- Makefile文件確定要編譯的文件和目錄
- Makefile.build文件包含規則
- 每個需要編譯的子目錄都放個Makefile文件,把需要編譯的文件編譯成built-in.o
- 再把所有子目錄的built-in.o鏈接到頂層的built-in.o
- 最後把頂層built-in.o鏈接到APP
最好重新分析一下通用Makefile
一個通用Makefile
本程式的Makefile分為3類:
- 頂層目錄的Makefile
- 頂層目錄的Makefile.build
- 各級子目錄的Makefile
源碼(注釋版)
Makefile
CROSS_COMPILE = # 交叉編譯工具頭,如:arm-linux-gnueabihf-
AS = $(CROSS_COMPILE)as # 把彙編文件生成目標文件
LD = $(CROSS_COMPILE)ld # 鏈接器,為前面生成的目標程式碼分配地址空間,將多個目標文件鏈接成一個庫或者一個可執行文件
CC = $(CROSS_COMPILE)gcc # 編譯器,對 C 源文件進行編譯處理,生成彙編文件
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar # 打包器,用於庫操作,可以通過該工具從一個庫中刪除或則增加目標程式碼模組
NM = $(CROSS_COMPILE)nm # 查看靜態庫文件中的符號表
STRIP = $(CROSS_COMPILE)strip # 以最終生成的可執行文件或者庫文件作為輸入,然後消除掉其中的源碼
OBJCOPY = $(CROSS_COMPILE)objcopy # 複製一個目標文件的內容到另一個文件中,可用於不同源文件之間的格式轉換
OBJDUMP = $(CROSS_COMPILE)objdump # 查看靜態庫或則動態庫的簽名方法
# 共享到sub-Makefile
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP
# -Wall : 允許發出 GCC 提供的所有有用的報警資訊
# -O2 : 「-On」優化等級
# -g : 在可執行程式中包含標準調試資訊
# -I : 指定頭文件路徑(可多個)
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include
# LDFLAGS是告訴鏈接器從哪裡尋找庫文件,這在本Makefile是鏈接最後應用程式時的鏈接選項。
LDFLAGS :=
# 共享到sub-Makefile
export CFLAGS LDFLAGS
# 頂層路徑
TOPDIR := $(shell pwd)
export TOPDIR
# 最終目標
TARGET := test
# 本次整個編譯需要源 文件 和 目錄
# 這裡的「obj-y」是自己定義的一個格式,和「STRIP」這些一樣,*但是 一般內核會搜集 」obj-」的變數*
obj-y += main.o # 需要把當前目錄下的 main.c 編進程式里
obj-y += sub.o # 需要把當前目錄下的 sub.c 編進程式里
obj-y += subdir/ # 需要進入 subdir 這個子目錄去尋找文件來編進程式里,具體是哪些文件,由 subdir 目錄下的 Makefile 決定。
#obj-y += $(patsubst %.c,%.o,$(shell ls *.c))
# 第一個目標
all : start_recursive_build $(TARGET)
@echo $(TARGET) has been built !
# 處理第一個依賴,**轉到 Makefile.build 執行**
start_recursive_build:
make -C ./ -f $(TOPDIR)/Makefile.build
# 處理最終目標,把前期處理得出的 built-in.o 用上
$(TARGET) : built-in.o
$(CC) -o $(TARGET) built-in.o $(LDFLAGS)
# 清理
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
# 徹底清理
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
Makefile.build
注意,include 命令,相對路徑為 執行本 Makefile.build 的路徑
# 偽目標
PHONY := __build
__build:
# 清空需要的變數
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=
# 包含同級目錄Makefile
# 這裡要注意,相對路徑為 執行本 Makefile.build 的路徑
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)
# 把子目錄的目標定為以下注釋
# built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# 獲取當前目錄需要編進程式的文件名作為,並寫為目標
# 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)
endif
PHONY += $(subdir-y)
# 第一個目標
__build : $(subdir-y) built-in.o
# 優先編譯 子目錄的內容
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
# 鏈接成 目標
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分為3類:
- 頂層目錄的 Makefile
- 頂層目錄的 Makefile.build
- 各級子目錄的 Makefile
1. 各級子目錄的Makefile
- 參考:
# 可以不添加下面兩個變數
EXTRA_CFLAGS :=
CFLAGS_test.o :=
obj-y += test.o
obj-y += subdir/
- obj-y += file.o 表示把當前目錄下的file.c編進程式里,
- obj-y += subdir/ 表示要進入subdir這個子目錄下去尋找文件來編進程式里,是哪些文件由subdir目錄下的Makefile決定。
- EXTRA_CFLAGS, 它給當前目錄下的所有文件(不含其下的子目錄)設置額外的編譯選項, 可以不設置
- CFLAGS_xxx.o, 它給當前目錄下的xxx.c設置它自己的編譯選項, 可以不設置
注意
- “subdir/”中的斜杠”/”不可省略
- 頂層Makefile中的CFLAGS在編譯任意一個.c文件時都會使用
- CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者組成xxx.c的編譯選項
2. 頂層目錄的Makefile
主要作用:
- 整個工程的參數初期定義
- 架構
- 工具鏈
- 編譯工具
- 編譯參數
- 需要導出的變數
- 等等
- 定義最終目標
- 跳轉到 Makefile.build
3. 頂層目錄的Makefile.build **
注意:該文件雖然放在頂層,但是也是提供給各級子目錄使用的。
主要功能:
- 把某個目錄及它的所有子目錄中、需要編進程式去的文件都編譯出來,把各個subdir/built-in.o和當前目錄的目標 .o 合併打包為當前目錄的built-in.o 。
使用提示
- 執行”make”來編譯,執行 make clean 來清除,執行 make distclean 來徹底清除。
Makefile附件
一些符號
符號 | 說明 |
---|---|
$@ | 表示規則中的目標文件集 |
$% | 當目標為函數庫的時候,則表示規則中的目標成員名。反之為空。如一個目標為”foo.a(bar.o)“,那麼,”$%”就是”bar.o”,以空格分隔開。 |
$< | 依賴文件集合中的第一個文件,如果依賴文件以”%“形式出現,則表示符合模式的一系列文件集合 |
$? | 所有比目標新的依賴集合,以空格分隔開。 |
$^ | 所有依賴文件集合,以空格分隔開。如果依賴有相同,則取其一。 |
$+ | 和 “$^”類同,但是不會把相同的刪除掉。 |
$* | 這個變數表示目標模式中 “%”及其之前的部分,如果目標是 test/a.test.c,目標模式為 a.%.c, 那麼 “$* ” 就是 test/a.test。 |
參考
- 以上筆記為整理韋東山老師筆記而來的