一、運用規則:
先來創建 simple 項目的兩個源程序:
foo.c
#include <stdio.h>
void foo()
{
printf("This is foo ()!\n");
}
main.c
#include <stdio.h>
extern void foo();
int main()
{
foo();
return 0;
}
對於項目,在編寫 Makefile 時,應該首先在腦子裏勾勒出 Makefile 的依賴關係。
對於 simple 可執行程序來說,上圖就是它的 “依賴樹” 。有了它,Makefile 的編寫就會變得輕鬆許多。
上圖展示了依賴關係與規則間的映射。
all : main.c foo.o
gcc -o simple main.c foo.o
main.o : main.c
gcc -o main.c -c main.c
foo.o : foo.c
gcc -o foo.o -c foo.c
clean :
rm simple main.o foo.o
運行結果(終端):
$ make
gcc -c main.c -o main.o
gcc -c foo.c -o foo.o
gcc -o simple main.o foo.o
$ make clean
rm simple main.o foo.o
在不修改源程序文件的情況下再執行一次 make ,出現以下結果:
$ make
gcc -o simple main.o foo.o
第二次編譯並沒有構建目標文件的動作,但有構建 simple 的動作。
- make 在第二次執行時,首先查找是否存在 all 目標,若有就不執行其後面的命令,若沒有,則執行。
- make 是通過文件的時間戳來判斷哪些文件需要重新編譯。 make 在分析一個規則以創建目標時,如果發現先決條件中文件的時間戳大於目標文件的時間戳,即目標文件比先決條件中的文件更新,就需要運行規則中命令重新構建目標。
假目標:
.PHONY : clean
simple : main.o foo.o
gcc -o simple main.o foo.o
main.o : main.c
gcc -o main.o -c main.c
foo.o : foo.c
gcc -o foo.o -c foo.c
clean:
rm -rf simple main.o foo.o
運行結果:
$ make clean
rm -rf simple main.o foo.o
採用 .PHONY 關鍵字聲明一個目標後,make 並不會將其當作一個文件來處理。由於假目標不與一個文件關聯,故無論 clean 文件是否存在,它都執行;
運用 “變量”
.PHONY : clean
CC = gcc
RM = rm
EXE = simple.exe
OBJS = main.o foo.o
$(EXE) : $(OBJS)
$(CC) -o $(EXE) $(OBJS)
main.o : main.c
$(CC) -o main.o -c main.c
foo.o : foo.c
$(CC) -o foo.o -c foo.c
clean :
$(RM) -rf $(EXE) $(OBJS)
引用變量需要採用 “$(變量名)” 或 “${變量名}” 的形式。
1.自動變量:
- $@ : 用於表示一個規則中的目標。當一個規則中有多個目標時,$@ 所指的是其中任何造成規則命令被運行的目標
- $^:表示規則中所有的先決條件
- $<:表示規則中第一個先決條件
.PHONY : all
all : first second third
@echo "\$$@ = $@"
@echo "$$^ = $^"
@echo "$$< = $<"
- first second third :
運行結果:
$make
$@ = all
$^ = first second third
$< = first
注意:
- 在 Makefile 中 “$” 具有特殊含義,如果採用 echo 輸出 “$” ,則必須用兩個連着的 “$”;
- “$@” 對於 Bash Shell 也有特殊含義,需要在 “$$@” 之前再加一個脫字符 “\” (引號不包括在內)
.PHONY : clean
CC = gcc
RM = rm
EXE = simple
OBJS = main.o foo.o
&(EXE) : $(OBJS)
$(CC) -o $@ $^
main.o : main.c
$(CC) -o $@ -c $^
foo.o : foo.c
$(CC) -o $@ -c $^
clean :
$(RM) -rf $(EXE) $(OBJS)
2.特殊變量:
.PHONY : all
all :
@echo "MAKE = $(MAKE)"
運行結果:
$make
MAKE = make
$(MAKE) 的值就是 “make” ,當在一個 Makefile 中運行另一個 Makefile 時,需要用到這個變量。
.PHONY : all clean
all clean :
$echo "\$$@ = $@"
$echo "MAKECMDGOALS = $MAKECMDGOALS"
$@ = all
MAKECMDGOALS =
$@ = all
MAKECMDGOALS = all
$@ = clean
MAKECMDGOALS = clean
$@ = all
MAKECMDGOALS = all
$@ = cleanl
MAKECMDGOALS = clean
從運行結果:
- MAKECMDGOALS 變量指的是用戶輸入的目標,當 make 不帶參數時,MAKECMDGOALS 爲空,即使前面我們所提到 make 不帶參數時 即生成默認目標。
- make 後可帶多個參數,表示構建多個目標,其將從左到右逐個構建目標。
3.變量的類別與賦值:
“=”
.PHONY : all
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all :
@echo $(foo)
運行結果
只用 “=” 符號定義的變量稱爲遞歸擴展變量,它的引用時遞歸的,容易出現死循環。
“:=”
.PHONY : all
x = foo
y = $(x) b
x = later
xx := foo
yy := &(xx) b
xx := later
all :
@echo "x = $(y), xx = $(yy)"
運行結果
用 “:=” 定義的變量稱爲簡單擴展變量,make 只對其展開一次。
實例:
.PHONY : all
obj = main.o foo.o bar.o
obj := $(obj) another.o
all :
@echo $(obj)
運行結果
main.o foo.o bar.o another.o
“?=”
.PHONY : all
foo = x
foo ?= y
bar ?= y
all :
@echo "foo = $(foo), bar = $(bar)"
運行結果
當用到 “?= ” 若變量被定義了,不改變其原值,若變量沒有被定義,則將右邊的值賦值給它。
“+=”
.PHONY : all
obj = main.o foo.o bar.o
obj += another.o
all :
@echo $(obj)
運行結果
main.o foo.o bar.o another.o
通過 “+=” 實現追加賦值,其效果與 “:=” 完全一樣。
4.變量的來源:
除了在 Makefile 中直接定義變量外,還可以有其他幾種方式是 make 獲得變量值
- 對於自動變量,它的值是每一條規則根據上下文自動獲取。
- 在運行 make 時, 可以使用 “make bar = x” 的形式對變量 bar 進行賦值。
- 變量也可以來自於 Shell 環境, 採用 export 命令定義變量,如(“?=”):
5.高級變量的引用:
.PHONY : all
foo = a.c b.c c.c
bar := $(foo : .c = .o)
all :
@echo "bar = $(bar)"
運行結果
從運行結果:
- bar 變量中的文件名從 .c 後綴變成了 .o
- 這種功能可用後面介紹的 patsubst 函數
6.避免變量被覆蓋:
.PHONY : all
override foo = x
all :
@echo "foo = $(foo)"
運行結果
從運行結果:
根據上面的 “4.變量的來源” 所提, 變量的值可以有 make 來獲取,若我們在設計 Makefile 時不希望它的值自定義後再被修改,可在變量前加上 “override“ 來進行保護。
二、採用精簡規則
.PHONY : clean
CC = gcc
RM = rm
EXE = simple.exe
OBJS = main.o foo.o
$(EXE) : $(OBJS)
$(CC) -o $@ $^
%.o : %.c
$(CC) -o $@ -c $^
clean :
$(RM) -rf $(EXE) $(OBJS)
參考文獻:《專業嵌入式軟件開發》李雲·著
2016年7月2日,星期六