Makefile 的使用
在 Linux 中使用 make 命令來編譯程式,特別是大程式;而 make 命令所執行的動作依賴於 Makefile 檔案。最簡單的 Makefile 檔案如下:
hello: hello。c
gcc -o hello hello。c
clean:
rm -f hello
將上述 4 行存為 Makefile 檔案(注意必須以 Tab 鍵縮排第 2、4 行,不能以空格鍵縮排),放入 01_hello目錄下,然後直接執行 make 命令即可編譯程式,執行“make clean”即可清除編譯出來的結果。
make 命令根據檔案更新的時間戳來決定哪些檔案需要重新編譯,這使得可以避免編譯已經編譯過的、沒有變化的程式,可以大大提高編譯效率。
要想完整地瞭解 Makefile 的規則,請參考《GNU Make 使用手冊》,以下僅粗略介紹。
3。1 配套影片內容大綱
3。1。1 Makefile 規則與示例
參考文件:gunmake。htm
① 為什麼需要 Makefile
怎麼高效地編譯程式?
想達到什麼樣的效果?請參考 Visual Studio:修改原始檔或標頭檔案,只需要重新編譯牽涉到的檔案,
就可以重新生成 APP
② Makefile 其實挺簡單
一個簡單的 Makefile 檔案包含一系列的“規則”,其樣式如下:
目標(target)…: 依賴(prerequiries)…
如果“依賴檔案”比“目標檔案”更加新,那麼執行“命令”來重新生成“目標檔案”。命令被執行的 2 個條件:依賴檔案比目標檔案新,或是 目標檔案還沒生成。
③ 先介紹 Makefile 的 2 個函式
A。 $(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
B。 $(wildcard pattern)
pattern 所列出的檔案是否存在,把存在的檔案都列出來。
例子:
src_files := $( wildcard *。c) // 最終 src_files 中列出了當前目錄下的所有。c` 檔案
④ 一步一步完善 Makefile
第 1 個 Makefile,簡單粗暴,效率低:
test : main。c sub。c sub。h
gcc -o test main。c sub。c
第 2 個 Makefile,效率高,相似規則太多太囉嗦,不支援檢測標頭檔案:
test : main。o sub。o
gcc -o test main。o sub。o
main。o : main。c
gcc -c -o main。o main。c
sub。o : sub。c
gcc -c -o sub。o sub。c
clean:
rm *。o test -f
第 3 個 Makefile,效率高,精煉,不支援檢測標頭檔案:
test : main。o sub。o
gcc -o test main。o sub。o
%。o : %。c
gcc -c -o $@ $<
clean:
rm *。o test -f
第 4 個 Makefile,效率高,精煉,支援檢測標頭檔案(但是需要手工新增標頭檔案規則):
test : main。o sub。o
gcc -o test main。o sub。o
%。o : %。c
gcc -c -o $@ $<
sub。o : sub。h
clean:
rm *。o test -f
第 5 個 Makefile,效率高,精煉,支援自動檢測標頭檔案:
objs := main。o sub。o
test : $(objs)
gcc -o test $^
# 需要判斷是否存在依賴檔案
# 。main。o。d 。sub。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,。$@。d -c -o $@ $<
clean:
rm *。o test -f
distclean:
rm $(dep_files) *。o test -f
3。1。2 通用 Makefile 的使用
我參考 Linux 核心的 Makefile 編寫了一個通用的 Makefile,它可以用來編譯應用程式:
① 支援多個目錄、多層目錄、多個檔案;
② 支援給所有檔案設定編譯選項;
③ 支援給某個目錄設定編譯選項;
④ 支援給某個檔案單獨設定編譯選項;
⑤ 簡單、好用。
使用 git 下載本教程的文件後,下列目錄中就有說明和示例:
01_all_series_quickstart\
04_嵌入式 Linux 應用開發基礎知識\source\05_general_Makefile
3。1。3 通用 Makefile 的解析
① 零星知識點
A。 make 命令的使用:
執行 make 命令時,它會去當前目錄下查詢名為“Makefile”的檔案,並根據它的指示去執行操作,生成第一個目標。
我們可以使用“-f”選項指定檔案,不再使用名為“Makefile”的檔案,比如:
make -f Makefile。build
我們可以使用“-C”選項指定目錄,切換到其他目錄裡去,比如:
make -C a/ -f Makefile。build
我們可以指定目標,不再預設生成第一個目標:
make -C a/ -f Makefile。build other_target
B。 即時變數、延時變數:
變數的定義語法形式如下:
A = xxx // 延時變數
B ?= xxx // 延時變數,只有第一次定義時賦值才成功;如果曾定義過,此賦值無效
C := xxx // 立即變數
D += yyy // 如果 D 在前面是延時變數,那麼現在它還是延時變數;
// 如果 D 在前面是立即變數,那麼現在它還是立即變數
在 GNU make 中對變數的賦值有兩種方式:延時變數、立即變數。
上圖中,變數 A 是延時變數,它的值在使用時才展開、才確定。比如:
A = $@
test:
@echo $A
上述 Makefile 中,變數 A 的值在執行時才確定,它等於 test,是延時變數。
如果使用“A := @ ” , 這 是 立 即 變 量 , 這 時 @”,這是立即變數,這時@”,這是立即變數,這時@為空,所以 A 的值就是空。
C。 變數的匯出(export):
在編譯程式時,我們會不斷地使用“make -C dir”切換到其他目錄,執行其他目錄裡的 Makefile。如果想讓某個變數的值在所有目錄中都可見,要把它 export 出來。
比如“CC = $(CROSS_COMPILE)gcc”,這個 CC 變量表示編譯器,在整個過程中都是一樣的。定義它之後,要使用“export CC”把它匯出來。
D。 Makefile 中可以使用 shell 命令:
比如:
TOPDIR := $(shell pwd)
這是個立即變數,TOPDIR 等於 shell 命令 pwd 的結果。
E。 在 Makefile 中怎麼放置第 1 個目標:
執行 make 命令時如果不指定目標,那麼它預設是去生成第 1 個目標。
所以“第 1 個目標”,位置很重要。有時候不太方便把第 1 個目標完整地放在檔案前面,這時可以在檔案的前面直接放置目標,在後面再完善它的依賴與命令。比如:
First_target: // 這句話放在前面
.... // 其他程式碼,比如 include 其他檔案得到後面的 xxx 變數
First_target : $(xxx) $(yyy) // 在檔案的後面再來完善
command
F。 假想目標:
我們的 Makefile 中有這樣的目標:
clean:
rm -f $(shell find -name “*。o”)
rm -f $(TARGET)
如果當前目錄下恰好有名為“clean”的檔案,那麼執行“make clean”時它就不會執行那些刪除命令。
這時我們需要把“clean”這個目標,設定為“假想目標”,這樣可以確保執行“make clean”時那些刪除命令肯定可以得到執行。
使用下面的語句把“clean”設定為假想目標:
。PHONY : clean
G。 常用的函式:
i. $(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
ii. $(wildcard pattern)
pattern 所列出的檔案是否存在,把存在的檔案都列出來。
例子:
src_files := $( wildcard *。c) // 最終 src_files 中列出了當前目錄下的所有。c 檔案
iii. $(filter pattern…,text)
把 text 中符合 pattern 格式的內容,filter(過濾)出來、留下來。
例子:
obj-y := a。o b。o c/ d/
DIR := $(filter %/, $(obj-y)) //結果為:c/ d/
iv. $(filter-out pattern…,text)
把 text 中符合 pattern 格式的內容,filter-out(過濾)出來、扔掉。
例子:
obj-y := a。o b。o c/ d/
DIR := $(filter-out %/, $(obj-y)) //結果為:a。o b。o
vi. $(patsubst pattern,replacement,text)
尋找
text’中符合格式
pattern’的字,用
replacement’替換它們。
pattern’和`replacement’中可以使用萬用字元。
比如:
subdir-y := c/ d/
subdir-y := $(patsubst %/, %, $(subdir-y)) // 結果為:c d
② 通用 Makefile 的設計思想
A。 在 Makefile 檔案中確定要編譯的檔案、目錄,比如:
obj-y += main。o
obj-y += a/
“Makefile”檔案總是被“Makefile。build”包含的。
B。 在 Makefile。build 中設定編譯規則,有 3 條編譯規則:
i。 怎麼編譯子目錄? 進入子目錄編譯:
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile。build
ii。 怎麼編譯當前目錄中的檔案?
%。o : %。c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
iii。 當前目錄下的。o 和子目錄下的 built-in。o 要打包起來:
built-in。o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
C。 頂層 Makefile 中把頂層目錄的 built-in。o 連結成 APP:
$(TARGET) : built-in。o
$(CC) $(LDFLAGS) -o $(TARGET) built-in。o
③ 情景演繹
本節下面的內容中不需要看,這是為寫書《嵌入式 Linux 應用開發完全手冊 升級版》而準備的。結合3。0 節看影片就可以了。
3。2 Makefile 規則
一個簡單的 Makefile 檔案包含一系列的“規則”,其樣式如下:
目標(target)…: 依賴(prerequiries)…
目標(target)通常是要生成的檔案的名稱,可以是可執行檔案或 OBJ 檔案,也可以是一個執行的動作名稱,諸如
clean
。
依賴是用來產生目標的材料(比如原始檔),一個目標經常有幾個依賴。
命令是生成目標時執行的動作,一個規則可以含有幾個命令,每個命令佔一行。
注意:每個命令列前面必須是一個 Tab 字元,即命令列第一個字元是 Tab。這是容易出錯的地方。
通常,如果一個依賴發生了變化,就需要規則呼叫命令以更新或建立目標。但是並非所有的目標都有依賴,例如,目標“clean”的作用是清除檔案,它沒有依賴。
規則一般是用於解釋怎樣和何時重建目標。make 首先呼叫命令處理依賴,進而才能建立或更新目標。
當然,一個規則也可以是用於解釋怎樣和何時執行一個動作,即列印提示資訊。
一個 Makefile 檔案可以包含規則以外的其他文字,但一個簡單的 Makefile 檔案僅僅需要包含規則。雖然真正的規則比這裡展示的例子複雜,但格式是完全一樣的。
對於上面的 Makefile,執行“make”命令時,僅當 hello。c 檔案比 hello 檔案新,才會執行命令“armlinux-gcc –o hello hello。c”生成可執行檔案 hello;如果還沒有 hello 檔案,這個命令也會執行。
執行“make clean”時,由於目標 clean 沒有依賴,它的命令“rm -f hello”將被強制執行。
3。3 Makefile 檔案裡的賦值方法
變數的定義語法形式如下:
immediate = deferred
immediate ?= deferred
immediate := immediate
immediate += deferred or immediate
define immediate
deferred
endef
在 GNU make 中對變數的賦值有兩種方式:延時變數、立即變數。區別在於它們的定義方式和擴充套件時的方式不同,前者在這個變數使用時才擴充套件開,意即當真正使用時這個變數的值才確定;後者在定義時它的值就已經確定了。使用
=
,
?=
定義或使用 define 指令定義的變數是延時變數;使用
:=
定義的變數是立即變數。需要注意的一點是,
?=
僅僅在變數還沒有定義的情況下有效,即
?=
被用來定義第一次出現的延時變數。
對於附加運算子
+=
,右邊變數如果在前面使用(:=)定義為立即變數則它也是立即變數,否則均為延時變數。
3。4 Makefile 常用函式
函式呼叫的格式如下:
$(function arguments)
這裡
function
是函式名,
arguments
是該函式的引數。引數和函式名之間是用空格或 Tab 隔開,
如果有多個引數,它們之間用逗號隔開。這些空格和逗號不是引數值的一部分。
核心的 Makefile 中用到大量的函式,現在介紹一些常用的。
3。4。1 字串替換和分析函式
(1)$(subst from,to,text)
在文字
text
中使用
to
替換每一處
from
。
比如:
$(subst ee,EE,feet on the street)
結果為
fEEt on the strEEt
。
(2)$(patsubst pattern,replacement,text)
尋找
text
中符合格式
pattern
的字,用
replacement
替換它們。
pattern
和
replacement
中可以使用萬用字元。
比如:
$(patsubst %。c,%。o,x。c。c bar。c)
結果為:
x。c。o bar。o
。
(3)$(strip string)
去掉前導和結尾空格,並將中間的多個空格壓縮為單個空格。
比如:
$(strip a b c )
結果為
a b c
。
(4)$(findstring find,in)
在字串
in
中搜尋
find
,如果找到,則返回值是
find
,否則返回值為空。
比如:
$(findstring a,a b c)
$(findstring a,b c)
將分別產生值
a
和``(空字串)。
(5)$(filter pattern…,text)
返回在
text
中由空格隔開且匹配格式
pattern。。。
的字,去除不符合格式
pattern。。。
的字。
比如:
$(filter %。c %。s,foo。c bar。c baz。s ugh。h)
結果為
foo。c bar。c baz。s
。
(6)$(filter-out pattern…,text)
返回在
text
中由空格隔開且不匹配格式
pattern。。。
的字,去除符合格式
pattern。。。
的字。它 是函式 filter 的反函式。
比如:
$(filter %。c %。s,foo。c bar。c baz。s ugh。h)
結果為
ugh。h
。
(7)$(sort list)
將
list
中的字按字母順序排序,並去掉重複的字。輸出由單個空格隔開的字的列表。
比如:
$(sort foo bar lose)
返回值是
bar foo lose
。
3。4。2 檔名函式
(1)$(dir names…)
抽取
names。。。
中每一個檔名的路徑部分,檔名的路徑部分包括從檔名的首字元到最後一個斜
槓(含斜槓)之前的一切字元。
比如:
$(dir src/foo。c hacks)
結果為
src/ 。/
。
(2)$(notdir names…)
抽取
names。。。
中每一個檔名中除路徑部分外一切字元(真正的檔名)。
比如:
$(notdir src/foo。c hacks)
結果為
foo。c hacks
。
(3)$(suffix names…)
抽取
names。。。
中每一個檔名的字尾。
比如:
$(suffix src/foo。c src-1。0/bar。c hacks)
結果為
。c 。c
。
(4)$(basename names…)
抽取
names。。。
中每一個檔名中除字尾外一切字元。
比如:
$(basename src/foo。c src-1。0/bar hacks)
結果為
src/foo src-1。0/bar hacks
。
(5)$(addsuffix suffix,names…)
引數
names。。。
是一系列的檔名,檔名之間用空格隔開;suffix 是一個字尾名。將 suffix(字尾) 的值附加在每一個獨立檔名的後面,完成後將檔名串聯起來,它們之間用單個空格隔開。
比如:
$(addsuffix 。c,foo bar)
結果為
foo。c bar。c
。
(6)$(addprefix prefix,names…)
引數
names
是一系列的檔名,檔名之間用空格隔開;prefix 是一個字首名。將 preffix(字首)
的值附加在每一個獨立檔名的前面,完成後將檔名串聯起來,它們之間用單個空格隔開。
比如:
$(addprefix src/,foo bar)
結果為
src/foo src/bar
。
(7)$(wildcard pattern)
引數
pattern
是一個檔名格式,包含有萬用字元(萬用字元和 shell 中的用法一樣)。函式 wildcard 的
結果是一列和格式匹配的且真實存在的檔案的名稱,檔名之間用一個空格隔開。
比如若當前目錄下有檔案 1。c、2。c、1。h、2。h,則:
c_src := $(wildcard *。c)
結果為
1。c 2。c
。
3。4。3 其他函式
(1)$(foreach var,list,text)
前兩個引數,
var
和
list
將首先擴充套件,注意最後一個引數
text
此時不擴充套件;接著,
list
擴充套件所得的每個字,都賦給
var
變數;然後
text
引用該變數進行擴充套件,因此
text
每次擴充套件都不 相同。
函式的結果是由空格隔開的
text
在
list
中多次擴充套件後,得到的新
list
,就是說:
text
多次擴充套件的字串聯起來,字與字之間由空格隔開,如此就產生了函式 foreach 的返回值。
下面是一個簡單的例子,將變數
files
的值設定為
dirs
中的所有目錄下的所有檔案的列表:
dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
這裡
text
是
$(wildcard $(dir)/*)
,它的擴充套件過程如下:
① 第一個賦給變數 dir 的值是
a
,擴充套件結果為
$(wildcard a/*)
;
② 第二個賦給變數 dir 的值是
b
,擴充套件結果為
$(wildcard b/*)
;
③ 第三個賦給變數 dir 的值是
c
,擴充套件結果為
$(wildcard c/*)
;
④ 如此繼續擴充套件。
這個例子和下面的例有共同的結果:
files := $(wildcard a/* b/* c/* d/*)
(2)$(if condition,then-part[,else-part])
首先把第一個引數‘condition’的前導空格、結尾空格去掉,然後擴充套件。如果擴充套件為非空字串,則條件‘condition’為‘真’;如果擴充套件為空字串,則條件‘condition’為‘假’。
如果條件‘condition’為‘真’,那麼計算第二個引數‘then-part’的值,並將該值作為整個函式 if的值。
如果條件‘condition’為‘假’,並且第三個引數存在,則計算第三個引數‘else-part’的值,並將該值作為整個函式 if 的值;如果第三個引數不存在,函式 if 將什麼也不計算,返回空值。
注意:僅能計算‘then-part’和‘else-part’二者之一,不能同時計算。這樣有可能產生副作用(例如函式 shell 的呼叫)。
(3)( o r i g i n v a r i a b l e ) 變 量 ‘ v a r i a b l e ’ 是 一 個 查 詢 變 量 的 名 稱 , 不 是 對 該 變 量 的 引 用 。 所 以 , 不 能 採 用 ‘ (origin variable) 變數‘variable’是一個查詢變數的名稱,不是對該變數的引用。所以,不能採用‘(
originvariable
)變數‘
variable
’是一個查詢變數的名稱,不是對該變數的引用。所以,不能採用‘’和圓括號的格式書寫該變數,當然,如果需要使用非常量的檔名,可以在檔名中使用變數引用。
函式 origin 的結果是一個字串,該字串變數是這樣定義的:
‘undefined’ :如果變數‘variable’從沒有定義;
‘default’ :變數‘variable’是預設定義;
‘environment’ :變數‘variable’作為環境變數定義,選項‘-e’沒有開啟;
‘environment override’:變數‘variable’作為環境變數定義,選項‘-e’已開啟;
‘file’ :變數‘variable’在 Makefile 中定義;
‘command line’ :變數‘variable’在命令列中定義;
‘override’ :變數‘variable’在Makefile 中用 override 指令定義;
‘automatic’ :變數‘variable’是自動變數
(4)$(shell command arguments)
函式 shell 是 make 與外部環境的通訊工具。函式 shell 的執行結果和在控制檯上執行‘command arguments’的結果相似。不過如果‘command arguments’的結果含有換行符(和回車符),則在函式 shell的返回結果中將把它們處理為單個空格,若返回結果最後是換行符(和回車符)則被去掉。
比如當前目錄下有檔案 1。c、2。c、1。h、2。h,則:
c_src := $(shell ls *。c)
結果為‘1。c 2。c’
《Makefile 介紹》這小節可以在閱讀核心、bootloader、應用程式的 Makefile 檔案時,作為手冊來查詢。下面以 options 程式的 Makefile 作為例子進行演示,Makefile 的內容如下:
File: Makefile
01 src := $(shell ls *。c)
02 objs := $(patsubst %。c,%。o,$(src))
03
04 test: $(objs)
05 gcc -o $@ $^
06
07 %。o:%。c
08 gcc -c -o $@ $<
09
10 clean:
11 rm -f test *。o
上述 Makefile 中@ 、 @、@、^、< 稱 為 自 動 變 量 。 <稱為自動變數。<稱為自動變數。@表示規則的目標檔名;表 示 所 有 依 賴 的 名 字 , 名 字 之 間 用 空 格 隔 開 ; ^表示所有依賴的名字,名字之間用空格隔開;表示所有依賴的名字,名字之間用空格隔開;<表示第一個依賴的檔名。‘%’是萬用字元,它和一個字串中任意個數的字元相匹配。
options 目錄下所有的檔案為 main。c,Makefile,sub。c 和 sub。h,下面一行行地分析:
① 第 1 行 src 變數的值為‘main。c sub。c’。
② 第 2 行 objs 變數的值為‘main。o sub。o’,是 src 變數經過 patsubst 函式處理後得到的。
③ 第 4 行實際上就是:
test : main。o sub。o
目標 test 的依賴有二:main。o 和 sub。o。開始時這兩個檔案還沒有生成,在執行生成 test 的命令之前先將 main。o、sub。o 作為目標查詢到合適的規則,以生成 main。o、sub。o。
④ 第 7、8 行就是用來生成 main。o、sub。o 的規則:
對於 main。o 這個規則就是:
main。o:main。c
gcc -c -o main。o main。c
對於 sub。o 這個規則就是:
sub。o:sub。c
gcc -c -o sub。o sub。c
這樣,test 的依賴 main。o 和 sub。o 就生成了。
⑤ 第 5 行的命令在生成 main。o、sub。o 後得以執行。
在 options 目錄下第一次執行 make 命令可以看到如下資訊:
gcc -c -o main。o main。c
gcc -c -o sub。o sub。c
gcc -o test main。o sub。o
然後修改 sub。c 檔案,再次執行 make 命令,可以看到如下資訊:
gcc -c -o sub。o sub。c
gcc -o test main。o sub。o
可見,只編譯了更新過的 sub。c 檔案,對 main。c 檔案不用再次編譯,節省了編譯的時間。
-end-
全文下載:
線上教學網站:
百問網開發板:
技術交流群(鴻蒙開發/Linux/嵌入式/驅動/資料下載)
QQ群:869222007
百問科技公眾號
百問科技服務號
韋東山嵌入式Linux隨身課堂