Makefile语法基础介绍
在Linux下,make是一個命令工具,是一個解釋Makefile中指令的命令工具。make命令執行時,需要一個Makefile文件,以告訴make命令需要怎么樣去編譯和鏈接程序。
make如何工作:在默認的方式下,只輸入make命令,那么:
(1)、make會在當前目錄下找名字叫”Makefile”或”makefile”的文件;
(2)、如果找到,它會找文件中的第一個目標文件(target),并把這個文件作為最終的目標文件;
(3)、如果目標文件不存在,或是目標文件所依賴的后面的.o文件的修改時間要比這個目標文件新,那么,它就會執行后面所定義的命令來生成目標文件;
(4)、如果目標文件所依賴的.o文件也不存在,那么make會在當前文件中找目標為.o文件的依賴性,如果找到則再根據那一個規則生成.o文件;
(5)、當然,對應的.c和.h文件是存在的,于是make會生成.o文件,然后再用.o文件生成make的終極任務,也就是執行文件(目標文件)了。
Makefile里主要包含了5部分:顯示規則、隱晦規則、變量定義、文件指示和注釋
(1)、顯示規則:顯示規則說明了,如何生成一個或多個的目標文件。這是由Makefile的書寫者明顯指出要生成的文件,文件的依賴文件,生成的命令。
(2)、隱晦規則:由于make有自動推導的功能,所以隱晦規則可以讓我們比較粗糙地簡略地書寫Makefile,這是由make所支持的。
(3)、變量定義:在Makefile中要定義一系列的變量,變量一般都是字符串。當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。
(4)、文件指示:其包括了三個部分,一個是在一個Makefile中引用另一個Makefile,就像C語言中的include一樣,另一個是指根據某些情況指定Makefile中的有效部分,就像C語言中的預編譯#if一樣,還有就是定義一個多行的命令。
(5)、注釋:Makefile中只有行注釋,和UNIX的Shell腳本一樣,其注釋是用”#”字符,這個就像C/C++中的”//”一樣。如果要在你的Makefile中使用”#”字符,可以用反斜框進行轉義,如”\#”。
在Makefile中的命令,必須要以[Tab]鍵開始。
Makefile的文件名:默認的情況下,make命令會在當前目錄下按順序找尋文件名為”GNUmakefile”、”makefile”、”Makefile”的文件,找到了解釋這個文件。在這三個文件名中,最好使用”Makefile”這個文件名。最好不要用”GNUmakefile”,這個文件是GNU的make識別的。有另外一些make只對全小寫的”makefile”文件名敏感,但是基本上來說,大多數的make都支持”makefile”和”Makefile”這兩種默認文件名。
當然,你可以使用別的文件名來書寫Makefile,比如”Make.Linux”等,如果要指定特定的Makefile,你可以使用make的”-f”和”--file”參數,如make? -f?Make.Linux或make? --file? Make.Linux.
引用其它的Makefile:在Makefile使用include關鍵字可以把別的Makefile包含進來,被包含的文件會原模原樣的放在當前文件的包含位置。
include的語法是:include filename
filename可以是當前操作系統Shell的文件模式(可以包含路徑和通配符)。
在include前面可以有一些空字符,但是決不能是[Tab]鍵開始。include和filename可以用一個或多個空格隔開。
make命令開始時,會找尋include所指出的其它Makefile,并把其內容安置在當前的位置。如果文件都沒有指定絕對路徑或是相對路徑的話,make會在當前目錄下首先尋找,如果當前目錄下沒有找到,那么,make還會再下面的幾個目錄下找:(1)、如果make執行時,有”-I”或”--include-dir”參數,那么make就會在這個參數所指定的目錄下去尋找;(2)、如果目錄<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的話,make也會去找。
如果有文件沒有找到的話,make會生成一條警告信息,但不會馬上出現致命錯誤。它會繼續載入其它的文件,一旦完成makefile的讀取,make會再重試這些沒有找到,或是不能讀取的文件,如果還是不行,make才會出現一條致命信息。如果你想讓make不理那些無法讀取的文件,而繼續執行,你可以在include前加一個減號”-”,如-include filename,其表示,無論include過程中出現什么錯誤,都不要報錯繼續執行。和其它版本make兼容的相關命令是sinclude,其作用和這一個是一樣的。
環境變量MAKEFILES:如果你的當前環境中定義了環境變量MAKEFILES,那么,make會把這個變量中的值做一個類似于include的動作。這個變量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,從這個環境變量中引入的Makefile的”目標”不會起作用,如果環境變量中定義的文件發現錯誤,make也不會理。建議,不要使用這個環境變量,因為只要這個變量一被定義,那么當你使用make時,所有的Makefile都會受到它的影響。
GNU的make工作時的執行步驟:
(1)、讀入所有的Makefile;
(2)、讀入被include的其它Makefile;
(3)、初始化文件中的變量;
(4)、推導隱晦規則,并分析所有規則;
(5)、為所有的目標文件創建依賴關系鏈;
(6)、根據依賴關系,決定哪些目標要重新生成;
(7)、執行生成命令。
Makefile文件書寫規則:
?
targets: prerequisites
command
... ...
或者:
?
?
targets: prerequisites ; command
command
... ...
?
targets是文件名,以空格分開,可以使用通配符。一般來說,目標基本上是一個文件,但也有可能是多個文件。
command是命令行,如果其不與”targets: prerequisites”在一行,那么,必須以[Tab]鍵開頭,如果和prerequisites在一行,那么可以用分號作為分隔。
prerequisites也就是目標所依賴的文件(或依賴目標)。如果其中的某個文件要比目標文件要新,那么,目標就被認為是”過時的”,被認為是需要重新生成的。
如果命令太長,可以使用反斜框(‘\’)作為換行符。make對一行上有多少個字符沒有限制。
在規則中使用通配符:make支持三種通配符:”*”、”?”、”[…]”。這和Unix的B-Shell是相同的。
波浪號(“~”)字符在文件名中也有比較特殊的用途。如果是”~/test”,這就表示當前用戶的$HOME目錄下的test目錄。而”~spring/test”則表示用戶spring的宿主目錄下的test目錄。
文件搜尋:Makefile文件中的特殊變量”VPATH”可以實現文件搜尋,如果沒有指明這個變量,make只會在當前的目錄中去找尋依賴文件和目標文件。如果定義了這個變量,那么,make就會在當前目錄找不到的情況下,到所指定的目錄中去找尋文件。
另一個設置文件搜索路徑的方法是使用make的”vpath”關鍵字(注意,它是全小寫的),這不是變量,這是一個make的關鍵字,它和VPATH變量很類似。它可以指定不同的文件在不同的搜索目錄中。
偽目標:”偽目標”并不是一個文件,只是一個標簽,由于”偽目標”不是文件,所以make無法生成它的依賴關系和決定它是否要執行。我們只有通過顯示地指明這個”目標”才能讓其生效。當然,”偽目標”的取名不能和文件名重名,不然其就失去了”偽目標”的意義了。為了避免和文件重名的這種情況,我們可以使用一個特殊的標記”.PHONY”來顯示地指明一個目標是”偽目標”,向make說明,不管是否有這個文件,這個目標就是”偽目標”。
偽目標一般沒有依賴的文件。但是,我們也可以為偽目標指定所依賴的文件。偽目標同樣可以作為”默認目標”,只要將其放在第一個。
多目標:Makefile的規則中目標可以不止一個,其支持多目標。
靜態模式:可以更加容易地定義多目標的規則,可以讓我們的規則變得更加的有彈性和靈活。
自動生成依賴性:大多數的C/C++編譯器都支持一個”-M”的選項,即自動找尋源文件中包含的頭文件,并生成一個依賴關系。如果你使用GNU的C/C++編譯器,你得用”-MM”參數,不然,”-M”參數會把一些標準庫的頭文件也包含進來。
書寫命令:每條規則中的命令和操作系統Shell的命令行是一致的。make會按順序一條一條的執行命令,每條命令的開頭必須以[Tab]鍵開頭,除非,命令是緊跟在依賴規則后面的分號后的。在命令行之間的空格或是空行會被忽略,但是如果該空格或空行是以[Tab]鍵開頭的,那么make會認為其是一個空命令。
make的命令默認是被”/bin/sh”----UNIX的標準Shell解釋執行的。除非你特別指定一個其它的Shell。
顯示命令:通常,make會把其要執行的命令行在命令執行前輸出到屏幕上。當我們用”@”字符在命令行前,那么這個命令將不被make顯示出來。如果make執行時,帶入make參數”-n”或”--just-print”,那么其只是顯示命令,但不會執行命令,這個功能很有利于我們調試我們的Makefile,看看我們書寫的命令是執行起來是什么樣子的或是什么順序的。
命令執行:當依賴目標新于目標時,也就是當規則的目標需要被更新時,make會一條一條的執行其后的命令。需要注意的是,如果你要讓上一條命令的結果應用在下一條命令時,你應該使用分號分隔這兩條命令。
?
#如果要讓上一條命令的結果應用于下一條命令時,
#應該使用分號分隔這兩條命令
#注意以下兩者的區別exec:@cd /home/spring; pwd #希望得到的結果@cd /home/springpwd
?
?
?
make一般是使用環境變量SHELL中所定義的系統Shell來執行命令,默認情況下使用UNIX的標準Shell----/bin/sh來執行命令。
忽略命令的出錯,我們可以在Makefile的命令行前加一個減號”-”(在Tab鍵之后),標記為不管命令出不出錯都認為是成功的。還有一個全局的辦法是,給make加上”-i”或是”--ignore-errors”參數,那么,Makefile中所有命令都會忽略錯誤。
嵌套執行make:可以通過export命令傳遞變量到下級Makefile中。需要注意的是,有兩個變量,一個是SHELL,一個是MAKEFLAGS,這兩個變量不管你是否export,其總是要傳遞到下層Makefile中,特別是MAKEFLAGS變量,其中包含了make的參數信息。但是make命令中有幾個參數并不往下傳遞,它們是”-C”、”-f”、”-h”、”-o”和”-W”。”-w”或是”--print-directory”會在make的過程中輸出一些信息,讓你看到目前的工作目錄。
定義命令包:如果Makefile中出現一些相同命令序列,那么我們可以為這些相同的命令序列定義一個變量。定義這種變量序列的語法以”define”開始,以”endef”結束。
使用變量:在Makefile中定義的變量,就像是C/C++語言中的宏一樣,它代表了一個文本字串,在Makefile中執行的時候其會自動原模原樣地展開在所使用的地方。其與C/C++所不同的是,你可以在Makefile中改變其值。在Makefile中,變量可以使用在”目標”、”依賴目標”、”命令”或是Makefile的其它部分中。
變量的名字可以包含字符、數字、下劃線(可以是數字開頭),但不應該含有”:”、”#”、”=”或是空字符(空格、回車等)。變量是大小寫敏感的。傳統的Makefile的變量名是全大寫的命名方式,但推薦使用大小寫搭配的變量名,這樣可以避免和系統的變量沖突,而發生意外的事情。
變量的基礎:變量在聲明時需要給予初值,而在使用時,需要給在變量名前加上”$”,但最好用小括號”()”或是大括號”{}”把變量給包起來。如果你要使用真實的”$”字符,那么你需要用”$$”來表示。
變量中的變量:在定義變量的值時,我們可以使用其它變量來構造變量的值,在Makefile中有兩種方式來用變量定義變量的值:(1)、簡單的使用”=”號,在”=”左側是變量,右側是變量的值,右側變量的值可以定義在文件的任何一處,也就是說,右側中的變量不一定非要是已定義好的值,其也可以使用后面定義的值;(2)、使用”:=”操作符,這種方法,前面的變量不能使用后面的變量,只能使用前面已經定義好了的變量。
?
#變量中的變量#使用“=”,右側的變量不一定非要是已定義好的值,其也可以使用后面定義的值
foo = $(bar) hello
bar = $(ugn)
ugn = spring#使用":="操作符,前面的變量不能使用后面的變量,
#只能使用前面已經定義好了變量,此時y的值為fun
y := $(x)fun
x := testexec:echo $(foo) #spring helloecho $(y) #fun
?
#定義一個變量其值是空格#但是這個例子好像并沒有起到作用????
nullstring :=
space := $(nullstring)#end of the line
out := spring$(space)spring#以下這個例子可以獲得預期的結果
dir := /bin/sh #directory
path := $(dir)/fileexec:echo $(out) #springspringecho $(path) #/bin/sh /file
?
#變量值的替換#將變量foo中全部以.o字串結尾全部替換成.c
foo := a.o b.o c.o
bar := $(foo:.o=.c)#靜態模式實現變量值的替換:依賴被替換字串中有相同模式
bar1 := $(foo:%.o=%.c)#把變量的值再當成變量
x = $(y)
y = z
z = Hello
a := $($(x))exec:echo $(bar) #a.c b.c c.cecho $(bar1) #a.c b.c c.cecho $(a) #Hello
?
?
?
還有一個比較有用的操作符”?=”,如foo? ?=? bar,其含義是,如果foo沒有被定義過,那么變量foo的值就是”bar”,如果foo先前被定義過,那么這條語句將什么也不做。
變量高級用法:(1)、變量值的替換:可以替換變量中共有的部分,其格式是”$(var:a=b)”或是”${var:a=b}”,其意思是,把變量”var”中所有以”a”字串”結尾”的”a”替換成”b”字符串。這里的”結尾”意思是”空格”或是”結束符”。另外一種變量替換的技術是以”靜態模式”定義的,其格式是”$(var:%.a=%.b)”,這依賴于被替換字串中有相同的模式。(2)、把變量的值再當成變量。
追加變量值:可以使用”+=”操作符給變量追加值。如果變量之前沒有定義過,那么,”+=”會自動變成”=”,如果前面有變量定義,那么”+=”會繼承于前次操作的賦值符。如果前一次的是”:=”,那么”+=”會以”:=”作為起賦值符。
?
#追加變量值objects = main.o foo.o bar.o#下面兩行語句等價
#objects := $(objects) another.o
objects += another.ovariable := value
#下面兩行語句等價
#variable := $(variable) more
variable += moreexec:echo $(objects) #main.o foo.o bar.o another.oecho $(variable) #value more
?
?
?
override指示符:有些變量通常是make的命令行參數設置的,那么Makefile中對這個變量的賦值會被忽略。如果想在Makefile中設置這類參數的值,那么,可以使用”override”指示符。
多行變量:使用define關鍵字設置變量的值可以有換行,這有利于定義一系列的命令。define指示符后面跟的是變量的名字,而重起一行定義變量的值,定義是以endef關鍵字結束。其工作方式和”=”操作符一樣。變量的值可以包含函數、命令、文字,或是其它變量。因為命令需要以[Tab]鍵開頭,所以如果你用define定義的命令變量中沒有以[Tab]鍵開頭,那么make就不會把其認為是命令。
環境變量:make運行時的系統環境變量可以在make開始運行時被載入到Makefile文件中,但是如果Makefile中已定義了這個變量,或是這個變量由make命令行帶入,那么系統的環境變量的值將被覆蓋(如果make指定了”-e”參數,那么,系統環境變量將覆蓋Makefile中定義的變量)。
使用條件判斷:可以讓make根據運行時的不同情況選擇不同的執行分支。條件表達式可以是比較變量的值,或是比較變量和常量的值:ifeq、ifneq、ifdef、ifndef。注意:make在讀取Makefile時就計算條件表達式的值,并根據條件表達式的值來選擇語句,所以,最好不要把自動化變量(如”$@”等)放入條件表達式中,因為自動化變量是在運行時才有的。而且,make不允許把整個條件語句分成兩部分放在不同的文件中。
?
#使用條件判斷#ifdef
bar =
foo = $(bar)
ifdef foofrobozz = yes
elsefrobozz = no
endif#ifeq
ifeq ($(CC), gcc)b = gcc
elseb = other
endifexec:echo $(frobozz) #yesecho $(b)
使用函數:在Makefile中可以使用函數來處理變量。函數調用后,函數的返回值可以當作變量來使用。函數參數間以逗號”,”分隔,而函數名和參數之間以”空格”分隔。函數調用以”$”開頭,以圓括號或花括號把函數名和參數括起。
?
?
?
?
#使用函數#subst
comma := ,
empty :=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))#sort
str := foo bar lose
str := $(sort $(str))#foreach
names := a b c d
files := $(foreach n,$(names),$(n).o)#shell
directory := $(shell pwd)exec:echo $(bar) #a,b,cecho $(str) #bar foo loseecho $(files) #a.o b.o c.o d.oecho $(directory)
?
?
?
常用的字符串處理函數有:subst、patsubst、strip、findstring、filter、filter-out、sort、word、wordlist、words、firstword。
文件名操作函數:每個函數的參數字符串都會被當作一個或是一系列的文件名來對待。常用的函數有:dir、notdir、suffix、basename、 addsuffix、addprefix、join。
foreach函數:這個函數是用來做循環用的,Makefile中的foreach函數幾乎是仿照Unix標準Shell(/bin/sh)中的for語句,或是C-Shell(/bin/csh)中的foreach語句而構建的。
if函數:if函數很像GNU的make所支持的條件語句ifeq。if函數可以包含”else”部分,或是不含。即if函數的參數可以是兩個,也可以是三個。
call函數:是唯一一個可以用來創建新的參數化的函數。
origin函數:它并不操作變量的值,它只是告訴你這個變量是哪里來的。
shell函數:它的參數應該就是操作系統Shell的命令,它和反引號”`”是相同的功能。這就是說,shell函數把執行操作系統命令后的輸出作為函數返回。于是,可以用操作系統命令以及字符串處理命令awk、sed等命令來生成一個變量。
控制make的函數:error、warning。
make的運行:一般來說,最簡單的就是直接在命令行下輸入make命令,make命令會找當前目錄的Makefile來執行,一切都是自動的。
make的退出碼:0,表示成功執行;1,如果make運行時出現任何錯誤,其返回1;2,如果你使用了make的”-q”選項,并且make使得一些目標不需要更新,那么返回2.
指定Makefile:GNU make找尋默認的Makefile的規則是在當前目錄下依次找三個文件----“GNUmakefile”、”makefile”和”Makefile”。其按順序找這三個文件,一旦找到,就開始讀取這個文件并執行。
我們也可以給make命令指定一個特殊名字的Makefile。要達到這個功能,要使用make的”-f”或是”--file”參數(“--makefile”參數也行)。
指定目標:一般來說,make的最終目標是makefile中的第一個目標,而其它目標一般是由這個目標連帶出來的。這是make的默認行為。若要指示make,讓其完成你所指定的目標,需要在make命令后直接跟目標的名字。
任何在makefile中的目標都可以被指定成終極目標,但是除了以”-”打頭,或是包含了”=”的目標,因為有這些字符的目標,會被解析成命令行參數或是變量。
隱含規則:也就是一種慣例,make會按照這種”慣例”來運行,哪怕我們的Makefile中沒有書寫這樣的規則。
隱含規則使用的變量:可以把隱含規則中使用的變量分成兩種,一種是命令相關的,如”CC”,一種是參數相關的,如”CFLAGS”。
命令相關的變量:
(1)、AR:函數庫打包程序,默認命令是”ar”;
(2)、AS:匯編語言編譯程序,默認命令是”as”;
(3)、CC:C語言編譯程序,默認命令是”cc”;
(4)、CXX:C++語言編譯程序,默認命令是”g++”;
(5)、CO:從RCS文件中擴展文件程序,默認命令是”co”;
(6)、CPP:C程序的預處理器(輸出是標準輸出設備),默認命令是”$(CC)-E”;
(7)、FC:Fortran和Ratfor的編譯器和預處理程序,默認命令是”f77”;
(8)、GET:從SCCS文件中擴展文件的程序,默認命令是”get”;
(9)、LEX:Lex方法分析器程序(針對于C或Ratfor),默認命令是”lex”;
(10)、PC:Pascal語言編譯程序,默認命令是”pc”;
(11)、YACC:Yacc文法分析器(針對于C程序),默認命令是”yacc”;
(12)、YACCR:Yacc文法分析器(針對于Ratfor程序),默認命令是”yacc-r”;
(13)、MAKEINFO:轉換Texinfo源文件(.texi)到Info文件程序,默認命令是”makeinfo”;
(14)、TEX:從TeX源文件創建TeX DVI文件的程序,默認命令是”tex”;
(15)、TEXI2DVI:從Texinfo源文件創建TeX DVI文件的程序,默認命令是”texi2dvi”;
(16)、WEAVE:轉換Web到TeX的程序,默認命令是”weave”;
(17)、CWEAVE:轉換C Web到TeX的程序,默認命令是”cweave”;
(18)、TANGLE:轉換Web到Pascal語言的程序,默認命令是”tangle”;
(19)、CTANGLE:轉換C Web到C,默認命令是”ctangle”;
(20)、RM:刪除文件命令,默認命令是”rm-f”。
參數相關的變量:如果沒有指明其默認值,那么其默認值都是空。
(1)、ARFLAGS:函數庫打包程序AR命令的參數,默認是”rv”;
(2)、ASFLAGS:匯編語言編譯器參數(當明顯地調用”.s”或”.S”文件時);
(3)、CFLAGS:C語言編譯器參數;
(4)、CXXFLAGS:C++語言編譯器參數;
(5)、COFLAGS:RCS命令參數;
(6)、CPPFLAGS:C預處理器參數(C和Fortran編譯器也會用到);
(7)、FFLAGS:Fortran語言編譯器參數;
(8)、GFLAGS:SCCS “get”程序參數;
(9)、LDFLAGS:鏈接器參數,如”ld”;
(10)、LFLAGS:Lex文法分析器參數;
(11)、PFLAGS:Pascal語言編譯器參數;
(12)、RFLAGS:Ratfor程序的Fortran編譯器參數;
(13)、YFLAGS:Yacc文法分析器參數。
定義模式規則:可以使用模式規則來定義一個隱含規則,一個模式規則就好像一個一般的規則,只是在規則中,目標的定義需要有”%”字符。”%”的意思是表示一個或多個任意字符。在依賴目標中同樣可以使用”%”,只是依賴目標中的”%”的取值,取決于其目標。注意:”%”的展開發生在變量和函數的展開之后,變量和函數的展開發生在make載入Makefile時,而模式規則中的”%”則發生在運行時。
自動化變量:就是這種變量會把模式中所定義的一系列的文件自動地挨個取出,直至所有符合模式的文件都取完了。這種自動化變量只應出現在規則的命令中。
以下是自動化變量的說明:
(1)、$@:表示規則中的目標文件集。在模式規則中,如果有多個目標,那么,”$@”就是匹配于目標中模式定義的集合;
(2)、$%:僅當目標是函數庫文件時,表示規則中的目標成員名。如果目標不是函數庫文件,那么,其值為空;
(3)、$<:依賴目標中的第一個目標名字。如果依賴目標是以模式(即”%”)定義的,那么”$<”將是符合模式的一系列的文件集,注意,其是一個一個取出來的;
(4)、$?:所有比目標新的依賴目標的集合,以空格分隔;
(5)、$^:所有依賴目標的集合,以空格分隔,如果在依賴目標中有多個重復的,那么這個變量會出去重復的依賴目標,只保留一份;
(6)、$+:這個變量很像”$^”,也是所有依賴目標的集合,只是它不去除重復的依賴目標;
(7)、$*:這個變量表示目標模式中”%”及其之前的部分。如果目標是”dir/a.foo.b”,并且目標的模式是”a.%.b”,那么,”$*”的值就是”dir /a.foo”。如果目標中沒有模式的定義,那么”$*”也就不能被推導出,但是,如果目標文件的后綴是make所識別的,那么”$*”就是除了后綴的那一部分。例如,如果目標是”foo.c”,因為”.c”是make所能識別的后綴名,所以,”$*”的值就是”foo”。這個特性是GNU make的,很有可能不兼容于其它版本的make,所以,應該盡量避免使用”$*”。
使用make更新函數庫文件:函數庫文件也就是對Object文件(程序編譯的中間文件)的打包文件。在Unix下,一般是由命令”ar”來完成打包工作。
以上內容整理自陳皓的CSDN博客:http://blog.csdn.net/haoel/article/category/9198
GitHub:https://github.com/fengbingchun/Linux_Code_Test
總結
以上是生活随笔為你收集整理的Makefile语法基础介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux基础介绍
- 下一篇: Shell脚本基础介绍