您當前的位置:首頁 > 歷史

Makefile 的使用

作者:由 韋東山嵌入式 發表于 歷史時間:2020-10-28

在 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)…

命令(command)

如果“依賴檔案”比“目標檔案”更加新,那麼執行“命令”來重新生成“目標檔案”。命令被執行的 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

③ 情景演繹

Makefile 的使用

本節下面的內容中不需要看,這是為寫書《嵌入式 Linux 應用開發完全手冊 升級版》而準備的。結合3。0 節看影片就可以了。

3。2 Makefile 規則

一個簡單的 Makefile 檔案包含一系列的“規則”,其樣式如下:

目標(target)…: 依賴(prerequiries)…

命令(command)

目標(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

百問科技公眾號

百問科技服務號

Makefile 的使用

韋東山嵌入式Linux隨身課堂

標簽: makefile  變數  sub  main  檔案