前言
基於韋東山三期視頻通用 Makefile 一節寫的個人筆記
相關源碼可去直接參考韋東山三期數碼相框第 7 課找
解釋
3. 編寫一個通用的Makefile
編譯test_Makefile的方法:
a. gcc -o test a.c b.c
對於a.c: 預處理、編譯、彙編
對於b.c:預處理、編譯、彙編
最後鏈接
優點:命令簡單
缺點:如果文件很多,即使你只修改了一個文件,但是所有的文件文件都要重新"預處理、編譯、彙編"
效率低
b. 寫Makefile
核心:規則
目標:依賴1 依賴2
【TAB】命令
命令執行的條件:
i. "依賴"文件 比 "目標"文件 新
ii.沒有"目標"這個文件
/*************************************/
$@--目標文件
$^--所有的依賴文件
$<--第一個依賴文件
/***********************************/
一個例子:
test:a.o b.o
gcc -o test a.o b.o
a.o:a.c a.h
%.o : %.c
gcc -c -o $@ $<
這樣寫可以自動將 .c 文件並生成可執行文件,但是有個問題
就是包括的 a.h 文件被修改時,他不會自動包括依賴關係,並
不會統對應的 .c 文件,所以會加上
a.o:a.c a.h
這樣比較麻煩,需要自動生成依賴關係纔行。
解決過程:
1. 網上搜,一個個試,這樣比較麻煩
2. 直接修改內核的某個頭文件,然後執行
make V=1
這樣可以直接看內核是怎麼解決這個問題的。
結果發現內核編譯時加上了 -Wp,-MD,a.d【依賴輸出到哪個文件】
做這樣修改:
%.o : %.c
gcc -Wp,-MD,[email protected] -c -o $@ $<
但是這樣還是很麻煩, 我們還不能自動使用生成的依賴文件。
/*********************************************************************************/
對於
$@--目標文件
$^--所有的依賴文件
$<--第一個依賴文件
的理解的一個簡單例子。
假設有一個 main.c 它調用了 mytool1.c mytool2.c 中的函數。
則其 makefile 可以寫成這樣:
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
main.o:main.c mytool1.h mytool2.h
gcc -c $<
mytool1.o:mytool1.c mytool1.h
gcc -c $<
mytool2.o:mytool2.c mytool2.h
gcc -c $<
簡化:根據默認規則 .o 文件都是由同名 .c 文件生成的,則這個 makefile 可以寫成這樣:
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
.o:.c
gcc -c $<
/***************以下是參照內核的 makefile 修改的。**********************************************************/
objs := a.o b.o
test:$(objs)
gcc -o test $^
# .a.o.d .b.o.d【依賴文件格式定義 .文件名.d 】
dep_files := $(foreach f,$(objs),.$(f).d) # 依賴文件名爲 .a.o.d .b.o.d
dep_files := $(wildcard $(dep_files)) # 根據依賴文件名,判斷這些文件是否存在,存在則 dep_files 等於他們
ifneq ($(dep_files),) # 如果依賴文件存在,說明不是第一次執行,則包含依賴文件
include $(dep_files)
endif
%.o : %.c
gcc -Wp,-MD,[email protected] -c -o $@ $< # 這裏編譯時生成依賴文件,並根據依賴文件生成可執行文件
clean:
rm *.o test
/******************** 編寫一個通用的Makefile *******************************************************************************************/
程序結構:
./
|-- display 【子目錄生成一個 built-in.o 】
| |-- disp_manager.c
| |-- fb.c
| |-- Makefile
| `-- test
| |-- Makefile
| `-- test.c
|-- draw
| |-- draw.c
| `-- Makefile
|-- encoding
| |-- ascii.c
| |-- encoding_manager.c
| |-- Makefile
| |-- utf-16be.c
| |-- utf-16le.c
| `-- utf-8.c
|-- fonts
| |-- ascii.c
| |-- fonts_manager.c
| |-- freetype.c
| |-- gbk.c
| `-- Makefile
|-- include
| |-- config.h
| |-- disp_manager.h
| |-- draw.h
| |-- encoding_manager.h
| `-- fonts_manager.h
| 【會根據子目錄下的 built-in.o 以及本地的 built-in.o 生成目標文件 】
|-- log.txt
|-- main.c
|-- Makefile.build
`-- Makefile
根目錄的 Makefile 解讀:【用於調用子目錄 makefile ,並生成目標文件】
CROSS_COMPILE = arm-linux- # 指定交叉編譯工具
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP
CFLAGS := -Wall -O2 -g # 編譯標誌
CFLAGS += -I $(shell pwd)/include # 編譯時頭文件位置
LDFLAGS := -lm -lfreetype # 鏈接時庫文件
export CFLAGS LDFLAGS
TOPDIR := $(shell pwd)
export TOPDIR
TARGET := show_file
#################################################
# 添加需要包含的子目錄文件
obj-y += main.o
obj-y += display/
obj-y += draw/
obj-y += encoding/
obj-y += fonts/
all :
make -C ./ -f $(TOPDIR)/Makefile.build # 切換到 -C 指定的目錄,使用 -f 指定的 Makefile 文件【這裏就是遞歸進各個子目錄生成 built-in.o 再生成根目錄的 built-in.o】
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
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 解讀:【用於生成目錄下的 built-in.o】
PHONY := __build # 特殊目標.PHONY的依賴是假想目標。假想目標是這樣一些目標,make 無條件的執行它命令,和目
錄下是否存在該文件以及它最後一次更新的時間沒有關係
__build:
obj-y :=
subdir-y :=
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))) # 獲得根目錄下的文件夾名稱,如 display/ 變成 display
subdir-y += $(__subdir-y)
# subdir_objs := c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) # 遍歷獲得所有子目標下生成 built-in.o
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y)) # 獲得子 makefile 中添加的 obj-y 中所有文件名,如 crt.o 經過這個處理就成了 crt
dep_files := $(foreach f,$(cur_objs),.$(f).d) # 根據文件名,轉換成 .【文件名】.d 的依賴文件,如 .crt.d
dep_files := $(wildcard $(dep_files)) # 判斷這些文件是否存在
ifneq ($(dep_files),) # 如果這些依賴文件存在,說明不是第一次編譯,包括進來
include $(dep_files)
endif
PHONY += $(subdir-y) # PHONY = 子目錄名
__build : $(subdir-y) built-in.o # __build 依賴於 子目標 及 built-in.o
$(subdir-y): # 對於 subdir-y 這具目標文件,遞歸調用 Makefile.build 的 make 文件處理
make -C $@ -f $(TOPDIR)/Makefile.build
built-in.o : $(cur_objs) $(subdir_objs) # 對於當前目錄的目標文件 built-in.o ,使用當前目錄下 .c 生成的 .o 文件生成
$(LD) -r -o $@ $^
dep_file = [email protected] # 要生成的依賴文件是以所有文件名單獨命令的,如 .crt.d .utf-16be.d 等等,dep_file = 這些名字
%.o : %.c # 對所有 .c 文件,都生成對應的 .o 文件
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $< # 依次生成各個目標 .o 文件以及依賴文件
.PHONY : $(PHONY)
本程序的 Makefile 分爲3類:
1. 頂層目錄的 Makefile
2. 頂層目錄的 Makefile.build
3. 各級子目錄的 Makefile
一、各級子目錄的 Makefile:
它最簡單,形式如下:
obj-y += file.o
obj-y += subdir/
"obj-y += file.o" 表示把當前目錄下的file.c編進程序裏,
"obj-y += subdir/" 表示要進入 subdir 這個子目錄下去尋找文件來編進程序裏,是哪些文件由 subdir 目錄下的Makefile決定。
注意: "subdir/"中的斜槓"/"不可省略
二、頂層目錄的 Makefile:
它除了定義 obj-y 來指定根目錄下要編進程序去的文件、子目錄外,主要是定義工具鏈、編譯參數、鏈接參數──就是文件中用 export 導出的各變量。
三、頂層目錄的 Makefile.build:
這是最複雜的部分,它的功能就是把某個目錄及它的所有子目錄中、需要編進程序去的文件都編譯出來,打包爲 built-in.o
詳細的講解請看視頻。
四、怎麼使用這套 Makefile:
1.把頂層 Makefile, Makefile.build 放入程序的頂層目錄
2.修改頂層 Makefile
2.1 修改工具鏈
2.2 修改編譯選項、鏈接選項
2.3 修改 obj-y 決定頂層目錄下哪些文件、哪些子目錄被編進程序
2.4 修改 TARGET,這是用來指定編譯出來的程序的名字
3. 在各一個子目錄下都建一個 Makefile,形式爲:
obj-y += file1.o
obj-y += file2.o
obj-y += subdir1/
obj-y += subdir2/
4. 執行"make"來編譯,執行"make clean"來清除,執行"make distclean"來徹底清除