嵌入式之Makefile學習筆記

Donke_Dong發表於2020-10-15

注: 以下內容來自朱老師物聯網大講堂課件

1. 為什麼需要Makefile

工程專案中c檔案太多管理不方便,因此用Makefile來做專案管理,方便編譯連結過程。
在一個正式的軟體專案中,由很多個.c和.h檔案構成,此時如果直接在命令列編譯,就會像這樣:gcc a.c b.c c.c d.c e.c f.c g.c -o exe 每次編譯都要輸入一堆東西很麻煩,這個問題嚴重影響工作效率,所以我們就使用Makefile來進行管理

makefile帶來的好處就是——“自動化編譯”,一旦寫好,只需要一個make命令,整個工程完全自動編譯,極大的提高了軟體開發的效率。make是一個命令工具,是一個解釋makefile中指令的命令工具,一般來說,大多數的IDE都有這個命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可見,makefile都成為了一種在工程方面的編譯方法。

2. 一個簡單的Makefile示例

在這裡插入圖片描述

2.1 Makefile中的一些基本概念

目標:目標定格寫(如上例:led.bin),後面是冒號(冒號後面是依賴),目標就是我們要去make xxx的那個xxx,就是我們最終要生成的東西。

依賴:依賴是用來產生目標的原材料,也就是(目標:)後面的部分。

命令命令前面一定是Tab,不能是空格,也不能說多個空格。命令就是要生成那個目標需要做的動作。

Makefile中的註釋用#
Makefile中註釋使用#,和shell一樣。

2.2 Makefile的基本工作原理

其一,當我們執行 make xx 的時候,Makefile會自動執行xx這個目標下面的命令語句。
其二,當我們make xx的時候,是否執行命令是取決於依賴的。依賴如果成立就會執行命令,否則不執行。
其三,我們直接執行make 和make 第一個目標  效果是一樣的。(第一個目標其實就是預設目標)

make的依賴性決定了make會一層又一層地去找檔案的依賴關係,直到最終編譯出第一個目標檔案。

2.3 Makefile的執行過程

在預設的方式下,也就是我們只輸入make命令。那麼,

  1. make會在當前目錄下找名字叫“Makefile”或“makefile”的檔案。
  2. 如果找到,它會找檔案中的第一個目標檔案(target),在上面的例子中,他會找到“led.bin”這個檔案,並把這個檔案作為最終的目標檔案。
  3. 如果led.bin檔案不存在,或是led.bin所依賴的後面的 .o 檔案的檔案修改時間要比led.bin這個檔案新,那麼,他就會執行後面所定義的命令來生成led.bin這個檔案。
  4. 如果led.bin所依賴的.o檔案也存在,那麼make會在當前檔案中找目標為.o檔案的依賴性,如果找到則再根據那一個規則生成.o檔案。(這有點像一個堆疊的過程)
  5. 當然,你的C檔案和H檔案是存在的啦,於是make會生成 .o 檔案,然後再用 .o 檔案宣告make的終極任務,也就是執行檔案led.bin了。

3.一個稍微複雜的Makefile示例

在這裡插入圖片描述

CC		= arm-linux-gcc
LD 		= arm-linux-ld
OBJCOPY	= arm-linux-objcopy
OBJDUMP	= arm-linux-objdump
AR		= arm-linux-ar

INCDIR	:= $(shell pwd)

# C前處理器的flag,flag就是編譯器可選的選項
CPPFLAGS	:= -nostdlib -nostdinc -I$(INCDIR)/include

# C編譯器的flag
CFLAGS		:= -Wall -O2 -fno-builtin

#匯出這些變數到全域性,其實就是給子資料夾下面的Makefile使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGS

objs := start.o sdram_init.o led.o uart.o main.o

objs += lib/libc.a

uart.bin: $(objs)
	$(LD) -Tlink.lds -o uart.elf $^
	$(OBJCOPY) -O binary uart.elf uart.bin
	$(OBJDUMP) -D uart.elf > uart_elf.dis
	gcc mkv210_image.c -o mkx210
	./mkx210 uart.bin 210.bin

lib/libc.a:
	cd lib;	make;	cd ..
	
%.o : %.S
	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c

%.o : %.c
	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
.PHONY:clean
clean:
	rm *.o *.elf *.bin *.dis mkx210 -f
	cd lib; make clean; cd ..

3.1 Makefile中的萬用字元和Makefile自動推導

(1)%
%是Makefile中的萬用字元,代表一個或幾個字母。也就是說%.o就代表所有以.o為結尾的檔案。
(2)"~"
波浪號(“~ ”)字元在檔名中也有比較特殊的用途。如果是“ ~/test ”,這就表示當前使用者的$HOME目錄下的test目錄。而“~hchen/test”則表示使用者hchen的宿主目錄下的test目錄。(這些都是Unix下的小知識了,make也支援)而在Windows或是MS-DOS下,使用者沒有宿主目錄,那麼波浪號所指的目錄則根據環境變數“HOME”而定。
(3)" * "
萬用字元代替了你一系列的檔案,如“ * .c”表示所以字尾為c的檔案。一個需要我們注意的是,如果我們的檔名中有萬用字元,如:“ * ”,那麼可以用轉義字元“\”,如“\ * ”來表示真實的“*”字元,而不是任意長度的字串。

所謂自動推導其實就是Makefile的規則。當Makefile需要某一個目標時,他會把這個目標去套規則說明,一旦套上了某個規則說明,則Makefile會試圖尋找這個規則中的依賴,如果能找到則會執行這個規則用依賴生成目標。

3.2 Makefile中定義和使用變數

Makefile中定義和使用變數,和shell指令碼中非常相似。相似是說:都沒有變數型別,直接定義使用,引用變數時用$(CC)

3.3 偽目標(.PHONY)

(1)偽目標意思是這個目標本身不代表一個檔案,執行這個目標不是為了得到某個檔案或東西,而是單純為了執行這個目標下面的命令。
(2)偽目標一般都沒有依賴,因為執行偽目標就是為了執行目標下面的命令。既然一定要執行命令了那就不必加依賴,因為不加依賴意思就是無條件執行。
(3)偽目標可以直接寫,不影響使用;但是有時候為了明確宣告這個目標是偽目標會在偽目標的前面用.PHONY來明確宣告它是偽目標。

.PHONY:clean
clean:
	rm *.o *.elf *.bin *.dis mkx210 -f
	cd lib; make clean; cd ..

3.4 Makfile中引用其他Makefile(include指令)

(1)有時候Makefile總體比較複雜,因此分成好幾個Makefile來寫。然後在主Makefile中引用其他的,用include指令來引用。
在Makefile使用include關鍵字可以把別的Makefile包含進來,這很像C語言的#include,被包含的檔案會原模原樣的放在當前檔案的包含位置。include的語法是:
include< filename >filename可以是當前作業系統Shell的檔案模式(可以保含路徑和萬用字元)

注意:在include前面可以有一些空字元,但是絕不能是[Tab]鍵開始。include和可以用一個或多個空格隔開。
例如:

include $(srctree)/arch/$(SRCARCH)/Makefile

3.5 命令前面的@用來靜默執行

(1)在makefile的命令列中前面的@表示靜默執行。
(2)Makefile中預設情況下在執行一行命令前會先把這行命令給列印出來,然後再執行這行命令。
(3)如果你不想看到命令本身,只想看到命令執行就靜默執行即可。
未新增@

all:
        echo "hello"

執行結果

root@ubuntu:/mnt/hgfs/winshare/uboot_practice# make
echo "hello"
hello
root@ubuntu:/mnt/hgfs/winshare/uboot_practice# 

新增@

all:
        @echo "hello"                                      

執行結果

root@ubuntu:/mnt/hgfs/winshare/uboot_practice# make
hello
root@ubuntu:/mnt/hgfs/winshare/uboot_practice# 

3.6 Makefile中幾種變數賦值運算子

(1)= 最簡單的賦值
(2):= 一般也是賦值
以上這兩個大部分情況下效果是一樣的,但是有時候不一樣。
用=賦值的變數,在被解析時他的值取決於最後一次賦值時的值,所以你看變數引用的值時不能只往前面看,還要往後面看。
用:=來賦值的,則是就地直接解析,只用往前看即可。

=賦值練習

var1=abc
var2=$(var1)de
var1=gh

all:
        @echo $(var1)
        @echo $(var2)

執行結果

root@ubuntu:/mnt/hgfs/winshare/uboot_practice# make
gh
ghde
root@ubuntu:/mnt/hgfs/winshare/uboot_practice# 

:= 練習


var1=abc
var2:=$(var1)de
var1=gh

all:
        @echo $(var1)
        @echo $(var2)

執行結果

root@ubuntu:/mnt/hgfs/winshare/uboot_practice# make
gh
abcde
root@ubuntu:/mnt/hgfs/winshare/uboot_practice# 

(3)?= 如果變數前面並沒有賦值過則執行這條賦值,如果前面已經賦值過了則本行被忽略。(實驗可以看出:所謂的沒有賦值過其實就是這個變數沒有被定義過)

var = Donke
var ?= Dong

all:
        @echo $(var)

執行結果:Donke

var ?= Dong

all:
        @echo $(var)

執行結果:Dong

(4)+= 用來給一個已經賦值的變數接續賦值,意思就是把這次的值加到原來的值的後面,有點類似於strcat。(在shell makefile等檔案中,可以認為所有變數都是字串,+=就相當於給字串stcat接續內容)(注意一個細節,+=續接的內容和原來的內容之間會自動加一個空格隔開)

var = Donke
var += Dong

all:
        @echo $(var)

輸出結果:Donke Dong

注意:Makefile中並不要求賦值運算子兩邊一定要有空格或者無空格,這一點比shell的格式要求要鬆一些。

3.7 Makefile的環境變數

(1)makefile中用export匯出的就是環境變數。一般情況下要求環境變數名用大寫,普通變數名用小寫。

#匯出這些變數到全域性,其實就是給子資料夾下面的Makefile使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGS

(2)環境變數和普通變數不同,可以這樣理解:環境變數類似於整個工程中所有Makefile之間可以共享的全域性變數,而普通變數只是當前本Makefile中使用的區域性變數。所以要注意:定義了一個環境變數會影響到工程中別的Makefile檔案,因此要小心。
(3)Makefile中可能有一些環境變數可能是makefile本身自己定義的內部的環境變數或者是當前的執行環境提供的環境變數(譬如我們在make執行時給makefile傳參。make CC=arm-linux-gcc,其實就是給當前Makefile傳了一個環境變數CC,值是arm-linux-gcc。我們在make時給makefile傳的環境變數值優先順序最高的,可以覆蓋makefile中的賦值)。這就好像C語言中編譯器預定義的巨集__LINE__ __FUNCTION__等一樣。

3.8 Makefile中使用萬用字元

(1)* 若干個任意字元
(2)? 1個?表示一個任意字元,n個?表示輸出n個字元
(3)[] 將[]中的字元依次去和外面的結合匹配,如果有則輸出,無則不輸出

練習

all:
        echo *.c
        echo ?.c
        echo ??.c
        echo *.h
        echo [abcdef].c                                               

執行結果:

root@ubuntu:/mnt/hgfs/winshare/uboot_practice/2.14.makefile# make
echo *.c
12.c 234.c a.c b.c d.c
echo ?.c
a.c b.c d.c
echo ??.c
12.c
echo *.h
a.h b.h
echo [abcdef].c
a.c b.c d.c
root@ubuntu:/mnt/hgfs/winshare/uboot_practice/2.14.makefile# 

(4)% 也是萬用字元,表示任意多個字元,和 * 很相似,但是%一般只用於規則描述中,又叫做規則萬用字元。

%.o : %.S
	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c

%.o : %.c
	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c

3.9 Makefile的自動變數

(1)為什麼使用自動變數。在有些情況下檔案集合中檔案非常多,描述的時候很麻煩,所以我們Makefile就用一些特殊的符號來替代符合某種條件的檔案集,這就形成了自動變數。
(2)自動變數的含義:預定義的特殊意義的符號。就類似於C語言編譯器中預製的那些巨集__FILE__一樣。
(3)常見自動變數:
$@ 規則的目標檔名
$< 規則的依賴檔名
$^ 依賴的檔案集合
在這裡插入圖片描述

%.o : %.S
	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c

%.o : %.c
	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< 

make使用變數CC來定義編譯器,並且傳遞變數CFLAGS(編譯器引數)、CPPFLAGS(C語言前處理器引數)、TARGET_ARCH(目標機器的結構定義)給編譯器然後加上引數-c,後面跟變數$ <(第一個依賴檔名),然後是引數-o加變數$@(目標檔名)。綜上所述,一個C編譯的具體命令將會是:

$ {CC} $ {CFLAGS} $ {CPPFLAGS} $ {TARGET_ARCH} –c $< -o $@

更多Makefile 資料
《跟我一起學makefile》
《葵花寶典》

相關文章