1. 程式人生 > 其它 >linuxmake沒有指明目標並且找不到makefile_Makefile筆記

linuxmake沒有指明目標並且找不到makefile_Makefile筆記

技術標籤:linuxmake沒有指明目標並且找不到makefile

0121ca53b46c92edc7942543409a5031.gif


一般來說,無論是C、C++、還是pas,首先要把原始檔編譯成中間程式碼檔案,在Windows下也就是 .obj 檔案,UNIX下是 .o 檔案,即 Object File,這個動作叫做編譯(compile)。然後再把大量的Object File合成執行檔案,這個動作叫作連結(link)。

原始檔首先會生成中間目標檔案,再由中間目標檔案生成執行檔案。在編譯時,編譯器只檢測程式語法,和函式、變數是否被宣告。如果函式未被宣告,編譯器會給出一個警告,但可以生成Object File。而在連結程式時,連結器會在所有的Object File中找尋函式的實現,如果找不到,那到就會報連結錯誤碼(Linker Error),在VC下,這種錯誤一般是:Link 2001錯誤,意思說是說,連結器未能找到函式的實現。你需要指定函式的Object File 。

make命令執行時,需要一個 Makefile 檔案,以告訴make命令需要怎麼樣的去編譯和連結程式。

Makefile規則

Makefile的規則。

target ... : prerequisites ...        command......

target也就是一個目標檔案,可以是Object File,也可以是執行檔案。還可以是一個標籤(Label),對於標籤這種特性,在後續的“偽目標”章節中會有敘述。

prerequisites就是,要生成那個target所需要的檔案或是目標。

command也就是make需要執行的命令。(任意的Shell命令)

demo Makefile如下:

edit : main.o kbd.o command.o display.o /           insert.o search.o files.o utils.o            cc -o edit main.o kbd.o command.o display.o /                       insert.o search.o files.o utils.omain.o : main.c defs.h            cc -c main.ckbd.o : kbd.c defs.h command.h            cc -c kbd.ccommand.o : command.c defs.h command.h            cc -c command.cdisplay.o : display.c defs.h buffer.h            cc -c display.cinsert.o : insert.c defs.h buffer.h            cc -c insert.csearch.o : search.c defs.h buffer.h            cc -c search.cfiles.o : files.c defs.h buffer.h command.h            cc -c files.cutils.o : utils.c defs.h            cc -c utils.cclean :                 rm edit main.o kbd.o command.o display.o /               insert.o search.o files.o utils.o

反斜槓(/)是換行符的意思,這樣比較便於Makefile的易讀。

需要指出的是,clean不是一個檔案,它只不過是一個動作名字,有點像C語言中的lable一樣,其冒號後什麼也沒有。make就不會自動去找檔案的依賴性,也就不會自動執行其後所定義的命令。要執行其後的命令,就要在make命令後明顯得指出這個lable的名字。這樣的方法非常有用,我們可以在一個makefile中定義不用的編譯或是和編譯無關的命令,如程式的打包、程式的備份等。如果要刪除執行檔案和所有的中間目標檔案,那麼,只要簡單地執行一下make clean就可以了。

make的流程

1.make會在當前目錄下找名字叫Makefilemakefile的檔案。2.如果找到,它會找檔案中的第一個目標檔案(target),在上面的例子中,它會找到edit這個檔案,並把這個檔案作為最終的目標檔案。3.如果edit檔案不存在,或是edit所依賴的後面的 .o 檔案的檔案修改時間要比edit這個檔案新,它就會執行後面所定義的命令來生成edit這個檔案。4.如果edit所依賴的.o檔案不存在,那麼make會在當前檔案中找目標為.o檔案的依賴性,如果找到則再根據那一個規則生成.o檔案。5.最終,生成目標檔案所依賴的C檔案和H檔案存在,於是make會生成 .o 檔案,形成了make所依賴的.o檔案後,最終連結生成可執行檔案edit。

make會一層又一層地去找檔案的依賴關係,直到最終編譯出第一個目標檔案。在找尋的過程中,如果出現錯誤,比如最後被依賴的檔案找不到,那麼make就會直接退出並報錯;而對於所定義的命令的錯誤,或是編譯不成功,make根本不理。

Makefile變數及自動推導

Makefile 可以使用變數,以提高效率。如使用下列變數,後可以在makefile中使用$(objects)來代替相應字串。

objects = main.o kbd.o command.o display.o /              insert.o search.o files.o utils.o

GNU的make很強大,它可以自動推導檔案以及檔案依賴關係後面的命令。make看到一個.o檔案,它就會自動的把.c檔案加在依賴關係中。如果make找到一個whatever.o,那麼whatever.c就會是whatever.o的依賴檔案。並且cc -c whatever.c也會被推匯出來,所以上面的makefile再也不用那麼複雜,如下所示:

objects = main.o kbd.o command.o display.o /              insert.o search.o files.o utils.oedit : $(objects)            cc -o edit $(objects)main.o : defs.hkbd.o : defs.h command.hcommand.o : defs.h command.hdisplay.o : defs.h buffer.hinsert.o : defs.h buffer.hsearch.o : defs.h buffer.hfiles.o : defs.h buffer.h command.hutils.o : defs.h.PHONY : cleanclean :                rm edit $(objects)

這種方法是make的“隱晦規則”。上面檔案內容中,“.PHONY”表示,clean是個偽目標檔案。

清空目標檔案的規則

每個Makefile中都應該寫一個清空目標檔案(.o和執行檔案)的規則,這不僅便於重編譯,也很利於保持檔案的清潔。這是一個“修養”,一般的風格都是:

clean:    rm edit $(objects)

更為穩健的做法是:

.PHONY : cleanclean :-rm edit $(objects)

.PHONY意思表示clean是一個“偽目標”,。而在rm命令前面加了一個小減號的意思就是,也許某些檔案出現問題,但不要管,繼續做後面的事。當然,clean的規則不要放在檔案的開頭,不然,這就會變成make的預設目標,一般將其放在檔案的最後。

Makefile構成

Makefile裡主要包含了五個東西:顯式規則、隱晦規則、變數定義、檔案指示和註釋。

1.顯式規則。顯式規則說明了,如何生成一個或多的的目標檔案。這是由Makefile的書寫者明顯指出,要生成的檔案,檔案的依賴檔案,生成的命令。2.隱晦規則。由於我們的make有自動推導的功能,所以隱晦的規則可以讓我們比較粗糙地簡略地書寫Makefile,這是由make所支援的。3.變數的定義。在Makefile中我們要定義一系列的變數,變數一般都是字串,當Makefile被執行時,其中的變數都會被擴充套件到相應的引用位置上。4.檔案指示。其包括了三個部分,一個是在一個Makefile中引用另一個Makefile,就像C語言中的include一樣;另一個是指根據某些情況指定Makefile中的有效部分,就像C語言中的預編譯#if一樣;還有就是定義一個多行的命令。5.註釋。Makefile中只有行註釋,其註釋是用#字元,這個就像C/C++中的//一樣。如果你要在你的Makefile中使用#字元,可以用反斜框進行轉義,如:/#

最後,還值得一提的是,在Makefile中的命令,必須要以[Tab]鍵開始。

引用其它的Makefile

在Makefile使用include關鍵字可以把別的Makefile包含進來,這很像C語言的#include,被包含的檔案會原模原樣的放在當前檔案的包含位置。include的語法是:

include filename可以是當前作業系統Shell的檔案模式(可以保含路徑和萬用字元)

include前面可以有一些空字元,但是絕不能是[Tab]鍵開始。include可以用一個或多個空格隔開。如,當存在以下幾個Makefile:a.mkb.mkc.mk,還有一個檔案叫foo.make,以及一個變數$(bar),其包含了e.mkf.mk,那麼,下面的語句:

include foo.make *.mk $(bar)等價於:include foo.make a.mk b.mk c.mk e.mk f.mk

make命令開始時,會把找尋include所指出的其它Makefile,並把其內容安置在當前的位置。就好像C/C++的#include指令一樣。如果檔案都沒有指定絕對路徑或是相對路徑的話,make會在當前目錄下首先尋找,如果當前目錄下沒有找到,那麼,make還會在下面的幾個目錄下找:

1.如果make執行時,有-I--include-dir引數,那麼make就會在這個引數所指定的目錄下去尋找。2.如果目錄/include(一般是:/usr/local/bin/usr/include)存在的話,make也會去找。

如果有檔案沒有找到的話,make會生成一條警告資訊,但不會馬上出現致命錯誤。它會繼續載入其它的檔案,一旦完成makefile的讀取,make會再重試這些沒有找到,或是不能讀取的檔案,如果還是不行,make才會出現一條致命資訊。如果你想讓make忽略那些無法讀取的檔案,而繼續執行,可以在include前加一個減號“-”。如:

-include 

其表示,無論include過程中出現什麼錯誤,都不要報錯繼續執行。和其它版本make相容的相關命令是sinclude,其作用和這一個是一樣的。

同時,如果當前環境中定義了環境變數MAKEFILES,那麼,make會把這個變數中的值做一個類似於include的動作。這個變數中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,從這個環境變中引入的Makefile的目標不會起作用,如果環境變數中定義的檔案發現錯誤,make會忽略。

但是在這裡建議不要使用這個環境變數。因為只要這個變數一被定義,當使用make時,所有的Makefile都會受到它的影響。

make的執行流程

GNU的make工作時的執行流程如下:

1.讀入所有的Makefile。2.讀入被include的其它Makefile。3.初始化檔案中的變數。4.推導隱晦規則,並分析所有規則。5.為所有的目標檔案建立依賴關係鏈。6.根據依賴關係,決定哪些目標要重新生成。7.執行生成命令。1-5步為第一個階段,6-7為第二個階段。第一個階段中,如果定義的變數被使用了,make會把其展開在使用的位置。但make並不會完全馬上展開,make使用的是拖延戰術,如果變量出現在依賴關係的規則中,那麼僅當這條依賴被決定要使用了,變數才會在其內部展開。

Makefile規則規範

規則包含兩個部分,一個是依賴關係,一個是生成目標的方法。

在Makefile中,規則的順序很重要。Makefile中只應該有一個最終目標,其它的目標都是被這個目標所連帶出來的,所以一定要讓make知道你的最終目標是什麼。一般來說,定義在Makefile中的目標可能會有很多,但是第一條規則中的目標將被確立為最終的目標。如果第一條規則中的目標有很多個,則第一個目標會成為最終的目標。

示例如下:

foo.o : foo.c defs.h       # foo模組            cc -c -g foo.c

上面這個例子表明兩件事:

1.檔案的依賴關係,foo.o依賴於foo.c和defs.h的檔案,如果foo.c和defs.h的檔案日期要比foo.o檔案日期要新,或是foo.o不存在,那麼依賴關係發生。2.如何生成(或更新)foo.o檔案,即第二行的cc命令。

規則語法如下:

targets : prerequisites    command...或是這樣:targets : prerequisites ; command    command...

規則告訴make兩件事:檔案的依賴關係和如何成成目標檔案。

targets是檔名,以空格分開,可以使用萬用字元。一般來說,我們的目標基本上是一個檔案,但也有可能是多個檔案。

command是命令列,如果其不與target:prerequisites在一行,那麼,必須以[Tab鍵]開頭,如果和prerequisites在一行,那麼可以用分號做為分隔。

prerequisites也就是目標所依賴的檔案(或依賴目標)。如果其中的某個檔案要比目標檔案要新,那麼,目標就被認為是過時的,被認為是需要重生成的。

如果命令太長,你可以使用反斜框(/)作為換行符。make對一行上有多少個字元沒有限制。

make支援三各萬用字元:*?[…]。萬用字元代替了你一系列的檔案,如*.c表示所以後綴為c的檔案。需要注意的是,如果我們的檔名中有萬用字元,如:*,則為了表示,需使用用轉義字元//*來表示真實的*字元,而不是任意長度的字串。

objects = *.o

上面這個例子,表示了,通符同樣可以用在變數中。並不是說[*.o]會展開。objects的值就是*.o。Makefile中的變數其實就是C/C++中的巨集。如果要讓萬用字元在變數中展開,也就是讓objects的值是所有.o的檔名的集合,可以使用如下方式實現:

objects := $(wildcard *.o)

這種用法由關鍵字wildcard指出,後續會給出討論。

檔案搜尋

在一些大的工程中,有大量的原始檔,我們通常的做法是把這許多的原始檔分類,並存放在不同的目錄中。所以,當make需要去找尋檔案的依賴關係時,可以在檔案前加上路徑,但最好的方法是把一個路徑告訴make,讓make在自動去找。

Makefile檔案中的特殊變數VPATH就是完成這個功能的,如果沒有指明這個變數,make只會在當前的目錄中去找尋依賴檔案和目標檔案。如果定義了這個變數,那麼,make就會在當前目錄找不到的情況下,到所指定的目錄中去找尋檔案。

VPATH = src:../headers

上面的的定義指定兩個目錄,src../headers,make會按照這個順序進行搜尋。目錄由冒號分隔。(當前目錄具有最高優先順序)。

另一個設定檔案搜尋路徑的方法是使用make的vpath關鍵字(注意,它是全小寫的)。這不是變數,這是一個make的關鍵字,這和上面提到的那個VPATH變數很類似,但是它更為靈活。它可以指定不同的檔案在不同的搜尋目錄中。這是一個很靈活的功能。它的使用方法有三種:

1.vpath 為符合模式的檔案指定搜尋目錄。2.vpath 清除符合模式的檔案的搜尋目錄。3.vpath清除所有已被設定好了的檔案搜尋目錄。

vapth使用方法中的需要包含%字元。%的意思是匹配零或若干字元,例如,%.h表示所有以.h結尾的檔案。指定了要搜尋的檔案集,而則指定了的檔案集的搜尋的目錄。例如:

vpath %.h ../headers

該語句表示,如果該檔案在當前目錄沒有找到,則make在../headers目錄下搜尋相應以.h結尾的檔案。

我們可以連續地使用vpath語句,以指定不同搜尋策略。如果連續的vpath語句中出現了相同的,或是被重複了的,那麼,make會按照vpath語句的先後順序來執行搜尋。如:

vpath %.c foovpath %   blishvpath %.c bar

其表示.c結尾的檔案,搜尋的順序會是:先在foo目錄,然後是blish,最後是bar目錄。

vpath %.c foo:barvpath %   blish

而上面的語句則表示.c結尾的檔案,先在foo目錄,然後是bar目錄,最後才是blish目錄。

偽目標

最開始例子中,提到過一個clean的目標,這是一個偽目標,

clean:        rm *.o temp

“偽目標”並不是一個檔案,只是一個標籤,由於“偽目標”不是檔案,所以make無法生成它的依賴關係和決定它是否要執行。我們只有通過顯示地指明這個“目標”才能讓其生效。當然,“偽目標”的取名不能和檔名重名,不然其就失去了“偽目標”的意義了。

為了避免和檔案重名的這種情況,我們可以使用一個特殊的標記“.PHONY”來顯示地指明一個目標是“偽目標”,向make說明,不管是否有這個檔案,這個目標就是“偽目標”。

.PHONY : clean

只要有這個宣告,不管是否有clean檔案,要執行clean這個目標,需要輸入make clean命令。

.PHONY: cleanclean:        rm *.o temp

偽目標一般沒有依賴的檔案,但也可以為偽目標指定所依賴的檔案。偽目標同樣可以作為預設目標,只要將其放在第一個。一個示例就是,如果你的Makefile需要一口氣生成若干個可執行檔案,但你只想簡單地敲一個make完事,並且,所有的目標檔案都寫在一個Makefile中,那麼你可以使用“偽目標”這個特性:

all : prog1 prog2 prog3.PHONY : allprog1 : prog1.o utils.o        cc -o prog1 prog1.o utils.oprog2 : prog2.o        cc -o prog2 prog2.oprog3 : prog3.o sort.o utils.o        cc -o prog3 prog3.o sort.o utils.o

Makefile中的第一個目標會被作為其預設目標。我們聲明瞭一個all的偽目標,其依賴於其它三個目標。由於偽目標的特性是--總是被執行,所以其依賴的那三個目標就總是不如all這個目標新。所以,其它三個目標的規則總是會被編譯。也就達到了我們一口氣生成多個目標的目的。

從上面的例子我們可以看出,目標也可以成為依賴。所以偽目標同樣也可成為依賴。看下面的例子:

.PHONY: cleanall cleanobj cleandiffcleanall : cleanobj cleandiff        rm programcleanobj :        rm *.ocleandiff :        rm *.diff

cleanobjcleandiff這兩個偽目標有點像子程式的意思。我們可以輸入make cleanallmake cleanobjmake cleandiff`命令來達到清除不同種類檔案的目的。

靜態模式

靜態模式可以更加容易地定義多目標的規則,可以讓我們的規則變得更加的有彈性和靈活。語法如下:

 ...>: :  ...>...

targets定義了一系列的目標檔案,可以有萬用字元。是目標的一個集合。

target-parrtern是指明瞭targets的模式,也就是的目標集模式。

prereq-parrterns是目標的依賴模式,它對target-parrtern形成的模式再進行一次依賴目標的定義。

如果我們的定義成%.o,意思是我們的集合中都是以.o結尾的;而如果我們的定義成%.c,意思是對所形成的目標集進行二次定義。其計算方法是,取模式中的%(也就是去掉了[.o]這個結尾),併為其加上[.c]這個結尾,形成的新集合。

所以,我們的目標模式或是依賴模式中都應該有%這個字元,如果你的檔名中有%那麼你可以使用反斜槓/進行轉義,來標明真實的%字元。

看一個例子:

objects = foo.o bar.oall: $(objects)$(objects): %.o: %.c        $(CC) -c $(CFLAGS) $< -o [email protected]

上面的例子中,指明瞭我們的目標從$object中獲取,%.o表明要所有以.o結尾的目標,也就是foo.o bar.o,也就是變數$object集合的模式,而依賴模式%.c則取模式%.o%,也就是foo bar,併為其加下.c的字尾,於是,我們的依賴目標就是foo.c bar.c。而命令中的$<[email protected]則是自動化變數,$<表示所有的依賴目標集(也就是foo.c bar.c),[email protected]表示目標集(也就是foo.o bar.o)。於是,上面的規則展開後等價於下面的規則:

foo.o : foo.c        $(CC) -c $(CFLAGS) foo.c -o foo.obar.o : bar.c        $(CC) -c $(CFLAGS) bar.c -o bar.o

Makefile 命令規範

每條規則中的命令和作業系統Shell的命令列是一致的。make會一按順序一條一條的執行命令,每條命令的開頭必須以[Tab]鍵開頭,除非,命令是緊跟在依賴規則後面的分號後的。在命令列之間中的空格或是空行會被忽略,但是如果該空格或空行是以Tab鍵開頭的,那麼make會認為其是一個空命令。

make會把其要執行的命令列在命令執行前輸出到螢幕上。當我們用@字元在命令列前。這個命令將不被make顯示出來,最具代表性的例子是,我們用這個功能來像螢幕顯示一些資訊。如:

@echo正在編譯XXX模組......

當make執行時,會輸出“正在編譯XXX模組......”字串,但不會輸出命令。如果沒有@,那麼,make將輸出:

echo 正在編譯XXX模組......正在編譯XXX模組......

如果make執行時,帶入make引數-n--just-print,其只是顯示命令,不會執行命令。這個功能很有利於我們除錯我們的Makefile,看看我們書寫的命令是執行起來是什麼樣子的或是什麼順序的。

而make引數-s—slient則是全面禁止命令的顯示。

當依賴目標新於目標時,也就是當規則的目標需要被更新時,make會一條一條的執行其後的命令。需要注意的是,如果你要讓上一條命令的結果應用在下一條命令時,你應該使用分號分隔這兩條命令。比如你的第一條命令是cd命令,你希望第二條命令得在cd之後的基礎上執行,那麼你就不能把這兩條命令寫在兩行上,而應該把這兩條命令寫在一行上,用分號分隔。如:

示例一:exec:            cd /home/            pwd示例二:exec:            cd /home/; pwd

當我們執行make exec時,第一個例子中的cd沒有作用,pwd會打印出當前Makefile目錄;而第二個例子中,cd就起作用了,pwd會打印出/home/

命令出錯

每當命令執行完後,make會檢測每個命令的返回碼,如果命令返回成功,那麼make會執行下一條命令,當規則中所有的命令成功返回後,這個規則就算是成功完成了。如果一個規則中的某個命令出錯了(命令退出碼非零),那麼make就會終止執行當前規則,這將有可能終止所有規則的執行。

有些時候,命令的出錯並不表示就是錯誤的。例如mkdir命令,我們一定需要建立一個目錄,如果目錄不存在,那麼mkdir就成功執行,萬事大吉,如果目錄存在,那麼就出錯了。我們之所以使用mkdir的意思就是一定要有這樣的一個目錄,於是我們就不希望mkdir出錯而終止規則的執行。

為了做到這一點,忽略命令的出錯,我們可以在Makefile的命令列前加一個減號-(在[Tab]鍵之後),表示不管命令出不出錯都認為是成功的。如:

   clean:-rm -f *.o

還有一個全域性的辦法是,給make加上-i或是--ignore-errors引數,Makefile中所有命令都會忽略錯誤。而如果一個規則是以.IGNORE作為目標的,那麼這個規則中的所有命令將會忽略錯誤。

還有一個引數的是-k或是--keep-going。這個引數的意思是,如果某規則中的命令出錯了,就終目該規則的執行,但繼續執行其它規則。

巢狀執行make

在一些大的工程中,我們會把我們不同模組或是不同功能的原始檔放在不同的目錄中,我們可以在每個目錄中都書寫一個該目錄的Makefile,這有利於讓我們的Makefile變得更加地簡潔,而不至於把所有的東西全部寫在一個Makefile中,這樣會很難維護我們的Makefile,這個技術對於我們模組編譯和分段編譯有著非常大的好處。

如有一個子目錄叫subdir,這個目錄下有個Makefile檔案,來指明瞭這個目錄下檔案的編譯規則。那麼我們總控的Makefile可以這樣書寫:

subsystem:        cd subdir && $(MAKE)

其等價於:

subsystem:        $(MAKE) -C subdir

定義$(MAKE)巨集變數的意思是,也許我們的make需要一些引數,所以定義成一個變數比較利於維護。這兩個例子的意思都是先進入subdir目錄,然後執行make命令。

我們把這個Makefile叫做“總控Makefile”,總控Makefile的變數可以傳遞到下級的Makefile中,但是不會覆蓋下層的Makefile中所定義的變數,除非指定了-e引數。

如果要傳遞變數到下級Makefile中,可以使用下面的宣告:

export

如果你不想讓某些變數傳遞到下級Makefile中,可以這樣宣告:

   unexport 

如果要傳遞所有的變數,那麼,只要一個export就行了。後面什麼也不用跟,表示傳遞所有的變數。

定義命令包

如果Makefile中出現一些相同命令序列,那麼我們可以為這些相同的命令序列定義一個變數。定義這種命令序列的語法以define開始,以endef結束:

define run-yaccyacc $(firstword $^)mv y.tab.c [email protected]

run-yacc是命令包的名字,不要和Makefile中的變數重名。在defineendef中的兩行就是命令序列。這個命令包中的第一個命令是執行Yacc程式,因為Yacc程式總是生成“y.tab.c”的檔案,所以第二行的命令就是把這個檔案改改名字。把這個命令包放到一個示例:

foo.c : foo.y        $(run-yacc)

可以像使用變數一樣使用此命令包。在這個命令包的使用中,命令包run-yacc中的$^就是foo.y[email protected]就是foo.c,make在執行命令包時,命令包中的每個命令會被依次獨立執行。

條件判斷

使用條件判斷,可以讓make根據執行時的不同情況選擇不同的執行分支。條件表示式可以是比較變數的值,或是比較變數和常量的值。

下面的例子,判斷$(CC)變數是否gcc,如果是的話,則使用GNU函式編譯目標。

libs_for_gcc = -lgnunormal_libs =foo: $(objects)ifeq ($(CC),gcc)        $(CC) -o foo $(objects) $(libs_for_gcc)else        $(CC) -o foo $(objects) $(normal_libs)endif

可見,在上面示例的這個規則中,目標foo可以根據變數$(CC)值來選取不同的函式庫來編譯程式。

我們可以從上面的示例中看到三個關鍵字:ifeqelseendififeq的意思表示條件語句的開始,並指定一個條件表示式,表示式包含兩個引數,以逗號分隔,表示式以圓括號括起。else表示條件表示式為假的情況。endif表示一個條件語句的結束,任何一個條件表示式都應該以endif結束。

條件表示式的語法為:

endif

以及:

elseendif

其中表示條件關鍵字,這個關鍵字有四個。

第一個是我們前面所見過的ifeq,語法如下:

ifeq (, ) ifeq ''''ifeq """"ifeq ""''ifeq ''""

比較引數arg1arg2的值是否相同。

第二個條件關鍵字是ifneq,語法是:

ifneq (, ) ifneq ''''ifneq """"ifneq ""''ifneq ''""

第三個條件關鍵字是ifdef,語法是:

  ifdef 

第四個條件關鍵字是ifndef,其語法是:

ifndef 

這一行上,多餘的空格是被允許的,但是不能以[Tab]鍵做為開始(不然就被認為是命令)。而註釋符#同樣也是安全的。elseendif也一樣,只要不是以[Tab]鍵開始就行了。

特別注意的是,make是在讀取Makefile時就計算條件表示式的值,並根據條件表示式的值來選擇語句。所以最好不要把自動化變數(如[email protected]等)放入條件表示式中,因為自動化變數是在執行時才有的。

而且,為了避免混亂,make不允許把整個條件語句分成兩部分放在不同的檔案中。

函式

函式的呼叫語法

函式呼叫,很像變數的使用,也是以“$”來標識的,其語法如下:

$(<function> )

或是

${<function> }

就是函式名,make支援的函式不多。是函式的引數,引數間以逗號,分隔,而函式名和引數之間以空格分隔。函式呼叫以$開頭,以圓括號或花括號把函式名和引數括起。

示例:

comma:= ,empty:=space:= $(empty) $(empty)foo:= a b cbar:= $(subst $(space),$(comma),$(foo))

在這個示例中,$(comma)的值是一個逗號。$(space)使用$(empty)定義了一個空格,$(foo)的值是a b c$(bar)的呼叫了函式subst,這是一個替換函式,這個函式有三個引數,第一個引數是被替換字串,第二個引數是替換字串,第三個引數是替換操作作用的字串。這個函式也就是把$(foo)中的空格替換成逗號,所以$(bar)的值是a,b,c

make 的執行

最簡單的就是直接在命令列下輸入make命令,make命令會找當前目錄的makefile來執行,一切都是自動的。但也有時也許只想讓make重編譯某些檔案,而不是整個工程,而又有的時候有幾套編譯規則,想在不同的時候使用不同的編譯規則等等,本部分主要講述如何使用make命令。

make命令執行後有三個退出碼:

•0 —— 表示成功執行。•1 —— 如果make執行時出現任何錯誤,其返回1。•2 —— 如果你使用了make的“-q”選項,並且make使得一些目標不需要更新,那麼返回2

GNU make找尋預設的Makefile的規則是在當前目錄下依次找三個檔案——GNUmakefilemakefileMakefile。其按順序找這三個檔案,一旦找到,就開始讀取這個檔案並執行。

也可以給make命令指定一個特殊名字的Makefile。要達到這個功能,要使用引數-f或是—file引數(—makefile引數也行)。如果不只一次地使用了-f引數,那麼所有指定的makefile將會被連在一起傳遞給make執行。

一般來說,make的最終目標是makefile中的第一個目標,而其它目標一般是由這個目標連帶出來的。這是make的預設行為。當然,makefile中的第一個目標通常是由許多個目標組成,你可以指示make,讓其完成你所指定的目標。要達到這一目的很簡單,需在make命令後直接跟目標的名字就可以完成。

任何在makefile中的目標都可以被指定成終極目標,但是除了以-開頭,或是包含了=的目標,因為有這些字元的目標,會被解析成命令列引數或是變數。甚至沒有被我們明確寫出來的目標也可以成為make的終極目標,也就是說,只要make可以找到其隱含規則推導規則,那麼這個隱含目標同樣可以被指定成終極目標。

示例:

.PHONY: allall: prog1 prog2 prog3 prog4

從示例中,可以看到,makefile中有四個需要編譯的程式——prog1prog2prog3prog4。我們可以使用make all命令來編譯所有的目標(如果把all置成第一個目標,那麼只需執行make),也可以使用make prog2來單獨編譯目標prog2

make可以指定所有makefile中的目標,也包括偽目標。我們可以根據這種性質來讓我們的makefile根據指定的不同的目標來完成不同的事。在Unix中,軟體釋出時,特別是GNU這種開源軟體的釋出時,其makefile都包含了編譯、安裝、打包等功能,我們可以參照這種規則來書寫我們的makefile中的目標:

 all這個偽目標是所有目標的目標,其功能一般是編譯所有的目標。 clean這個偽目標功能是刪除所有被make建立的檔案。 install這個偽目標功能是安裝已編譯好的程式,其實就是把目標執行檔案拷貝到指定的目標中去。print這個偽目標的功能是例出改變過的原始檔。 tar這個偽目標功能是把源程式打包備份。也就是一個tar檔案。 dist這個偽目標功能是建立一個壓縮檔案,一般是把tar檔案壓成Z檔案。或是gz檔案。 TAGS這個偽目標功能是更新所有的目標,以備完整地重編譯使用。 check”和“test這兩個偽目標一般用來測試makefile的流程。

當然一個專案的makefile中也不一定要書寫這樣的目標,這些東西都是GNU的東西,但是GNU搞出這些東西一定有其可取之處(等你的UNIX下的程式檔案一多時你就會發現這些功能很有用了),這裡只不過是說明了,如果要書寫這種功能,最好使用這種名字命名你的目標,這樣規範一些,規範的好處就是——不用解釋,大家都明白。而且如果你的makefile中有這些功能,一是很實用,二是可以顯得你的makefile很專業。

隱含規則

隱含規則是指一些在Makefile中的隱含的,早先約定了的,不需要再寫出來的規則。

隱含規則也就是一種慣例,make會按照這種慣例來執行,哪怕我們的Makefile中沒有書寫這樣的規則。例如,把[.c]檔案編譯成[.o]檔案這一規則,根本就不用寫出來,make會自動推匯出這種規則,並生成我們需要的[.o]檔案。

如果要使用隱含規則生成需要的目標,所需要做的就是不要寫出這個目標的規則。make會試圖去自動推導產生這個目標的規則和命令,如果make可以自動推導生成這個目標的規則和命令,這個行為就是隱含規則的自動推導。當然,隱含規則是make事先約定好的一些東西。例如,我們有下面的一個Makefile:

foo : foo.o bar.o        cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

我們可以注意到,這個Makefile中並沒有寫下如何生成foo.obar.o這兩目標的規則和命令。因為make的隱含規則功能會自動為我們自動去推導這兩個目標的依賴目標和生成命令。

make會在自己的隱含規則庫中尋找可以用的規則,如果找到,就會使用。如果找不到,就會報錯。在上面的例子中,make呼叫的隱含規則是,把[.o]的目標的依賴檔案置成[.c],並使用C的編譯命令cc –c $(CFLAGS) [.c]來生成[.o]的目標。即我們完全沒有必要寫下下面的兩條規則:

foo.o : foo.c        cc –c foo.c $(CFLAGS)bar.o : bar.c    cc –c bar.c $(CFLAGS)

因為,這已經是約定好了的事了,make和我們約定好了用C編譯器cc生成[.o]檔案的規則,這就是隱含規則。

當然,如果我們為[.o]檔案書寫了自己的規則,那麼make就不會自動推導並呼叫隱含規則,它會按照我們寫好的規則執行。

模式規則

我們可以使用模式規則來定義一個隱含規則。一個模式規則就好像一個一般的規則,只是在規則中,目標的定義需要有%字元。%的意思是表示一個或多個任意字元。在依賴目標中同樣可以使用%,只是依賴目標中的%的取值,取決於其目標。

有一點需要注意的是,%的展開發生在變數和函式的展開之後,變數和函式的展開發生在make載入Makefile時,而模式規則中的%則發生在執行時。

模式規則中,至少在規則的目標定義中要包含%,否則,就是一般的規則。目標中的%定義表示對檔名的匹配,%表示長度任意的非空字串。如%.c表示以.c結尾的檔名(檔名的長度至少為3),而s.%.c則表示以s.開頭,.c結尾的檔名(檔名的長度至少為5)。

如果%定義在目標中,那麼,目標中的%的值決定了依賴目標中的%的值。也就是說,目標中的模式的%決定了依賴目標中%的樣子。例如有一個模式規則如下:

%.o : %.c ; 

其含義是,指出了怎麼從所有的[.c]檔案生成相應的[.o]檔案的規則。如果要生成的目標是a.o b.,那麼%c就是a.c b.c

一旦依賴目標中的%模式被確定,那麼,make會被要求去匹配當前目錄下所有的檔名,一旦找到,make就會規則下的命令,所以,在模式規則中,目標可能會是多個的,如果有模式匹配出多個目標,make就會產生所有的模式目標,此時,make關心的是依賴的檔名和生成目標的命令這兩件事。

示例:

%.o : %.c            $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o [email protected]

其中,[email protected]表示每個目標的值,$<表示了每個依賴目標的值。這些奇怪的變數我們叫自動化變數

目標和依賴檔案都是一系列的檔案,我們如何書寫一個命令來完成從不同的依賴檔案生成相應的目標?因為在每一次的對模式規則的解析時,都會是不同的目標和依賴檔案。

自動化變數就是完成這個功能的,所謂自動化變數,就是這種變數會把模式中所定義的一系列的檔案自動地挨個取出,直至所有的符合模式的檔案都取完了,這種自動化變數只應出現在規則的命令中。

下面是所有的自動化變數及其說明:

變數說明
[email protected]表示規則中的目標檔案集。在模式規則中,如果有多個目標,那麼,[email protected]就是匹配於目標中模式定義的集合。
$%僅當目標是函式庫檔案中,表示規則中的目標成員名。例如,如果一個目標是foo.a(bar.o),那麼,$%就是bar.o[email protected]就是foo.a。如果目標不是函式庫檔案(Unix下是[.a],Windows下是[.lib]),其值則為空。
$<依賴目標中的第一個目標名字。如果依賴目標是以模式(即%)定義的,那麼$<將是符合模式的一系列的檔案集。注意,其是一個一個取出來的
$?所有比目標新的依賴目標的集合。以空格分隔。
$^所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重複的,那個這個變數會去除重複的依賴目標,只保留一份。
$+這個變數很像$^,也是所有依賴目標的集合。只是它不去除重複的依賴目標。
$*這個變量表示目標模式中%及其之前的部分。如果目標是dir/a.foo.b,並且目標的模式是a.%.b,那麼$*的值就是dir/a.foo。這個變數對於構造有關聯的檔名是比較有較。如果目標中沒有模式的定義,那麼$*也就不能被推匯出。但是如果目標檔案的字尾是make所識別的,那麼$*就是除了字尾的那一部分。例如:如果目標是foo.c,因為.c是make所能識別的字尾名,所以$*的值就是foo。這個特性是GNU make的,很有可能不兼容於其它版本的make,所以應該儘量避免使用$*,除非是在隱含規則或是靜態模式中。如果目標中的字尾是make所不能識別的,那麼$*就是空值。

當你希望只對更新過的依賴檔案進行操作時,$?在顯式規則中很有用。如假設有一個函式庫檔案叫lib,其由其它幾個object檔案更新。那麼把object檔案打包的比較有效率的Makefile規則是:

lib : foo.o bar.o lose.o win.o        ar r lib $?

在上述所列出來的自動量變數中。四個變數([email protected]$<$%$*)在擴充套件時只會有一個檔案,而另三個的值是一個檔案列表。這七個自動化變數還可以取得檔案的目錄名或是在當前目錄下的符合模式的檔名,只需要搭配上DF字樣。這是GNU make中老版本的特性,在新版本中,我們使用函式dirnotdir就可以做到了。D的含義就是Directory(目錄),F的含義就是File(檔案)。

小結

省了很多的東西,主要是簡單的常見的內容,能夠看懂大部分makefile就夠了。

References

[1]跟我一起寫 Makefile:https://blog.csdn.net/haoel/article/details/2886

往期推薦

2020.6.8-6.14一週知識動態

論文筆記:FuzzIL Coverage Guided Fuzzing for JavaScript Engines

2020.6.1-6.7一週知識動態

浮生Pwn記——老年選手回憶錄

f1eff84fd2220809f875eed1f403501c.png