說說Makefile那些事兒面試
|揚說|透過現象看本質shell
工做至今,一直對Makefile半知半解。忽然某天幡然醒悟,以爲此舉極爲不妥,只得洗心革面從頭學來,之前許多不明覺厲之處頓時茅塞頓開,想一想好記性不如爛筆頭,便來講說Makefile那些事兒。微信
Makefile就是一文本文件。函數
-----------------------------------------------工具
$ file Makefile性能
Makefile: ASCII make commands textui
-----------------------------------------------spa
通常來講,咱們平時所稱的Makefile是指make命令以及Makefile文件,Makefile文件中記錄着各類規則,make命令經過分析Makefile執行規則中的操做。命令行
看看百度百科的定義:設計
make是一個命令工具,它解釋Makefile 中的指令(應該說是規則)。在Makefile文件中描述了整個工程全部文件的編譯順序、編譯規則。Makefile 有本身的書寫格式、關鍵字、函數。像C 語言有本身的格式、關鍵字和函數同樣。並且在Makefile 中可使用系統shell所提供的任何命令來完成想要的工做。Makefile(在其它的系統上多是另外的文件名)在絕大多數的IDE 開發環境中都在使用,已經成爲一種工程的編譯方法。
再看看官方文檔的定義:
You need a file called a makefile to tell make what to do. Most often, the makefile tells makehow to compile and link a program.
任何一種技能或知識都是源之於某種社會需求,那爲何要用Makefile呢?
當項目源文件不多的時候,咱們也許還能夠手動使用gcc命令來進行編譯,可是當項目發展到一個龐大的規模時,再手動敲gcc命令去編譯就變得不可能的事情。因此呢,在這樣的歷史背景下,就出現了某個大牛(斯圖亞特·費爾德曼),在某年(1977年)在某地(貝爾實驗室)製做了這樣一個軟件,它的名字就叫作make。
用一句話來講明爲啥用Makefile:爲了實現自動化(固然大多數場景都是用在自動化編譯中)。
另外在編譯過程當中,爲了節省時間,但願僅編譯修改過的文件,這也是Makefile在設計時一個重要的設計觀點。
Makefile包含五個東西:顯示規則,隱式規則,變量定義,文件指示,註釋。具體含義仍是直接引用網上的版本吧| | |
一、顯式規則。顯式規則說明了,如何生成一個或多的的目標文件。這是由Makefile的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。
二、隱式規則。因爲咱們的make有自動推導的功能,因此隱晦的規則可讓咱們比較粗糙地簡略地書寫Makefile,這是由make所支持的。
三、變量的定義。在Makefile中咱們要定義一系列的變量,變量通常都是字符串,這個有點你C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。
四、文件指示。其包括了三個部分,一個是在一個Makefile中引用另外一個Makefile,就像C語言中的include同樣;另外一個是指根據某些狀況指定Makefile中的有效部分,就像C語言中的預編譯#if同樣;還有就是定義一個多行的命令。有關這一部分的內容,我會在後續的部分中講述。
五、註釋。Makefile中只有行註釋,和UNIX的Shell腳本同樣,其註釋是用「#」字符,這個就像C/C++中的「//」同樣。若是你要在你的Makefile中使用「#」字符,能夠用反斜框進行轉義,如:「/#」。
最後,還值得一提的是,在Makefile中的命令,必需要以[Tab]鍵開始。
這裏說說規則(Rules):
target ... : prerequisites ...
command
...
...
一條規則由三部分組成,目標(target)、先決條件(prerequisites)、命令(commands)。
target也就是一個目標文件,能夠是Object File,也能夠是執行文件。還能夠是一個標籤(Label)。
prerequisites就是,要生成那個target所須要的文件或是目標。
command也就是make須要執行的命令。(任意的Shell命令)
這是一個文件的依賴關係,也就是說,target這一個或多個的目標文件依賴於prerequisites中的文件,其生成規則定義在command中。
說白一點就是說,prerequisites中若是有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。
這就是Makefile的規則。也就是Makefile中最核心的內容。
四個字,依賴關係
1. 依次讀取變量「MAKEFILES」定義的makefile文件列表
2. 讀取工做目錄下的makefile文件(根據命名的查找順序「GNUmakefile」,「makefile」,「Makefile」,首先找到那個就讀取那個)
3. 依次讀取工做目錄makefile文件中使用指示符「include」包含的文件
4. 查找重建全部已讀取的makefile文件的規則(若是存在一個目標是當前讀取的某一個makefile文件,則執行此規則重建此makefile文件,完成之後從第一步開始從新執行)
5. 初始化變量值並展開那些須要當即展開的變量和函數並根據預設條件肯定執行分支
6. 根據「終極目標」以及其餘目標的依賴關係創建依賴關係鏈表
7. 執行除「終極目標」之外的全部的目標的規則(規則中若是依賴文件中任一個文件的時間戳比目標文件新,則使用規則所定義的命令重建目標文件)
8. 執行「終極目標」所在的規則
模式規則其實也是普通規則,但它使用瞭如%這樣的通配符。以下面的例子:
%.o : %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
此規則描述了一個.o文件如何由對應的.c文件建立。規則的命令行中使用了自動化變量「$<」和「$@」,其中自動化變量「$<」表明規則的依賴,「$@」表明規則的目標。此規則在執行時,命令行中的自動化變量將根據實際的目標和依賴文件取對應值。
若是發現某變量在shell和makefile中未找不到其定義,那麼恭喜你,你極大可能遇到隱式規則了。固然隱式規則中的變量只是隱式規則的一部分。
1. 隱式規則中的變量
隱式規則中使用的變量分紅兩種:一種是命令相關的,如「CC」;一種是參數相關的,如「CFLAGS」。
1)與命令相關的變量
變量 |
含義 |
AR |
函數庫打開包程序。默認命令是「ar」 |
AS |
彙編語言編譯程序。默認命令是「as」 |
CC |
C語言編譯程序。默認命令是「cc」 |
CXX |
C++語言編譯程序。默認命令是「g++」 |
CO |
從RCS文件中擴展文件程序。默認命令是「co」 |
CPP |
C程序的預處理器(輸出是標準輸出設備)。默認命令是「$(CC) -E」 |
FC |
Fortran和Ratfor的編譯器和預處理程序。默認命令是」f77」 |
GET |
從SCCS文件擴展文件的程序。默認命令是「get」 |
LEX |
Lex方法分析器程序(針對於C或Ratfor)。默認命令是」lex」 |
PC |
Pascal語言編譯程序。默認命令是」pc」 |
YACC |
Yacc文法分析器(針對C程序)。默認命令是「yacc」 |
YACCR |
Yacc文法分析器(針對Ratfor程序)。默認命令是「yacc -r」 |
MAKEINFO |
轉換Texinfo源文件(.texi)到info文件程序。默認命令是「makeinfo」 |
TEX |
從TeX源文件建立TeX DVI文件的程序。默認命令是「tex」 |
WEAVE |
轉化Web到TeX的程序。默認命令是「weave」 |
TEXI2DVI |
從Texinfo源文件建立TeX DVI文件的程序。默認命令是「texi2dvi」 |
CWEAVE |
轉化C Web到TeX的程序。默認命令是「cweave」 |
TANGLE |
轉換Web到Pascal語言的程序,默認命令是」tangle「 |
CTANGLE |
轉換C Web到C。默認命令是」ctangle「 |
RM |
刪除文件命令。默認命令是」rm -f「 |
2)與參數相關的變量
變量 |
含義 |
ARFLAGS |
函數庫打包程序AR命令的參數。默認值是「rv」 |
ASFLAGS |
彙編語言編譯參數(當明顯地調用」.s」或」.S」文件時) |
CFLAGS |
C語言編譯器參數 |
CXXFLAGS |
C++語言編譯器參數 |
COFLAGS |
RCS命令參數 |
CPPFLAGS |
C預處理器參數(C和Fortran編譯器也會用到) |
FFLAGS |
Fortran語言編譯器參數 |
GFLAGS |
SCCS 」get「程序參數 |
LDFLAGS |
鏈接器參數(如「ld」) |
LFLAGS |
Lex文法分析器參數 |
PFLAGS |
Pascal語法編譯器參數 |
RFLAGS |
Ratfor程序的Fortran編譯器參數 |
YFLAGS |
Yacc文法分析器參數 |
2. 使用模式規則
可使用模式規則定義一個隱式規則。和通常規則相似,只是在模式規則中,目標的定義須要有「%」字符。「%」定義對文件名的匹配,表示任意長度的非空字符串。在依賴目標中一樣可使用「%」,只是依賴目標中「%」的取值,取決於其目標。
模式規則中「%」的展開和變量與函數的展開是有區別的,「%」的展開發生在變量和函數的展開以後。變量和函數的展開發生在make載入Makefile時,而「%」的展開則發生在運行時。 |
1) 模式規則舉例
模式規則中,至少在規則的目標中要包含「%」符號。
%.o : %.c ; <command ......>
其含義是,字指出了從全部的.c文件生成相應的.o文件的規則。若是要生成的目標是」a.o b.o」,那麼
%.c」就是」a.c b.c」。
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
表示把全部的.c文件都編譯成.o文件。
其中,「$@」表示全部目標的集合,」$<」表示全部依賴目標的集合(在模式定義規則的情形下)。
2) 自動化變量
自動化變量只應出如今規則的命令中。
變量 |
含義 |
$@ |
表示規則中的全部目標文件的集合。在模式規則中若是有多個目標,「$@」就是匹配於目標中模式定義的集合 |
$% |
僅當目標是函數庫文件時,表示規則中的目標成員名,若是目標不是函數庫文件(UNIX下是 .a,Windows是.lib),其值爲空。 |
$< |
依賴目標中的第一個目標名字,若是依賴目標是以模式(即」%「)定義的,則」$<」是符合模式的一系列的文件集 |
$? |
全部比目標新的依賴目標的集合,以空格分隔 |
$^ |
全部依賴目標的集合,以空格分隔。如若是在依賴目標中有多個重複的,則自動去除重複的依賴目標,只保留一份 |
$+ |
同」$^」,也是全部依賴目標的集合,只是它不去除重複的依賴目標。 |
$* |
目標模式中「%」及其以前的部分 |
$(@D) |
「$@」的目錄部分(不以斜槓做爲結尾),若是」$@」中沒有包含斜槓,其值爲「.」(當前目錄) |
$(@F) |
「$@」的文件部分,至關於函數」$(notdir $@)」 |
$(*D) |
同」$(@D)」,取文件的目錄部分 |
$(*F) |
同」$(@F)」,取文件部分,但不取後綴名 |
$(%D) |
函數包文件成員的目錄部分 |
$(%F) |
函數包文件成員的文件名部分 |
$(<D) |
依賴目標中的第一個目標的目錄部分 |
$(<F) |
依賴目標中的第一個目標的文件名部分 |
$(^D) |
全部依賴目標文件中目錄部分(無相同的) |
$(^F) |
全部依賴目標文件中文件名部分(無相同的) |
$(+D) |
全部依賴目標文件中的目錄部分(能夠有相同的) |
$(+F) |
全部依賴目標文件中的文件名部分(能夠有相同的) |
$(?D) |
全部被更新文件的目錄部分 |
$(?F) |
全部被更新文件的文件名部分 |
這些稀奇古怪的符號是前面隱式規則中出現過,單獨拎出來是由於咱們會常常用到它們。
這些符號也就是咱們常說的自動變量:
$@ :規則中的目標集
$^ :規則中的全部先決條件
$< :表示規則中的第一個先決條件
再來講說$VAR和$$VAR的區別:
makefile文件中的規則絕大部分都是使用shell命令來實現的,這裏就涉及到了變量的使用,包括makefile中的變量和shell命令範疇內的變量。在makefile的規則命令行中使用$var就是在命令中引用makefile的變量,這裏僅僅是讀取makefile的變量而後擴展開,將其值做爲參數傳給了一個shell命令;而$$var是在訪問一個shell命令內定義的變量,而非makefile的變量。若是某規則有n個shell命令行構成,而相互之間沒有用';'和'\'鏈接起來的話,就是相互之間沒有關聯的shell命令,相互之間也不能變量共享。
使用其緣由一:避免和同名文件衝突
在現實中不免存在所定義的目標與所存在的目標是同名的,採用Makefile如何處理這種狀況呢?Makefile中的假目標(phony target)能夠解決這個問題。
假目標可使用.PHONY關鍵字進行聲明,對於假目標,能夠想象,由於不依賴於某文件,make該目標的時候,其所在規則的命令都會被執行。
若是編寫一個規則,並不產生目標文件,則其命令在每次make 該目標時都執行。
例如:
clean:
rm *.o temp
由於"rm"命令並不產生"clean"文件,則每次執行"make clean"的時候,該命令都會執行。若是目錄中出現了"clean"文件,則規則失效了:沒有依賴文件,文件"clean"始終是最新的,命令永遠不會執行;爲避免這個問題,可以使用".PHONY"指明該目標。如:
.PHONY : clean
這樣執行"make clean"會無視"clean"文件存在與否。
已知phony 目標並不是是由其它文件生成的實際文件,make 會跳過隱含規則搜索。這就是聲明phony 目標會改善性能的緣由,即便你並不擔憂實際文件存在與否。
完整的例子以下:
.PHONY : clean
clean :
rm *.o temp
使用其緣由二:提升執行make的效率
當一個目標被聲明爲僞目標後,make在執行此規則時不會試圖去查找隱含規則來建立這個目標。這樣也提升了make的執行效率,同時咱們也不用擔憂因爲目標和文件名重名而使咱們的指望失敗。
面試中常常被問到的問題: [=]和[:=]符號的區別。
=
能夠先使用後定義,這就致使makefile在所有展開後才能決定變量的值。
有可能出現循環遞歸,沒法暫開的問題。
:=
必須先定義而後再使用,在當前的位置就能夠決定變量的值。
再補充兩種符號?= 、+=,若是熟悉C語言那對這兩種符號理解會很容易。
?=
至關於選擇疑問句,若是前面的變量沒被賦值,那就作賦值操做
+=
至關於遞加操做
看一個例子就明白了。新建一個Makefile,內容以下:
ifdef DEFINE_VRE VRE = 「Hello World!」 else endif ifeq ($(OPT),define) VRE ?= 「Hello World! First!」 endif ifeq ($(OPT),add) VRE += 「Kelly!」 endif ifeq ($(OPT),recover) VRE := 「Hello World! Again!」 endif all: @echo $(VRE)
敲入如下make命令:
make DEFINE_VRE=true OPT=define 輸出:Hello World!
make DEFINE_VRE=true OPT=add 輸出:Hello World! Kelly!
make DEFINE_VRE=true OPT=recover 輸出:Hello World! Again!
make DEFINE_VRE= OPT=define 輸出:Hello World! First!
make DEFINE_VRE= OPT=add 輸出:Kelly!
make DEFINE_VRE= OPT=recover 輸出:Hello World! Again!
如下,摘止採銅大大的《開放的智力》,獻給那些不甘平庸的人。
在今天這個時代,不少人瘋狂地追求着不少東西,卻沒有反思過本身爲何要追求這些。咱們爭先恐後地買房,買車,咱們笑談着「賣腎」買iPhone,咱們爲各類名牌神魂顛倒,咱們在微博、微信這些社交媒體上八面玲瓏,咱們在知乎上寫答案而後等着贊同票一點點刷上去。這些都構成了米蘭·昆德拉筆下的「媚俗」的生活。
在一個鐘鼓齊鳴的地方,你會失去駐足傾聽的能力;在一個霓虹閃爍的地方,你就沒法發現事物自己的光澤;在一我的流如梭的鬧市,你會忘記本來行進的方向。以旁人的眼光做爲本身人生的參考線,你會爲追逐一個心裏並不想要的東西,而氣喘吁吁地奔跑;更糟糕的是,你的人生會被割裂開來,變成不少碎片,每一塊碎片都用來討好特定的一羣人。也許,以一個較小的概率,你最終得到了世俗意義上的成功,但這種成功也極可能是平庸的。若是你作不成本身,再大的成功又有何意義?
趨同的益處簡單而直接,你能夠很容易的融入一個集體,與別人有更多共同的興趣,聊天時有更多的談資,得到長輩更多的稱讚,甚至,你會成爲一個小羣體中的明星。而它的代價是緩慢而隱蔽的,它會讓你不去努力發現本身的天賦和才能,讓你失去變得不同凡響的全部可能性。曾經有一個日本小說家,從小學開始,成績就很很差,是老師同窗眼中的大笨蛋,固然他本身也這麼以爲。不過他很早就有一個愛好,就是喜歡看小說。到了高一時,他的成績在整年級四百多人中都排到了倒數幾名,可他對此倒是不急不躁,不只不迎頭遇上,而是變本加厲,開始作一件同窗老師家長玩玩沒想到的事:寫小說。天天晚上,他用小夥伴們作做業的時間伏案寫做,一上來就是長篇,一下就是幾十萬字,這樣寫了多年,遭遇了幾回挫折以後,終於成名,他是東野圭吾。
因此,心裏堅決的人,歷來不忌憚作一些不尋常之事。那些在別人看來瘋狂的舉動,對本身來講卻多是最好最安寧的選擇。逃脫獻媚於他人的牢籠,才能真正得到自由。