轉自:https://www.linuxidc.com/Linux/2018-09/154071.htm
當你需要在一些源文件改變后運行或更新一個任務時,通常會用到 make 工具。make 工具需要讀取一個 Makefile(或 makefile)文件,在該文件中定義了一系列需要執行的任務。你可以使用 make 來將源代碼編譯為可執行程序。大部分開源項目會使用 make 來實現最終的二進制文件的編譯,然后使用 make install 命令來執行安裝。
本文將通過一些基礎和進階的示例來展示 make 和 Makefile 的使用方法。在開始前,請確保你的系統中安裝了 make。
基礎示例
依然從打印 “Hello World” 開始。首先創建一個名字為 myproject 的目錄,目錄下新建 Makefile 文件,文件內容為:
say_hello:echo"Hello World"
在 myproject 目錄下執行 make,會有如下輸出:
$ makeecho"Hello World"HelloWorld
在上面的例子中,“say_hello” 類似於其他編程語言中的函數名。這被稱之為目標target。在該目標之后的是預置條件或依賴。為了簡單起見,我們在這個示例中沒有定義預置條件。echo ‘Hello World' 命令被稱為步驟recipe。這些步驟基於預置條件來實現目標。目標、預置條件和步驟共同構成一個規則。
總結一下,一個典型的規則的語法為:
目標:預置條件<TAB>步驟
作為示例,目標可以是一個基於預置條件(源代碼)的二進制文件。另一方面,預置條件也可以是依賴其他預置條件的目標。
final_target: sub_target final_target.cRecipe_to_create_final_targetsub_target: sub_target.cRecipe_to_create_sub_target
目標並不要求是一個文件,也可以只是步驟的名字,就如我們的例子中一樣。我們稱之為“偽目標”。
再回到上面的示例中,當 make 被執行時,整條指令 echo "Hello World" 都被顯示出來,之后才是真正的執行結果。如果不希望指令本身被打印處理,需要在 echo 前添加 @。
say_hello:
@echo "Hello World"
重新運行 make,將會只有如下輸出:
$ makeHelloWorld
接下來在 Makefile 中添加如下偽目標:generate 和 clean:
say_hello:@echo"Hello World"generate:@echo"Creating empty text files..."touchfile-{1..10}.txtclean:@echo"Cleaning up..."rm*.txt
隨后當我們運行 make 時,只有 say_hello 這個目標被執行。這是因為Makefile 中的第一個目標為默認目標。通常情況下會調用默認目標,這就是你在大多數項目中看到 all 作為第一個目標而出現。all 負責來調用它他的目標。我們可以通過 .DEFAULT_GOAL 這個特殊的偽目標來覆蓋掉默認的行為。
在 Makefile 文件開頭增加 .DEFAULT_GOAL:
.DEFAULT_GOAL := generate
make 會將 generate 作為默認目標:
$ makeCreatingempty text files...touchfile-{1..10}.txt
顧名思義,.DEFAULT_GOAL 偽目標僅能定義一個目標。這就是為什么很多 Makefile 會包括 all 這個目標,這樣可以調用多個目標。
下面刪除掉 .DEFAULT_GOAL,增加 all 目標:
all: say_hello generatesay_hello:@echo"Hello World"generate:@echo"Creating empty text files..."touchfile-{1..10}.txtclean:@echo"Cleaning up..."rm*.txt
運行之前,我們再增加一些特殊的偽目標。.PHONY 用來定義這些不是文件的目標。make 會默認調用這些偽目標下的步驟,而不去檢查文件名是否存在或最后修改日期。完整的 Makefile 如下:
.PHONY: all say_hello generate cleanall: say_hello generatesay_hello:@echo"Hello World"generate:@echo"Creating empty text files..."touchfile-{1..10}.txtclean:@echo"Cleaning up..."rm*.txt
make 命令會調用 say_hello 和 generate:
$ makeHelloWorldCreatingempty text files...touchfile-{1..10}.txt
clean 不應該被放入 all 中,或者被放入第一個目標中。clean 應當在需要清理時手動調用,調用方法為 make clean。
$ make cleanCleaning up...rm*.txt
現在你應該已經對 Makefile 有了基礎的了解,接下來我們看一些進階的示例。
進階示例
變量
在之前的實例中,大部分目標和預置條件是已經固定了的,但在實際項目中,它們通常用變量和模式來代替。
定義變量最簡單的方式是使用 = 操作符。例如,將命令 gcc 賦值給變量 CC:
CC =gcc
這被稱為遞歸擴展變量,用於如下所示的規則中:
hello: hello.c${CC} hello.c -o hello
你可能已經想到了,這些步驟將會在傳遞給終端時展開為:
gcc hello.c -o hello
${CC} 和 $(CC) 都能對 gcc 進行引用。但如果一個變量嘗試將它本身賦值給自己,將會造成死循環。讓我們驗證一下:
CC =gccCC = ${CC}all:@echo ${CC}
此時運行 make 會導致:
$ makeMakefile:8:***Recursive variable 'CC' references itself (eventually). Stop.
為了避免這種情況發生,可以使用 := 操作符(這被稱為簡單擴展變量)。以下代碼不會造成上述問題:
CC :=gccCC := ${CC}all:@echo ${CC}
模式和函數
下面的 Makefile 使用了變量、模式和函數來實現所有 C 代碼的編譯。我們來逐行分析下:
#Usage:#make # compile all binary#make clean # remove ALL binaries and objects.PHONY = all cleanCC =gcc # compiler to useLINKERFLAG =-lmSRCS := $(wildcard *.c)BINS := $(SRCS:%.c=%)all: ${BINS}%:%.o@echo"Checking.."${CC} ${LINKERFLAG} $<-o $@%.o:%.c@echo"Creating object.."${CC}-c $<clean:@echo"Cleaning up..."rm-rvf *.o ${BINS}
- 以
#開頭的行是評論。 .PHONY = all clean行定義了all和clean兩個偽目標。- 變量
LINKERFLAG定義了在步驟中gcc命令需要用到的參數。 SRCS := $(wildcard *.c):$(wildcard pattern)是與文件名相關的一個函數。在本示例中,所有 “.c”后綴的文件會被存入SRCS變量。BINS := $(SRCS:%.c=%):這被稱為替代引用。本例中,如果SRCS的值為'foo.c bar.c',則BINS的值為'foo bar'。all: ${BINS}行:偽目標all調用${BINS}變量中的所有值作為子目標。-
規則:
%:%.o@echo"Checking.."${CC} ${LINKERFLAG} $<-o $@
下面通過一個示例來理解這條規則。假定
foo是變量${BINS}中的一個值。%會匹配到foo(%匹配任意一個目標)。下面是規則展開后的內容:foo: foo.o@echo"Checking.."gcc-lm foo.o -o foo
如上所示,
%被foo替換掉了。$<被foo.o替換掉。$<用於匹配預置條件,$@匹配目標。對${BINS}中的每個值,這條規則都會被調用一遍。 -
規則:
%.o:%.c@echo"Creating object.."${CC}-c $<
之前規則中的每個預置條件在這條規則中都會都被作為一個目標。下面是展開后的內容:
foo.o: foo.c@echo"Creating object.."gcc-c foo.c
-
最后,在
clean目標中,所有的二進制文件和編譯文件將被刪除。
下面是重寫后的 Makefile,該文件應該被放置在一個有 foo.c 文件的目錄下:
#Usage:#make # compile all binary#make clean # remove ALL binaries and objects.PHONY = all cleanCC =gcc # compiler to useLINKERFLAG =-lmSRCS := foo.cBINS := fooall: foofoo: foo.o@echo"Checking.."gcc-lm foo.o -o foofoo.o: foo.c@echo"Creating object.."gcc-c foo.cclean:@echo"Cleaning up..."rm-rvf foo.o foo
