乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      GNU make中文手冊(cè)-第四章:Makefile的規(guī)則

       todaytomo 2006-12-30
      GNU make中文手冊(cè)-第四章:Makefile的規(guī)則 作者: hew  發(fā)布日期: 2006-3-21    查看數(shù): 149   出自: http://www.
      第四章:Makefile的規(guī)則

      --------------------------------------------------------------------------------

      本章我們開始討論Makefile的一個(gè)重要內(nèi)容,Makefile的規(guī)則。

      Makefile中,規(guī)則描述了何種情況下使用什么命令來重建一個(gè)特定的文件,此文件被稱為規(guī)則“目標(biāo)”(通常規(guī)則中的目標(biāo)只有一個(gè))。規(guī)則所羅列的其他文件稱為“目標(biāo)”的依賴,而規(guī)則中的命令是用來更新或者創(chuàng)建此規(guī)則的目標(biāo)。

      除了makefile的“終極目標(biāo)”所在的規(guī)則以外,其他規(guī)則的順序在makefile文件中沒有意義。“終極目標(biāo)”就是當(dāng)沒有使用make 命令行指定具體目標(biāo)時(shí),make默認(rèn)的哪一個(gè)目標(biāo)。它是makefile文件中第一個(gè)規(guī)則的目標(biāo)。如果在makefile中第一個(gè)規(guī)則有多個(gè)目標(biāo)的話,那 么多個(gè)目標(biāo)中的第一個(gè)將會(huì)被作為make的“終極目標(biāo)”。有兩種情況的例外:1. 目標(biāo)名是以點(diǎn)號(hào)“.”開始的其后不存在斜線“/”(“./”被認(rèn)為是當(dāng)前目錄;“../”被認(rèn)為是上一級(jí)目錄);2. 作為模式規(guī)則的目標(biāo)。此兩種情況的Makefile的第一個(gè)目標(biāo)都不會(huì)被作為“終極目標(biāo)”來對(duì)待。

      “終極目標(biāo)”是執(zhí)行make的唯一目的,其所在的規(guī)則作為第一個(gè)規(guī)則。而其他的規(guī)則是在完成重建“終極目標(biāo)”的過程中被連帶出來的。所以這些目標(biāo)所在規(guī)則在Makefile中的順序無關(guān)緊要。

      因此,我們書寫的makefile的第一個(gè)規(guī)則應(yīng)該就是重建整個(gè)程序或者多個(gè)程序的依賴關(guān)系和執(zhí)行命令的描述。

      4.1 一個(gè)例子
      我們來看一個(gè)規(guī)則的例子:



      foo.o : foo.c defs.h # module for twiddling the frobs

      cc -c -g foo.c



      這是一個(gè)典型的規(guī)則??吹竭@個(gè)例子,大家也許能夠說出這個(gè)規(guī)則的各個(gè)部分之間的關(guān)系。不過我們還是要把這個(gè)例子拿出來討論。目的是讓我們更加明確的理解 Makefile的規(guī)則。本例第一行中,文件“foo.o”是規(guī)則需要重建的文件,而“foo.c”和“defs.h”是重建“foo.o”所要使用的文 件。我們把規(guī)則所需要重建的文件稱為規(guī)則的“目標(biāo)”(foo.o),而把重新目標(biāo)所需要的文件稱為“目標(biāo)”的“依賴”。規(guī)則中的第二行“cc -c -g foo.c”就是規(guī)則的“命令”。它描述了如何使用規(guī)則中的依賴文件重建目標(biāo)。

      而且,上面的規(guī)則告訴我們了兩件事:

      1. 如何確定目標(biāo)文件是否過期(需要重建目標(biāo)),過期是指目標(biāo)文件不存在或者目標(biāo)文件“foo.o”在時(shí)間戳上比依賴文件中的任何一個(gè)“foo.c”或者“defs.h”“老”。

      2. 如何重建目標(biāo)文件“foo.o”。這個(gè)規(guī)則中使用cc編譯器。在命令中沒有明確的使用到依賴文件“defs.h”。我們假設(shè)在源文件“foo.c”中已經(jīng)包含了此頭文件。這也是為什么它作為目標(biāo)依賴出現(xiàn)的原因。

      4.2 規(guī)則語法
      通常規(guī)則的語法格式如下:



      TARGETS : PREREQUISITES

      COMMAND

      ...



      或者是這樣:



      TARGETS : PREREQUISITES ; COMMAND

      COMMAND

      ...



      規(guī)則中“TARGETS”可以是空格分開的多個(gè)文件名,也可以是一個(gè)標(biāo)簽(執(zhí)行清空的“clean”)。“TARGETS”的文件名可以使用通配符,格式 “A(M)”表示檔案文件(Linux下的靜態(tài)庫.a文件)的成員“M”(關(guān)于靜態(tài)庫的重建可參考 第十章 使用make更新靜態(tài)庫文件)。通常規(guī)則只有一個(gè)目標(biāo)文件(建議這么做),偶爾會(huì)在一個(gè)規(guī)則中需要多個(gè)目標(biāo)。

      書寫規(guī)則是我們需要注意的幾點(diǎn):

      1. 規(guī)則的命令部分有兩種書寫方式:a. 命令可以和目標(biāo):依賴描述放在同一行。命令在依賴文件列表后并使用分號(hào)(;)和依賴文件列表分開。b. 命令在目標(biāo):依賴的描述的下一行,作為獨(dú)立的命令行。當(dāng)作為獨(dú)立的命令行時(shí)此行必須以[Tab]字符開始。在Makefile中,在第一個(gè)規(guī)則之后出現(xiàn)的 所有以[Tab]字符開始的行都會(huì)被當(dāng)作命令來處理。

      2. Makefile中對(duì)“$”有特殊的含義(表示變量或者函數(shù)的引用),如果我們的規(guī)則如果需要“$”,需要書寫兩個(gè)連續(xù)的(“$$”)。

      3. 在前邊我們也提到過,Makefile一個(gè)較長的行,可以使用反斜線“\”將其書寫到幾個(gè)獨(dú)立的物理行上。雖然make對(duì)Makefile文本行的最大長 度是沒有限制的,但是還是建議這樣做。不僅書寫方便而且更有利于別人的閱讀(這也是一個(gè)程序員修養(yǎng)的體現(xiàn))。

      一個(gè)規(guī)則告訴“make”兩件事:1. 目標(biāo)在什么情況下已經(jīng)過期; 2. 在需要重建目標(biāo)的時(shí)候,怎么樣去重建這個(gè)目標(biāo)。目標(biāo)是否過期是由那些使用空格分開的規(guī)則的依賴文件所決定的。當(dāng)目標(biāo)文件不存在或者目標(biāo)文件的最后修改時(shí)間 比依賴文件中的任何一個(gè)都晚,則目標(biāo)就會(huì)被創(chuàng)建或者重建。也就是說執(zhí)行規(guī)則命令行的前提條件是:1。 目標(biāo)文件不存在; 2. 存在一個(gè)依賴的最后修改時(shí)間比目標(biāo)的最后修改時(shí)間晚。規(guī)則的中心思想就是:目標(biāo)文件的內(nèi)容是由依賴文件文件決定,依賴文件的任何一處改動(dòng),將導(dǎo)致目前已經(jīng) 存在的目標(biāo)文件的內(nèi)容過期。規(guī)則的命令為重建目標(biāo)提供了方法。這些命令運(yùn)行在系統(tǒng)shell之上。

      4.3 依賴的類型
      GNU make的規(guī)則中可以使用兩種不同類型的依賴:1. 在以前章節(jié)所提到的規(guī)則中使用的是常規(guī)依賴,這是我們書寫的Makefile規(guī)則中最常用的一種。2. 另外一種在我們書寫Makefile時(shí)不會(huì)經(jīng)常使用,它比較特殊、稱之為“order-only”依賴。一個(gè)規(guī)則的常規(guī)依賴(通常是多個(gè)依賴文件)表明了 兩件事:首先,它決定了重建規(guī)則目標(biāo)所要執(zhí)行命令的順序;表明在更新這個(gè)規(guī)則的目標(biāo)(執(zhí)行此規(guī)則的命令行)之前必需要按照什么樣的順序、執(zhí)行那些命令來重 建這些依賴文件(對(duì)所有依賴文件的重建,使用明確或者隱含規(guī)則。就是說對(duì)于這樣的規(guī)則:A:B C,那么在重建目標(biāo)A之前,首先需要完成對(duì)它的依賴文件B和C的重建。重建B和C的過程就是執(zhí)行Makefile中文件B和C所在的規(guī)則)。其次,它確定 了一個(gè)依存關(guān)系;規(guī)則中如果依賴文件的任何一個(gè)比目標(biāo)文件新,則被認(rèn)為規(guī)則的目標(biāo)已經(jīng)過期同時(shí)需要重建目標(biāo)。

      通常,如果規(guī)則中依賴文件中的任何一個(gè)被更新,則規(guī)則的目標(biāo)相應(yīng)地也應(yīng)該被更新。

      有時(shí),我們需要定義一個(gè)這樣的規(guī)則,在更新目標(biāo)(目標(biāo)文件已經(jīng)存在)時(shí)只需要根據(jù)依賴文件中的部分來決定目標(biāo)是否需要被重建,而不是在依賴文件的任何一個(gè) 被修改后都重建目標(biāo)。為了實(shí)現(xiàn)這個(gè)目的,我們需要對(duì)依賴進(jìn)行分類,一類是這些依賴文件的更新需要對(duì)應(yīng)更新目標(biāo)文件,另一類是這些依賴的更新不會(huì)導(dǎo)致目標(biāo)被 重建。第二類的依賴我們就稱他為:“order-only”依賴。在書寫規(guī)則時(shí),“order-only”依賴使用管道符號(hào)“|”開始,作為目標(biāo)的一個(gè)依 賴文件。規(guī)則的依賴列表中管道符號(hào)“|”左邊的是常規(guī)依賴文件,所有出現(xiàn)在管道符號(hào)右邊的就是“order-only”依賴。這樣的規(guī)則書寫格式如下:



      TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES



      規(guī)則中常規(guī)依賴文件可以是空。允許對(duì)一個(gè)目標(biāo)聲明多行按正確順序依次追加的依賴。需要注意:規(guī)則依賴文件中如果一個(gè)文件被同時(shí)聲明為常規(guī)依賴和 “order-only”依賴,那么此文件被作為常規(guī)依賴處理(因?yàn)槌R?guī)依賴所實(shí)現(xiàn)的動(dòng)作是“order-only”依賴所實(shí)現(xiàn)的動(dòng)作的一個(gè)超集)。



      “order-only”依賴的使用舉例:

      LIBS = libtest.a

      foo : foo.c | $(LIBS)

      $(CC) $(CFLAGS) $< -o $@ $(LIBS)

      make在執(zhí)行這個(gè)規(guī)則時(shí),如果目標(biāo)文件“foo”已經(jīng)存在。當(dāng)“foo.c”被修改以后,目標(biāo)“foo”將會(huì)被重建,但是當(dāng)“libtest.a”被修改以后。將不執(zhí)行規(guī)則的命令來重建目標(biāo)“foo”。

      就是說,規(guī)則中依賴文件$(LIBS)只有在目標(biāo)文件不存在的情況下,才會(huì)參與規(guī)則的執(zhí)行。當(dāng)目標(biāo)文件存在時(shí)此依賴不會(huì)參與規(guī)則的執(zhí)行過程。

      4.4 文件名使用通配符
      Maekfile中表示一個(gè)單一的文件名時(shí)可使用通配符??墒褂玫耐ㄅ浞校?#8220;*”、“?”和“[…]”。在Makefile中通配符的用法和含義和 Linux(unix)的Bourne shell完全相同。例如,“*.c”代表了當(dāng)前工作目錄下所有的以“.c”結(jié)尾的文件等。但是在Makefile中這些統(tǒng)配符并不是可以用在任何地方, Makefile中統(tǒng)配符可以出現(xiàn)在以下兩種場合:

      1. 可以用在規(guī)則的目標(biāo)、依賴中,此時(shí)make會(huì)自動(dòng)將其展開;

      2. 可出現(xiàn)在規(guī)則的命令中,其展開是在shell在執(zhí)行此命令時(shí)完成。

      除這兩種情況之外的其它上下文中,不能直接使用通配符。二是需要通過函數(shù)“wildcard”(可參考 7.3 文件名處理函數(shù) 一節(jié))來實(shí)現(xiàn)。

      如果規(guī)則中的某一個(gè)文件的文件名包含作為統(tǒng)配符的字符(“*”、“.”字符),在使用文件時(shí)需要對(duì)文件名中的統(tǒng)配字符進(jìn)行轉(zhuǎn)義處理,使用反斜線(\)來進(jìn) 行通配符的轉(zhuǎn)義。例如“foo\*bar”,在Makefile中它表示了文件“foo*bar”。Makefile中對(duì)一些特殊字符的轉(zhuǎn)移和B- SHELL以及C語言中的基本上相同。

      另外需要注意:在Linux(unix)中,以波浪線“~”開始的文件名有特殊含義。

      單獨(dú)使用它或者其后跟一個(gè)斜線(~/),代表了當(dāng)前用戶的宿主目錄。(在shell下可以通過命令“echo ~(~\)”來查看)。例如“~/bin”代表“/home/username/bin/”(當(dāng)前用戶宿主目錄下的bin目錄)

      波浪線之后跟一個(gè)單詞(~word),其代表由這個(gè)“word”所指定的用戶的宿主目錄。例如“~john/bin”就是代表用戶john的宿主目錄下的bin目錄。

      在一些系統(tǒng)中(像MS-DOS和MS-Windows),用戶沒有各自的宿主目錄,此情況下可通過設(shè)置環(huán)境變量“HOME”來模擬。

      4.4.1 統(tǒng)配符使用舉例
      本節(jié)開始已經(jīng)提到過,通配符可被用在規(guī)則的命令中,它是在命令被執(zhí)行時(shí)由shell進(jìn)行處理的。例如Makefile的清空過程文件規(guī)則:



      clean:

      rm -f *.o



      通配符也可以用在規(guī)則的依賴文件名中??纯聪旅孢@個(gè)例子。執(zhí)行“make print”,執(zhí)行的結(jié)果是打印當(dāng)前工作目錄下所有的在上一次打印以后被修改過的“.c”文件。



      print: *.c

      lpr -p $?

      touch print



      兩點(diǎn)說明:1. 上述的規(guī)則中目標(biāo)“print”時(shí)一個(gè)空目標(biāo)文件。(不存在一個(gè)這樣的文件,此目標(biāo)不代表一個(gè)文件,它只是記錄了一個(gè)所要執(zhí)行的動(dòng)作或者命令。參考 4.8 空目標(biāo)文件 一節(jié))。2. 自動(dòng)環(huán)變量“$?”用在這里表示依賴文件列表中被改變過的所有文件。

      變量定義中使用的通配符不會(huì)被展開(因此在定義變量不能按照這這種方式,下一小節(jié)將會(huì)詳細(xì)討論)。如果Makefile有這樣一句:“objects = *.o”。那么變量“objects”的值就是“*.o”,而不是使用空格分開的所有.o文件列表。如果需要變量“objects”代表所有的.o文件, 則需要是用函數(shù)“wildcard”來實(shí)現(xiàn)(objects = $(wildcar *.o))。

      4.4.2 通配符存在的缺陷
      上一小節(jié)已經(jīng)提到過在變量定義時(shí)使用通配符可能會(huì)導(dǎo)致意外的結(jié)果。本小節(jié)將此詳細(xì)地分析和討論。在書寫Makefile時(shí),可能存在這種不正確使用通配符 的方法。這種看似正確的方式產(chǎn)生的結(jié)果可能并非你所期望得到的。假如在你的makefile中,期望能夠根據(jù)所有的.o文件生成可執(zhí)行文件“foo”。實(shí) 現(xiàn)如下:



      objects = *.o



      foo : $(objects)

      cc -o foo $(CFLAGS) $(objects)



      這里變量“objects”的值是一個(gè)字符串“*.o”。在重建“foo”的規(guī)則中對(duì)變量“objects”進(jìn)行展開,目標(biāo)“foo”的依賴就是 “*.o”,即所有的.o文件的列表。如果工作目錄下已經(jīng)存在必需的.o文件,那么這些.o文件將成為目標(biāo)的依賴文件,目標(biāo)“foo”將根據(jù)規(guī)則被重建。

      但是如果我們將工作目錄下所有的.o文件刪除,在執(zhí)行規(guī)則時(shí)將會(huì)得到一個(gè)類似于“沒有創(chuàng)建*.o文件的規(guī)則” 的錯(cuò)誤提示。這當(dāng)然不是我們所期望的結(jié)果(可能在出現(xiàn)這個(gè)錯(cuò)誤時(shí)會(huì)令你感到萬分迷惑!)。為了實(shí)現(xiàn)們的初衷,在對(duì)變量進(jìn)行定義的時(shí)需要使用一些高級(jí)的技 巧,包括使用“wildcard”函數(shù)和實(shí)現(xiàn)字符串的置換。關(guān)于如何實(shí)現(xiàn)字符串的置換,我們將在后續(xù)進(jìn)行詳細(xì)地討論。

      4.4.3 函數(shù)wildcard
      前邊提到過,在規(guī)則中,通配符會(huì)被自動(dòng)展開。但在變量的定義和使用函數(shù)是,通配符不會(huì)被自動(dòng)的展開。這種情況下需要通配符有效,要用到函數(shù) “wildcard”,其用法:$(wildcard PATTERN...) ;在Makefile中,它被展開未已經(jīng)存在的、空格分割的、匹配此模式的所有文件列表。如果不存在符合此模式的文件,那么函數(shù)會(huì)忽略模式并返回空。需要 注意的是:這種情況下規(guī)則中通配符的展開和上一小節(jié)匹配通配符的區(qū)別。

      一般我們可以使用“$(wildcard *.c)”來獲取工作目錄下的所有的.c文件列表。復(fù)雜一些用法;可以使用“$(patsubst %.c,%.o,$(wildcard *.c))”,首先使用“wildcard”函數(shù)獲取工作目錄下的.c文件列表;之后將列表中所有文件名的后綴.c替換為.o。這樣我們就可以得到在當(dāng)前 目錄可生成的.o文件列表。因此在一個(gè)目錄下可以使用如下內(nèi)容的Makefile來將工作目錄下的所有的.c文件進(jìn)行編譯并最后連接成為一個(gè)可執(zhí)行文件:



      #sample Makefile

      objects := $(patsubst %.c,%.o,$(wildcard *.c))



      foo : $(objects)

      cc -o foo $(objects)



      這里我們使用了make的隱含規(guī)則來編譯.c的源文件。對(duì)變量的賦值也用到了一個(gè)特殊的符號(hào)(:=)。關(guān)于變量定義可參考 5.2 兩種變量定義 一節(jié)。函數(shù)“patsubst”可參考 7.2 文本處理函數(shù) 一節(jié)



      4.5 目錄搜尋
      在一個(gè)較大的工程中,一般會(huì)將源代碼和二進(jìn)制文件(.o文件和可執(zhí)行文件)安排在不同的目錄來進(jìn)行區(qū)分管理。這種情況下,我們需要使用make提供的目錄 自動(dòng)搜索依賴文件功能(在指定的若干個(gè)目錄下搜索依賴文件)。書寫makefile時(shí),指定依賴文件的搜索目錄。當(dāng)工程的目錄結(jié)構(gòu)發(fā)生變化時(shí),我們就可以 不更改Makefile的規(guī)則,而只更改依賴文件的搜索目錄。

      本節(jié)我們將詳細(xì)討論在書寫Makefile時(shí)如何使用這一特性。在自己的工程中靈活運(yùn)用這一特性,將會(huì)起到事半功倍的效果。

      4.5.1 一般搜索(變量VPATH)
      make可識(shí)別一個(gè)特殊變量“VPATH”。通過變量“VPATH”可以指定依賴文件的搜索路徑,在規(guī)則的依賴文件在當(dāng)前目錄不存在時(shí),make會(huì)在此變 量所指定的目錄下去尋找這些依賴文件。一般我們都是用此變量來說明規(guī)則中的依賴文件的搜索路徑。其實(shí)“VPATH”變量所指定的是Makefile中所有 文件的搜索路徑,包括依賴文件和目標(biāo)文件。

      變量“VPATH”的定義中,使用空格或者冒號(hào)(:)將多個(gè)目錄分開。make搜索的目錄順序按照變量“VPATH”定義中順序進(jìn)行(當(dāng)前目錄永遠(yuǎn)是第一搜索目錄)。例如對(duì)變量的定義如下:



      VPATH = src:../headers



      它指定了兩個(gè)搜索目錄,“src”和“../headers”。對(duì)于規(guī)則“foo:foo.c”如果“foo.c”在“src”目錄下,此時(shí)此規(guī)則等價(jià)于“foo:src:/foo.c”。

      通過“VPATH”變量指定的路徑在Makefile中對(duì)所有文件有效。當(dāng)需要為不類型的文件指定不同的搜索目錄時(shí),需要使用另外一種方式。下一小節(jié)我們將會(huì)討論這種更高級(jí)的方式。

      4.5.2 選擇性搜索(關(guān)鍵字vpath)
      另一個(gè)設(shè)置文件搜索路徑的方法是使用make的“vpath”關(guān)鍵字(全小寫的)。它不是一個(gè)變量,是一個(gè)make的關(guān)鍵字,它所實(shí)現(xiàn)的功能和上一小節(jié)提 到的“VPATH”變量很類似,但是它更為靈活。它可以為不同類型的文件(由文件名區(qū)分)指定不同的搜索目錄。它的使用方法有三種:

      1、vpath PATTERN DIRECTORIES

      為符合模式“PATTERN”的文件指定搜索目錄“DIRECTORIES”。多個(gè)目錄使用空格或者冒號(hào)(:)分開。類似上一小節(jié)的“VPATH”

      2、vpath PATTERN

      清除之前為符合模式“PATTERN”的文件設(shè)置的搜索路徑。

      3、vpath

      清除所有已被設(shè)置的文件搜索路徑。



      vapth使用方法中的“PATTERN”需要包含模式字符“%”。“%”意思是匹配一個(gè)或者多個(gè)字符,例如,“%.h”表示所有以“.h”結(jié)尾的文件。 如果在“PATTERN”中沒有包含模式字符“%”而是一個(gè)明確的文件名,就是指出了此文件所在的目錄,我們很少使用這種方式來為單獨(dú)的一個(gè)文件指定搜索 路徑。在“vpath”所指定的模式中我們可以使用反斜杠來對(duì)字符“%”的引用(和其他的特使字符的引用一樣)。

      “PATTERN”表示了具有相同特征的一類文件,而“DIRECTORIES”則指定了搜索此類文件目錄。當(dāng)規(guī)則的依賴文件列表中出現(xiàn)的文件不能在當(dāng)前目錄下找到時(shí),make程序?qū)⒁来卧?#8220;DIRECTORIES”所描述的目錄下尋找此文件。例如:



      vpath %.h ../headers



      其含義是:Makefile中出現(xiàn)的.h文件;如果不能在當(dāng)前目錄下找到,則到目錄“../headers”下尋找。注意:這里指定的路徑僅限于在 Makefile文件內(nèi)容中出現(xiàn)的.h文件。 并不能指定源文件中包含的頭文件所在的路徑(在.c源文件中所包含的頭文件需要使用GCC的命令行來說明)。

      在Makefile中如果連續(xù)的多個(gè)vpath語句中使用了相同的“PATTERN”,make就對(duì)這些vpath語句一個(gè)一個(gè)進(jìn)行處理,搜索某種模式文 件的目錄將是所有的通過vpath指定的符合此模式的目錄,其搜索目錄的順序由vpath語句在Makefile出現(xiàn)的先后次序來決定。多個(gè)具有相同 “PATTERN”的vpath語句之間相互獨(dú)立。下邊是兩種方式下,所有的.c文件的查找目錄的順序(不包含工作目錄,對(duì)工作目錄的搜索永遠(yuǎn)處于最優(yōu)先 地位)比較:

      vpath %.c foo

      vpath % blish

      vpath %.c bar



      表示對(duì)所有的.c文件,make依次查找目錄:“foo”、blish”、“bar”。

      而:

      vpath %.c foo:bar

      vpath % blish



      對(duì)于所有的.c文件make將依次查找目錄:“foo”、“bar”、“blish”

      4.5.3 目錄搜索的機(jī)制
      規(guī)則中一個(gè)依賴文件可以通過目錄搜尋找到(使用前邊提到的一般搜索或者是選擇性搜索任一種),但是有可能此文件的完整路徑名(文件的相對(duì)路徑或者絕對(duì)路 徑,如:/home/Stallman/foo.c)卻并不是規(guī)則中列出的依賴(規(guī)則“foo : foo.c”,在執(zhí)行搜索后可能得到的依賴文件為:“../src/foo.c”。目錄“../src”是使用“VPATH”或“vpath”指定的); 因此使用目錄搜索所到的完整的文件路徑名可能需要廢棄。make在解析Makefile文件執(zhí)行規(guī)則時(shí)對(duì)文件路徑保存或廢棄所依據(jù)的算法如下:

      1. 首先,如果規(guī)則的目標(biāo)文件在Makefile文件所在的目錄(工作目錄)下不存在,那么就執(zhí)行目錄搜尋。

      2. 如果目錄搜尋成功,在指定的目錄下存在此規(guī)則的目標(biāo)。那么搜索到的完整的路徑名就被作為臨時(shí)的目標(biāo)文件被保存。

      3. 對(duì)于規(guī)則中的所有依賴文件使用相同的方法處理。

      4. 完成第三步的依賴處理后,make程序就可以決定規(guī)則的目標(biāo)是否需要重建,兩種情況時(shí)后續(xù)處理如下:

      a) 規(guī)則的目標(biāo)不需要重建:那么通過目錄搜索得到的所有完整的依賴文件路徑名有效,同樣,規(guī)則的目標(biāo)文件的完整的路徑名同樣有效。就是說,當(dāng)規(guī)則的目標(biāo)不需要被重建時(shí),規(guī)則中的所有的文件完整的路徑名有效。已經(jīng)存在的目標(biāo)文件所在的目錄不會(huì)被改變。

      b) 規(guī)則的目標(biāo)需要重建:那么通過目錄搜索所得到的目標(biāo)文件的完整的路徑名無效,規(guī)則中的目標(biāo)文件將會(huì)被在工作目錄下重建。就是說,當(dāng)規(guī)則的目標(biāo)需要重建時(shí), 規(guī)則的目標(biāo)文件會(huì)在工作目錄下被重建,而不是在目錄搜尋時(shí)所得到的目錄。這里,必須明確:此種情況只有目標(biāo)文件的完整路徑名失效,依賴文件的完整路徑名是 不會(huì)失效的。否則將無法重建目標(biāo)。

      該算法看起來比較法雜,但它確實(shí)使make實(shí)現(xiàn)了我們所需要的東西。此算法使用純粹的語言描述可能顯得晦澀。本小節(jié)后續(xù)將使用一個(gè)例子來說明。使大家能夠 對(duì)此算法有明確的理解。對(duì)于其他版本的make則使用了一種比較簡單的算法:如果規(guī)則的目標(biāo)文件的完整路徑名存在(通過目錄搜索可以定位到目標(biāo)文件),無 論該目標(biāo)是否需要重建,都使用搜索到的目標(biāo)文件完整路徑名。

      實(shí)際上,GNU make也可以實(shí)現(xiàn)這種功能。如果需要make在執(zhí)行時(shí),將目標(biāo)文件在已存在的目錄存下進(jìn)行重建,我們可以使用“GPATH”變量來指定這些目標(biāo)所在的目 錄。“GPATH”變量和“VPATH”變量具有相同的語法格式。make在執(zhí)行時(shí),如果通過目錄搜尋得到一個(gè)過時(shí)的完整的目標(biāo)文件路徑名,而目標(biāo)存在的 目錄又出現(xiàn)在“GPATH”變量的定義列表中,則該目標(biāo)的完整路徑將不廢棄,目標(biāo)將在該路徑下被重建。

      為了更清楚地描述此算法,我們使用一個(gè)例子來說明。存在一個(gè)目錄“prom”,“prom”的子目錄“src”下存在“sum.c”和“memcp.c”兩個(gè)源文件。在“prom”目錄下的Makefile部分內(nèi)容如下:



      LIBS = libtest.a

      VPATH = src



      libtest.a : sum.o memcp.o

      $(AR) $(ARFLAGS) $@ $^



      首先,如果在兩個(gè)目錄(“prom”和“src”)都不存在目標(biāo)“libtest.a”,執(zhí)行make時(shí)將會(huì)在當(dāng)前目錄下創(chuàng)建目標(biāo)文件“libtest.a”。另外;如果“src”目錄下已經(jīng)存在“libtest.a”,以下兩種不同的執(zhí)行結(jié)果:

      1) 當(dāng)它的兩個(gè)依賴文件“sum.c”和“memcp.c”沒有被更新的情況下我們執(zhí)行make,首先make程序會(huì)搜索到目錄“src”下的已經(jīng)存在的目標(biāo) “libtest.a”。由于目標(biāo)“libtest.a”的依賴文件沒有發(fā)生變化,所以不會(huì)重建目標(biāo)。并且目標(biāo)所在的目錄不會(huì)發(fā)生變化。

      2) 當(dāng)我們修改了文件“sum.c”或者“memcp.c”以后執(zhí)行make。“libtest.a”和“sum.o”或者“memcp.o”文件將會(huì)被在當(dāng) 前目錄下創(chuàng)建(目標(biāo)完整路徑名被廢棄),而不是在“src”目錄下更新這些已經(jīng)存在的文件。此時(shí)在兩個(gè)目錄下(“prom”和“src”)同時(shí)存在文件 “libtest.a”。但只有“prom/libtest.a”是最新的庫文件。

      當(dāng)在上邊的Makefile文件中使用“GPATH”指定目錄時(shí),情況就不一樣了。首先看看怎么使用“GPATH”,改變后的Makefile內(nèi)容如下:

      LIBS = libtest.a

      GPATH = src

      VPATH = src

      LDFLAGS += -L ./. –ltest

      …….

      ……



      同樣;當(dāng)兩個(gè)目錄都不存在目標(biāo)文件“libtest.a”時(shí),目標(biāo)將會(huì)在當(dāng)前目錄(“prom”目錄)下創(chuàng)建。如果“src”目錄下已經(jīng)存在目標(biāo)文件 “libtest.a”。當(dāng)其依賴文件任何一個(gè)被改變以后執(zhí)行make,目標(biāo)“libtest.a”將會(huì)被在“src”目錄下被更新(目標(biāo)完整路徑名不會(huì) 被廢棄)。

      4.5.4 命令行和搜索目錄
      make在執(zhí)行時(shí),通過目錄搜索得到的目標(biāo)的依賴文件可能會(huì)在其它目錄(此時(shí)依賴文件為文件的完整路徑名),但是已經(jīng)存在的規(guī)則命令卻不能發(fā)生變化。因此,書寫命令時(shí)我們必須保證當(dāng)依賴文件在其它目錄下被發(fā)現(xiàn)時(shí)規(guī)則的命令能夠正確執(zhí)行。

      處理這種問題的方式就是使用“自動(dòng)化變量”(可參考 9.5.3 自動(dòng)化變量 一小節(jié)),諸如“$^”等。規(guī)則命令行中的自動(dòng)化變量“$^”代表所有的是的通過目錄搜索得到的依賴文件的完整路徑名(目錄+一般文件名)列表。“$@” 代表規(guī)則的目標(biāo)。所以對(duì)于一個(gè)規(guī)則我們可以進(jìn)行如下的描述:



      foo.o : foo.c

      cc -c $(CFLAGS) $^ -o $@



      變量“CFLAGS”是編譯.c文件時(shí)GCC的命令行選項(xiàng),可以在Makefile中給它指定明確的值、也可以使用隱含的定義值。

      規(guī)則的依賴文件列表中可以包含頭文件,而在命令行不需要使用這些頭文件(這些頭文件的作用只有在make程序決定目標(biāo)是否需要重建時(shí)才有意義)。我們可以使用另外一個(gè)變量來書代替“$^”,例如:



      VPATH = src:../headers

      foo.o : foo.c defs.h hack.h

      cc -c $(CFLAGS) $< -o $@



      自動(dòng)化變量“$<”代表規(guī)則中通過目錄搜索得到的依賴文件列表的第一個(gè)依賴文件。關(guān)于自動(dòng)化變量我們?cè)诤罄m(xù)有專門的討論。

      4.5.5 隱含規(guī)則和搜索目錄
      隱含規(guī)則同樣會(huì)為依賴文件搜索通過變量“VPATH”、或者關(guān)鍵字“vpath”指定的搜索目錄。例如:一個(gè)目標(biāo)文件“foo.o”在Makefile中 沒有重建它的明確規(guī)則,make會(huì)使用隱含規(guī)則來由已經(jīng)存在的“foo.c”來重建它。當(dāng)“foo.c”在當(dāng)前目錄下不存在時(shí),make將會(huì)進(jìn)行目錄搜 索。如果能夠在一個(gè)可以搜索的目錄中找到此文件,同樣make會(huì)使用隱含規(guī)則根據(jù)搜索到的文件完整的路徑名去重建目標(biāo),編譯這個(gè).c源文件。

      隱含規(guī)則中的命令行中就是使用自動(dòng)化變量來解決目錄搜索可能帶來的問題;相應(yīng)的命令中的文件名都是使用目錄搜索得到的完整的路徑名。(可參考上一小節(jié))

      4.5.6 庫文件和搜索目錄
      Makefile中程序鏈接的靜態(tài)庫、共享庫同樣也可以有目錄搜索得到。這一特性需要我們?cè)跁?guī)則的依賴是指定一個(gè)類似“-lNNAM”的依賴文件名(一 個(gè)奇怪的依賴文件名!一般依賴文件名應(yīng)該是一個(gè)普通文件的名字。庫文件的命名也應(yīng)該是“libNAME.a”而不是所寫的“-lNAME”。這是為什么, 熟悉GNU ld的話我想這就不難理解了,“-lNAME”的表示方式和ld的對(duì)庫的引用方式完全一樣,只是我們?cè)跁鴮慚akefile的規(guī)則時(shí)使用了這種書寫方式。 因此你不應(yīng)該感到奇怪)。下邊我們就來看看這種奇怪的依賴文件到底是什么。

      當(dāng)規(guī)則中依賴文件列表中存在一個(gè)“-lNAME”形式的文件時(shí)。make將根據(jù)“NAME”首先搜索當(dāng)前系統(tǒng)可提供的共享庫,如果當(dāng)前系統(tǒng)不能提供這個(gè)共 享庫,則搜索它的靜態(tài)庫(當(dāng)然你可以在命令行中指定編譯或者連接選項(xiàng)來指定動(dòng)態(tài)連接還是靜態(tài)連接,這里我們不討論)。來看一下詳細(xì)的過程。1. make在執(zhí)行規(guī)則時(shí)會(huì)在當(dāng)前目錄下搜索一個(gè)名字為“libNAME.so”的文件;2. 如果當(dāng)前工作目錄下不存在這樣一個(gè)文件,則make程序會(huì)繼續(xù)搜索使用“VPATH”或者“vpath”指定的搜索目錄。3. 還是不存在,make程序?qū)⑺阉飨到y(tǒng)默認(rèn)目錄,順序是:“/lib”、“/usr/lib”和“PREFIX/lib”(在Linux系統(tǒng)中為 “/usr/local/lib”,其他的系統(tǒng)可能不同)。

      如果“libNAME.so”通過以上的途徑最后還是沒有找到的話,那么make程序?qū)?huì)按照以上的搜索順序查找名字為“libNAME.a”的文件。

      假設(shè)你的系統(tǒng)中存在“/usr/lib/libcurses.a”(不存在“/usr/lib/libcurses.so”)這個(gè)庫文件??匆粋€(gè)例子:



      foo : foo.c -lcurses

      cc $^ -o $@



      上例中,如果文件“foo.c”被修改或者“/usr/lib/libcurses.a”被更新,執(zhí)行規(guī)則時(shí)將使用命令“cc foo.c /usr/lib/libcurses.a -o foo”來完成目標(biāo)文件的重建。需要注意的是:如果“/usr/lib/libcurses.a”需要在執(zhí)行make的時(shí)生成,那么就不能這樣寫,因?yàn)? “-lNAME”只是告訴了鏈接器在生成目標(biāo)時(shí)需要鏈接那個(gè)庫文件。上例的中的“-lcurses”并沒有告訴make程序其依賴的庫文件應(yīng)該如何重建。 當(dāng)搜索的所有目錄中不存在庫“libcurses”時(shí)。Make將提示“沒有規(guī)則可以創(chuàng)建目標(biāo)“foo”需要的目標(biāo)“-lcurses”。如果在執(zhí)行 make時(shí),出現(xiàn)這樣的提示信息,你應(yīng)該明確發(fā)生了什么錯(cuò)誤,而不要因?yàn)殄e(cuò)誤而不知所措。

      當(dāng)規(guī)則的依賴列表中出現(xiàn)了“-lNAME”格式的依賴,默認(rèn)搜索的文件名為“libNAME.so”和“libNAME.a”,這是由變量 “.LIBPATTERNS”來指定的。“.LIBPATTERNS”的值一般是多個(gè)包含模式字符(%)的字(一個(gè)不包含空格的字符串),多個(gè)字之間使用 空格分開。在規(guī)則中出現(xiàn)“-lNAME”格式的的依賴時(shí),首先使用這里的“NAME”代替變量“.LIBPATTERNS”的第一個(gè)字的模式字符(%)而 得到第一個(gè)庫文件名,根據(jù)這個(gè)文件名在搜索目錄下查找,如果能夠找到、就是用這個(gè)文件,否則使用“NAME”代替第二個(gè)字的模式字符,進(jìn)行同樣的查找。默 認(rèn)情況時(shí),“.LIBPATTERNS”的值為:“lib%.so lib%.a”。這也是默認(rèn)情況下在規(guī)則存在“-lNAME”格式的依賴時(shí),鏈接生成目標(biāo)時(shí)使用“libNAME.so”和“libNAME.a”的原 因。

      變量“.LIBPATTERNS”就是告訴鏈接器在執(zhí)行鏈接過程中對(duì)于出現(xiàn)“-LNAME”的文件如何展開。當(dāng)然我們也可以將此變量制空,取消鏈接器對(duì)“-lNAME”格式進(jìn)行展開。



      4.6 Makefile偽目標(biāo)
      本節(jié)我們討論一個(gè)Makefile中的一個(gè)重要的特殊目標(biāo):偽目標(biāo)。

      偽目標(biāo)是這樣一個(gè)目標(biāo):它不代表一個(gè)真正的文件名,在執(zhí)行make時(shí)可以指定這個(gè)目標(biāo)來執(zhí)行其所在規(guī)則定義的命令,有時(shí)我們也可以將一個(gè)偽目標(biāo)稱為標(biāo)簽。 使用偽目標(biāo)有兩點(diǎn)原因:1. 避免在我們的Makefile中定義的只執(zhí)行命令的的目標(biāo)(此目標(biāo)的目的為了執(zhí)行執(zhí)行一些列命令,而不需要?jiǎng)?chuàng)建這個(gè)目標(biāo))和工作目錄下的實(shí)際文件出現(xiàn)名字 沖突。2. 提高執(zhí)行make時(shí)的效率,特別是對(duì)于一個(gè)大型的工程來說,編譯的效率也許你同樣關(guān)心。以下就這兩個(gè)問題我們進(jìn)行分析討論:

      1. 如果我們需要書寫這樣一個(gè)規(guī)則:規(guī)則所定義的命令不是去創(chuàng)建目標(biāo)文件,而是使用make指定具體的目標(biāo)來執(zhí)一些特定的命令。像下邊那樣:

      clean:

      rm *.o temp



      規(guī)則中“rm”不是創(chuàng)建文件“clean”的命令,只是刪除當(dāng)前目錄下的所有.o文件和temp文件。在工作目錄下不存在“clean”這個(gè)文件時(shí),我們輸入“make clean”后,“rm *.o temp”總會(huì)被執(zhí)行。這是我們的初衷。

      但當(dāng)前工作目錄下存在文件“clean”時(shí)情況就不一樣了,在我們輸入“make clean”時(shí)。規(guī)則沒有依賴文件,所以目標(biāo)被認(rèn)為是最新的而不去執(zhí)行規(guī)則作定義的命令,命令“rm”將不會(huì)被執(zhí)行。這并不是我們的初衷。為了避免這個(gè)問 題,我們可以將目標(biāo)“clean”明確的聲明為偽目標(biāo)。將一個(gè)目標(biāo)聲明為偽目標(biāo)需要將它作為特殊目標(biāo).PHONY”的依賴。如下:

      .PHONY : clean



      這樣目標(biāo)“clean”就是一個(gè)偽目標(biāo),無論當(dāng)前目錄下是否存在“clean”這個(gè)文件。我們輸入“make clean”之后。“rm”命令都會(huì)被執(zhí)行。而且,當(dāng)一個(gè)目標(biāo)被聲明為偽目標(biāo)后,make在執(zhí)行此規(guī)則時(shí)不會(huì)試圖去查找隱含規(guī)則來創(chuàng)建這個(gè)目標(biāo)。這樣也提 高了make的執(zhí)行效率,同時(shí)我們也不用擔(dān)心由于目標(biāo)和文件名重名而使我們的期望失敗。在書寫偽目標(biāo)規(guī)則時(shí),首先需要聲明目標(biāo)是一個(gè)偽目標(biāo),之后才是偽目 標(biāo)的規(guī)則定義。目標(biāo)“clean”書寫格式應(yīng)該如下:



      .PHONY: clean

      clean:

      rm *.o temp



      2. 偽目標(biāo)的另外一使用場合在make的并行和遞歸執(zhí)行過程中。此情況下一般存在一個(gè)變量,其定義為所有需要make的子目錄。對(duì)多個(gè)目錄進(jìn)行make的實(shí)現(xiàn)方式可以在一個(gè)規(guī)則中可以使用shell的循環(huán)來完成。如下:



      SUBDIRS = foo bar baz



      subdirs:

      for dir in $(SUBDIRS); do \

      $(MAKE) -C $$dir; \

      done



      但這種實(shí)現(xiàn)方法存在以下幾個(gè)問題。1. 當(dāng)子目錄執(zhí)行make出現(xiàn)錯(cuò)誤時(shí),make不會(huì)退出。就是說,在對(duì)某一個(gè)目錄執(zhí)行make失敗以后,會(huì)繼續(xù)對(duì)其他的目錄進(jìn)行make。在最終執(zhí)行失敗的情 況下,我們很難根據(jù)錯(cuò)誤的提示定位出具體是是那個(gè)目錄下的Makefile出現(xiàn)錯(cuò)誤。這給問題定位造成了很大的困難。為了避免這樣的問題,我們可以在命令 行部分加入錯(cuò)誤的監(jiān)測,在命令執(zhí)行錯(cuò)誤后make退出。不幸的是,如果在執(zhí)行make時(shí)使用了“-k”選項(xiàng),此方式將失效。2. 另外一個(gè)問題就是使用這種shell的循環(huán)方式時(shí),沒有用到make對(duì)目錄的并行處理功能,因?yàn)橐?guī)則的命令是一條完整的shell命令,不能被并行的執(zhí) 行。

      我們可以通過偽目標(biāo)方式來克服以上實(shí)現(xiàn)方式所存在的兩個(gè)問題。



      SUBDIRS = foo bar baz



      .PHONY: subdirs $(SUBDIRS)



      subdirs: $(SUBDIRS)

      $(SUBDIRS):

      $(MAKE) -C $@

      foo: baz



      上邊的實(shí)現(xiàn)中使用了一個(gè)沒有命令行的規(guī)則“foo: baz”,用來限制子目錄的make順序。此規(guī)則的含義時(shí)在處理“foo”目錄之前,需要等待“baz”目錄處理完成。在書寫一個(gè)并行執(zhí)行make的Makefile時(shí),目錄的處理順序是需要特別注意的。

      一般情況下,一個(gè)偽目標(biāo)不作為一個(gè)另外一個(gè)目標(biāo)文件的依賴。這是因?yàn)楫?dāng)一個(gè)目標(biāo)文件的依賴包含偽目標(biāo)時(shí),每一次在執(zhí)行這個(gè)規(guī)則時(shí)偽目標(biāo)所定義的命令都會(huì)被 執(zhí)行(因?yàn)樗且?guī)則的依賴,重建規(guī)則目標(biāo)文件時(shí)需要首先重建它的依賴)。當(dāng)偽目標(biāo)沒有作為任何目標(biāo)(此目標(biāo)是一個(gè)可被創(chuàng)建或者已存在的文件)的依賴時(shí),我 們只能通過make的命令行選項(xiàng)明確指定這個(gè)偽目標(biāo),來執(zhí)行它所定義的命令。例如我們的“make clean”。

      Makefile中,偽目標(biāo)可以有自己的依賴。在一個(gè)目錄下如果需要?jiǎng)?chuàng)建多個(gè)可執(zhí)行程序,我們可以將所有程序的重建規(guī)則在一個(gè)Makefile中描述。因 為Makefile中第一個(gè)目標(biāo)是“終極目標(biāo)”,約定的做法是使用一個(gè)稱為“all”的偽目標(biāo)來作為終極目標(biāo),它的依賴文件就是那些需要?jiǎng)?chuàng)建的程序。下邊 就是一個(gè)例子:



      #sample Makefile

      all : prog1 prog2 prog3

      .PHONY : all



      prog1 : prog1.o utils.o

      cc -o prog1 prog1.o utils.o



      prog2 : prog2.o

      cc -o prog2 prog2.o



      prog3 : prog3.o sort.o utils.o

      cc -o prog3 prog3.o sort.o utils.o



      執(zhí)行make時(shí),目標(biāo)“all”被作為終極目標(biāo)。為了完成對(duì)它的更新,make會(huì)創(chuàng)建(不存在)或者重建(已存在)目標(biāo)“all”的所有依賴文件 (prog1、prog2和prog3)。當(dāng)需要單獨(dú)更新某一個(gè)程序時(shí),我們可以通過make的命令行選項(xiàng)來明確指定需要重建的程序。(例如: “make prog1”)。

      當(dāng)一個(gè)偽目標(biāo)作為另外一個(gè)偽目標(biāo)依賴時(shí),make將其作為另外一個(gè)偽目標(biāo)的子例程來處理(可以這樣理解:其作為另外一個(gè)偽目標(biāo)的必須執(zhí)行的部分,就行C語言中的函數(shù)調(diào)用一樣)。下邊的例子就是這種用法:



      .PHONY: cleanall cleanobj cleandiff

      cleanall : cleanobj cleandiff

      rm program



      cleanobj :

      rm *.o



      cleandiff :

      rm *.diff



      “cleanobj”和“cleandiff”這兩個(gè)偽目標(biāo)有點(diǎn)像“子程序”的意思(執(zhí)行目標(biāo)“clearall時(shí)會(huì)觸發(fā)它們所定義的命令被執(zhí)行”)。我 們可以輸入“make cleanall”和“make cleanobj”和“make cleandiff”命令來達(dá)到清除不同種類文件的目的。例子首先通過特殊目標(biāo)“.PHONY”聲明了多個(gè)偽目標(biāo),它們之間使用空各分割,之后才是各個(gè)偽 目標(biāo)的規(guī)則定義。

      說明:

      通常在清除文件的偽目標(biāo)所定義的命令中“rm”使用選項(xiàng)“–f”(--force)來防止在缺少刪除文件時(shí)出錯(cuò)并退出,使“make clean”過程失敗。也可以在“rm”之前加上“-”來防止“rm”錯(cuò)誤退出,這種方式時(shí)make會(huì)提示錯(cuò)誤信息但不會(huì)退出。為了不看到這些討厭的信 息,需要使用上述的第一種方式。

      另外make存在一個(gè)內(nèi)嵌隱含變量“RM”,它被定義為:“RM = rm –f”。因此在書寫“clean”規(guī)則的命令行時(shí)可以使用變量“$(RM)”來代替“rm”,這樣可以免出現(xiàn)一些不必要的麻煩!這是我們推薦的用法。

      4.7 強(qiáng)制目標(biāo)(沒有命令或依賴的規(guī)則)
      如果一個(gè)規(guī)則沒有命令或者依賴,而且它的目標(biāo)不是一個(gè)存在的文件名。在執(zhí)行此規(guī)則時(shí),目標(biāo)總會(huì)被認(rèn)為是最新的。就是說:這個(gè)規(guī)則一旦被執(zhí)行,make就認(rèn) 為它的目標(biāo)已經(jīng)被更新過。這樣的目標(biāo)在作為一個(gè)規(guī)則的依賴時(shí),因?yàn)橐蕾嚳偙徽J(rèn)為被更新過,因此作為依賴所在的規(guī)則定義的命令總會(huì)被執(zhí)行。看一個(gè)例子:



      clean: FORCE

      rm $(objects)

      FORCE:



      這個(gè)例子中,目標(biāo)“FORCE”符合上邊的條件。它作為目標(biāo)“clean”的依賴出現(xiàn),在執(zhí)行make時(shí),它總被認(rèn)為被更新過。所以“clean”所在的規(guī)則在被執(zhí)行時(shí)規(guī)則所定義的命令總會(huì)被執(zhí)行。這樣的一個(gè)目標(biāo)通常我們將其命名為“FORCE”。

      上邊的例子中使用“FORCE”目標(biāo)的效果和我們指定“clean”為偽目標(biāo)效果相同。兩種方式相比較,使用“.PHONY”方式更加直觀高效。這種方式主要用在非GNU版本的make中。

      在使用GNU make,盡量避免使用這種方式。在GNU make中我們推薦使用偽目標(biāo)方式。關(guān)于偽目標(biāo)可參考3.6 Makefile偽目標(biāo) 一節(jié)

      4.8 空目標(biāo)文件
      空目標(biāo)是偽目標(biāo)的一個(gè)變種;此目標(biāo)所在規(guī)則執(zhí)行的目的和偽目標(biāo)相同——通過make命令行指定終極目標(biāo)來執(zhí)行規(guī)則所定義的命令。和偽目標(biāo)不同的是:這個(gè)目標(biāo)可以是一個(gè)存在的文件,一般文件的具體內(nèi)容我們并不關(guān)心,通常此文件是一個(gè)空文件。

      空目標(biāo)文件只是用來記錄上一次執(zhí)行此規(guī)則定義命令的時(shí)間。一般在這樣的規(guī)則中,命令部分都會(huì)使用“touch”在完成所有命令之后來更新目標(biāo)文件的時(shí)間 戳,記錄此規(guī)則命令的最后執(zhí)行時(shí)間。make時(shí)通過命令行將此目標(biāo)作為終極目標(biāo),當(dāng)前目錄下如果不存在這個(gè)文件,“touch”會(huì)在第一次執(zhí)行時(shí)創(chuàng)建一個(gè) 空的文件(命名為空目標(biāo)文件名)。

      通常,一個(gè)空目標(biāo)文件應(yīng)該存在一個(gè)或者多個(gè)依賴文件。將這個(gè)目標(biāo)作為終極目標(biāo),在它所依賴的文件比它新時(shí),此目標(biāo)所在規(guī)則的命令行將被執(zhí)行。就是說,如果空目標(biāo)的依賴文件被改變之后,空目標(biāo)所在規(guī)則中定義的命令會(huì)被執(zhí)行。看一個(gè)例子:



      print: foo.c bar.c

      lpr -p $?

      touch print



      執(zhí)行“make print”,當(dāng)目標(biāo)“print”的依賴文件任何一個(gè)被修改之后,命令“lpr –p $?”都會(huì)被執(zhí)行,打印這個(gè)被修改的文件。關(guān)于自動(dòng)化變量“$?”可參考 9.5.3 自動(dòng)化變量 一小節(jié)。

      4.9 Makefile的特殊目標(biāo)
      在Makefile中,有一些名字,當(dāng)它們作為規(guī)則的目標(biāo)出現(xiàn)時(shí),具有特殊含義。它們是一些特殊的目標(biāo),GNU make所支持的特殊的目標(biāo)有:

      .PHONY:

      目標(biāo)“.PHONY”的所有的依賴被作為偽目標(biāo)。偽目標(biāo)時(shí)這樣一個(gè)目標(biāo):當(dāng)使用make命令行指定此目標(biāo)時(shí),這個(gè)目標(biāo)所在規(guī)則定義的命令、無論目標(biāo)文件是否存在都會(huì)被無條件執(zhí)行。參考 3.6 Makefile偽目標(biāo) 一節(jié)

      .SUFFIXES:

      特殊目標(biāo)“SUFFIXES”的所有依賴指出了一系列在后綴規(guī)則中需要檢查的后綴名(就是當(dāng)前make需要處理的后綴)。參考 9.7 后綴規(guī)則 一節(jié)

      .DEFAULT

      Makefile中,目標(biāo)“.DEFAULT”所在規(guī)則定義的命令,被用在重建那些沒有具體規(guī)則的目標(biāo)(明確規(guī)則和隱含規(guī)則)。就是說一個(gè)文件作為某個(gè)規(guī) 則的依賴,但卻不是另外一個(gè)規(guī)則的目標(biāo)時(shí)。Make程序無法找到重建此文件的規(guī)則,此種情況時(shí)就執(zhí)行“.DEFAULT”所指定的命令。

      .PRECIOUS

      目標(biāo)“.PRECIOUS”的所有依賴文件在make過程中會(huì)被特殊處理:當(dāng)命令在執(zhí)行過程中被中斷時(shí),make不會(huì)刪除它們(可參考 4.5 中斷make的執(zhí)行 一節(jié))。而且如果目標(biāo)的依賴文件是中間過程文件,同樣這些文件不會(huì)被刪除。這一點(diǎn)目標(biāo)“.PRECIOUS”和目標(biāo)“.SECONDAY”實(shí)現(xiàn)的功能相 同。參考 9.4 make隱含規(guī)則鏈 一節(jié)

      另外,目標(biāo)“.PRECIOUS”的依賴文件也可以是一個(gè)模式,例如“%.o”。這樣可以保留有規(guī)則創(chuàng)建的中間過程文件。

      .INTERMEDIATE

      目標(biāo)“.INTERMEDIATE”的依賴文件在make時(shí)被作為中間過程文件對(duì)待。沒有任何依賴文件的目標(biāo)“.INTERMEDIATE”沒有意義。參考 9.4 make隱含規(guī)則鏈 一節(jié)

      .SECONDARY

      目標(biāo)“.SECONDARY”的依賴文件被作為中間過程文件對(duì)待。但這些文件不會(huì)被自動(dòng)刪除(可參考 9.4 make隱含規(guī)則鏈 一節(jié))

      沒有任何依賴文件的目標(biāo)“.SECONDARY”的含義是:將所有的文件作為中間過程文件(不會(huì)自動(dòng)刪除任何文件)。

      .DELETE_ON_ERROR

      如果在Makefile中存在特殊目標(biāo)“.DELETE_ON_ERROR”,make在執(zhí)行過程中,如果規(guī)則的命令執(zhí)行錯(cuò)誤,將刪除已經(jīng)被修改的目標(biāo)文件。參考 4.4 命令執(zhí)行的錯(cuò)誤 一節(jié)

      .IGNORE

      如果給目標(biāo)“.IGNORE”指定依賴文件,則忽略創(chuàng)建這個(gè)文件所執(zhí)行命令的錯(cuò)誤。給此目標(biāo)指定命令是沒有意義的。當(dāng)此目標(biāo)沒有依賴文件時(shí),將忽略所有命令執(zhí)行的錯(cuò)誤。參考 4.4 命令執(zhí)行的錯(cuò)誤 一節(jié)

      .LOW_RESOLUTION_TIME

      目標(biāo)“.LOW_RESOLUTION_TIME”的依賴文件被make認(rèn)為是低分辨率時(shí)間戳文件。給目標(biāo)“.LOW_RESOLUTION_TIME”指定命令是沒有意義的。

      通常文件的時(shí)間輟都是高分辨率的,make在處理依賴關(guān)系時(shí)、對(duì)規(guī)則目標(biāo)-依賴文件的高分辨率的時(shí)間戳進(jìn)行比較,判斷目標(biāo)是否過期。但是在系統(tǒng)中并沒有提 供一個(gè)修改文件高分辨率時(shí)間輟的機(jī)制(方式),因此類似“cp -p”這樣的命令在根據(jù)源文件創(chuàng)建目的文件時(shí),所產(chǎn)生的目的文件的高分辨率時(shí)間輟的細(xì)粒度部分被丟棄(來源于源文件)??赡軙?huì)造成目的文件的時(shí)間戳和源文 件的相等甚至不及源文件新。處理此類命令創(chuàng)建的文件時(shí),需要將命令創(chuàng)建的文件作為目標(biāo)“.LOW_RESOLUTION_TIME”的依賴,聲明這個(gè)文件 是一個(gè)低分辨率時(shí)間輟的文件。例如:



      .LOW_RESOLUTION_TIME: dst

      dst: src

      cp -p src dst



      首先規(guī)則的命令“cp –p src dst”,所創(chuàng)建的文件“dst”在時(shí)間戳上稍稍比“src”晚(因?yàn)槊畈荒芨挛募?#8220;dst”的細(xì)粒度時(shí)間)。因此make在判斷文件依賴關(guān)系時(shí)會(huì)出 現(xiàn)誤判,將文件作為目標(biāo)“.LOW_RESOLUTION_TIME”的依賴后,只要規(guī)則中目標(biāo)和依賴文件的時(shí)間戳中的初始時(shí)間相等,就認(rèn)為目標(biāo)已經(jīng)過 期。這個(gè)特殊的目標(biāo)主要作用是,彌補(bǔ)系統(tǒng)在沒有提供修改文件高分辨率時(shí)間戳機(jī)制的情況下,某些命令在make中的一些缺陷。

      對(duì)于靜態(tài)庫文件(文檔文件)成員的更新也存在這個(gè)問題。make在創(chuàng)建或者更新靜態(tài)庫時(shí),會(huì)自動(dòng)將靜態(tài)庫的所有成員作為目標(biāo)“.LOW_RESOLUTION_TIME”的依賴。

      .SILENT

      出現(xiàn)在目標(biāo)“.SILENT”的依賴列表中的文件,make在創(chuàng)建這些文件時(shí),不打印出重建此文件所執(zhí)行的命令。同樣,給目標(biāo)“.SILENT”指定命令行是沒有意義的。

      沒有任何依賴文件的目標(biāo)“.SILENT”告訴make在執(zhí)行過程中不打印任何執(zhí)行的命令?,F(xiàn)行版本make支持目標(biāo)“.SILENT”的這種功能和用法 是為了和舊版本的兼容。在當(dāng)前版本中如果需要禁命令執(zhí)行過程的打印,可以使用make的命令行參數(shù)“-s”或者“--silent”。參考 8.7 make的命令行選項(xiàng) 一節(jié)



      .EXPORT_ALL_VARIABLES

      此目標(biāo)應(yīng)該作為一個(gè)簡單的沒有依賴的目標(biāo),它的功能含義是將之后所有的變量傳遞給子make進(jìn)程。參考 4.6 make的遞歸執(zhí)行 一節(jié)

      .NOTPARALLEL

      Makefile中,如果出現(xiàn)目標(biāo)“.NOPARALLEL”,則所有命令按照串行方式執(zhí)行,即使存在make的命令行參數(shù)“-j”。但在遞歸調(diào)用的字make進(jìn)程中,命令可以并行執(zhí)行。此目標(biāo)不應(yīng)該有依賴文件,所有出現(xiàn)的依賴文件將被忽略。

      所有定義的隱含規(guī)則后綴作為目標(biāo)出現(xiàn)時(shí),都被視為一個(gè)特殊目標(biāo),兩個(gè)后綴串聯(lián)起來也是如此,例如“.c.o”。這樣的目標(biāo)被稱為后綴規(guī)則的目標(biāo),這種定義 方式是已經(jīng)過時(shí)的定義隱含規(guī)則的方法(目前,這種方式還被用在很多地方)。原則上,如果將其分為兩個(gè)部分、并將它們加到后綴列表中,任何目標(biāo)都可采用這種 方式來表示。實(shí)際中,后綴通常以“.”開始,因此,以上的這些特別目標(biāo)同樣是以“.”開始??蓞⒖?9.7 后綴規(guī)則 一節(jié)



      4.10 多目標(biāo)
      一個(gè)規(guī)則中可以有多個(gè)目標(biāo),規(guī)則所定義的命令對(duì)所有的目標(biāo)有效。一個(gè)具有多目標(biāo)的規(guī)則相當(dāng)于多個(gè)規(guī)則。規(guī)則中命令對(duì)不同的目標(biāo)的執(zhí)行效果不同,因?yàn)樵谝?guī)則的命令中可能使用自動(dòng)環(huán)變量“$@”。多目標(biāo)規(guī)則意味著所有的目標(biāo)具有相同的依賴文件。多目標(biāo)通常用在以下兩種情況:

      Ø 僅需要一個(gè)描述依賴關(guān)系的規(guī)則,而不需要在規(guī)則中定義命令。例如

      kbd.o command.o files.o: command.h



      這個(gè)規(guī)則實(shí)現(xiàn)了給同時(shí)給三個(gè)目標(biāo)文件指定一個(gè)依賴文件。

      ² 對(duì)于多個(gè)具有類似重建命令的目標(biāo)。重建這些目標(biāo)的命令并不需要是絕對(duì)相同,因?yàn)槲覀兛梢栽诿钚兄惺褂胢ake的自動(dòng)環(huán)變量“$@”來引用具體一個(gè)目標(biāo),并完成對(duì)它的重建(可參考 9.5.3 自動(dòng)化變量 一小節(jié))。例如規(guī)則:



      bigoutput littleoutput : text.g

      generate text.g -$(subst output,,$@) > $@

      其等價(jià)于:



      bigoutput : text.g

      generate text.g -big > bigoutput

      littleoutput : text.g

      generate text.g -little > littleoutput



      例子中的“generate”根據(jù)命令行參數(shù)來決定輸出文件的類型。使用了make的字符串處理函數(shù)“subst”來根據(jù)目標(biāo)產(chǎn)生對(duì)應(yīng)的命令行選項(xiàng)。

      雖然在多目標(biāo)的規(guī)則中,可以根據(jù)不同的目標(biāo)使用不同的命令(在命令行中使用自動(dòng)化變量“$@”)。但是,多目標(biāo)的規(guī)則并不能做到根據(jù)目標(biāo)文件自動(dòng)改變依賴 文件,就像我們?cè)谏线吚又惺褂米詣?dòng)化變量“$@”來改變規(guī)則的命令一樣。需要實(shí)現(xiàn)這個(gè)目的是,要用到make的靜態(tài)模式。



      4.11多規(guī)則目標(biāo)
      Makefile中,一個(gè)文件可以作為多個(gè)規(guī)則的目標(biāo)出現(xiàn)。這種情況時(shí),此目標(biāo)文件的所有依賴文件將會(huì)被合并成此目標(biāo)一個(gè)依賴文件列表,其中任何一個(gè)依賴文件比目標(biāo)更新(比較目標(biāo)文件和依賴文件的時(shí)間戳)時(shí),make將會(huì)執(zhí)行特定的命令來重建這個(gè)目標(biāo)。

      對(duì)于一個(gè)多規(guī)則的目標(biāo),重建此目標(biāo)的命令只能出現(xiàn)在一個(gè)規(guī)則中(可以是多條命令)。如果多個(gè)規(guī)則同時(shí)給出重建此目標(biāo)的命令,make將使用最后一個(gè)規(guī)則所 以的命令,同時(shí)提示錯(cuò)誤信息(一個(gè)特殊的例外是:使用“.”開頭的多規(guī)則目標(biāo)文件,可以在多個(gè)規(guī)則中給出多個(gè)重建命令。這種方式只是為了和其他版本 make進(jìn)行兼容,一般在GNU make中應(yīng)該避免使用這個(gè)功能)。某些情況下,需要對(duì)相同的目標(biāo)使用不同的規(guī)則中所定義的命令,這種情況我們可使用另外一種方式“雙冒號(hào)”規(guī)則來實(shí)現(xiàn)。

      一個(gè)僅僅描述依賴關(guān)系的描述規(guī)則可用來給出一個(gè)或做多個(gè)目標(biāo)文件的依賴文件。例如,Makefile中通常存在一個(gè)變量,就像以前我們提到的 “objects”,它定義為所有的需要編譯生成的.o文件的列表。當(dāng)這些.o文件在其源文件所包含的頭文件“config.h”發(fā)生變化之后能夠自動(dòng)的 被重建,我們可以使用多目標(biāo)像下邊那樣來書寫我們的Makefile:





      objects = foo.o bar.o

      foo.o : defs.h

      bar.o : defs.h test.h

      $(objects) : config.h



      這樣做的好處是:我們可以在源文件中增加或者刪除了包含的頭文件以后不用修改已經(jīng)存在的Makefile的規(guī)則,只需要增加或者刪除某一個(gè).o文件依賴的 頭文件。這種方式很簡單也很方便。對(duì)于一個(gè)大的工程來說,這樣做的好處是顯而易見的。在一個(gè)大的工程中,對(duì)于一個(gè)單獨(dú)目錄下的.o文件的依賴規(guī)則建議使用 此方式。規(guī)則中頭文件的依賴描述也可以使用GCC自動(dòng)產(chǎn)生??蓞⒖?3.14 自動(dòng)產(chǎn)生依賴 一節(jié)

      另外,我們也可以通過一個(gè)變量來增加目標(biāo)的依賴文件,使用make的命令行來指定某一個(gè)目標(biāo)的依賴頭文件,例如:



      extradeps=

      $(objects) : $(extradeps)



      它的意思是:如果我們執(zhí)行“make extradeps=foo.h”那么“foo.h”將作為所有的.o文件的依賴文件。當(dāng)然我們只執(zhí)行“make”的話,就沒有指定任何文件作為.o文件的依賴文件。

      在多規(guī)則的目標(biāo)中,如果目標(biāo)的任何一個(gè)規(guī)則沒有定義重建此目標(biāo)的命令,make將會(huì)尋找一個(gè)合適的隱含規(guī)則來重建此目標(biāo)。關(guān)于隱含規(guī)則可參考 第九章 make的隱含規(guī)則



      4.12 靜態(tài)模式
      靜態(tài)模式規(guī)則是這樣一個(gè)規(guī)則:規(guī)則存在多個(gè)目標(biāo),并且不同的目標(biāo)可以根據(jù)目標(biāo)文件的名字來自動(dòng)構(gòu)造出依賴文件。靜態(tài)模式規(guī)則比多目標(biāo)規(guī)則更通用,它不需要多個(gè)目標(biāo)具有相同的依賴。但是靜態(tài)模式規(guī)則中的依賴文件必須是相類似的而不是完全相同的。

      4.12.1 靜態(tài)模式規(guī)則的語法
      首先,我們來看一下靜態(tài)模式規(guī)則的基本語法:





      TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...

      COMMANDS

      ...



      “TAGETS”列出了此規(guī)則的一系列目標(biāo)文件。像普通規(guī)則的目標(biāo)一樣可以包含通配符。關(guān)于通配符的使用可參考 3.4 文件名使用通配符 一節(jié)

      “TAGET-PATTERN”和“PREREQ-PATTERNS”說明了如何為每一個(gè)目標(biāo)文件生成依賴文件。從目標(biāo)模式(TAGET- PATTERN)的目標(biāo)名字中抽取一部分字符串(稱為“莖”)。使用“莖”替代依賴模式(PREREQ-PATTERNS)中的相應(yīng)部分來產(chǎn)生對(duì)應(yīng)目標(biāo)的 依賴文件。下邊詳細(xì)說明這一替代的過程。

      首先在目標(biāo)模式和依賴模式中,一般需要包含模式字符“%”。在目標(biāo)模式(TAGET-PATTERN)中“%”可以匹配目標(biāo)文件的任何部分,模式字符 “%”匹配的部分就是“莖”。目標(biāo)文件和目標(biāo)模式的其余部分必須精確的匹配??匆粋€(gè)例子:目標(biāo)“foo.o”符合模式“%.o”,其“莖”為“foo”。 而目標(biāo)“foo.c”和“foo.out”就不符合此目標(biāo)模式。

      每一個(gè)目標(biāo)的依賴文件是使用此目標(biāo)的“莖”代替依賴模式(PREREQ-PATTERNS)中的模式字符“%”而得到。例如:上邊的例子中依賴模式 (PREREQ-PATTERNS)為“%.c”,那么使用“莖”“foo”替代依賴模式中的“%”得到的依賴文件就是“foo.c”。需要明確的一點(diǎn) 是:在模式規(guī)則的依賴列表中使用不包含模式字符“%”也是合法的。代表這個(gè)文件是所有目標(biāo)的依賴文件。

      在模式規(guī)則中字符‘%’可以用前面加反斜杠“\”方法引用。引用“%”的反斜杠也可以由更多的反斜杠引用。引用“%”、“\”的反斜杠在和文件名比較或由 “莖”代替它之前會(huì)從模式中被刪除。反斜杠不會(huì)因?yàn)橐?#8220;%”而混亂。如,模式“the\%weird\\%pattern\\”是“the%weird \”+“%”+“pattern\\”構(gòu)成。最后的兩個(gè)反斜杠由于沒有任何轉(zhuǎn)義引用“%”所以保持不變。

      我們來看一個(gè)例子,它根據(jù)相應(yīng)的.c文件來編譯生成“foo.o”和“bar.o”文件:

      objects = foo.o bar.o



      all: $(objects)



      $(objects): %.o: %.c

      $(CC) -c $(CFLAGS) $< -o $@



      例子中,規(guī)則描述了所有的.o文件的依賴文件為對(duì)應(yīng)的.c文件,對(duì)于目標(biāo)“foo.o”,取其莖“foo”替代對(duì)應(yīng)的依賴模式“%.c”中的模式字符 “%”之后可得到目標(biāo)的依賴文件“foo.c”。這就是目標(biāo)“foo.o”的依賴關(guān)系“foo.o: foo.c”,規(guī)則的命令行描述了如何完成由“foo.c”編譯生成目標(biāo)“foo.o”。命令行中“$<”和“$@”是自動(dòng)化變量,“$<” 表示規(guī)則中的第一個(gè)依賴文件,“$@”表示規(guī)則中的目標(biāo)文件(可參考 3.14 自動(dòng)產(chǎn)生依賴 一節(jié))。以上的規(guī)則就是描述了以下兩個(gè)具體的規(guī)則:



      foo.o : foo.c

      $(CC) -c $(CFLAGS) foo.c -o foo.o

      bar.o : bar.c

      $(CC) -c $(CFLAGS) bar.c -o bar.o



      在使用靜態(tài)模式規(guī)則時(shí),指定的目標(biāo)必須和目標(biāo)模式相匹配,否則在執(zhí)行make時(shí)將會(huì)得到一個(gè)錯(cuò)誤提示。如果存在一個(gè)文件列表,其中一部分符合某一種模式而 另外一部分符合另外一種模式,這種情況下我們可以使用“filter”函數(shù)(可參考 第七章 make的內(nèi)嵌函數(shù))來對(duì)這個(gè)文件列表進(jìn)行分類,在分類之后對(duì)確定的某一類使用模式規(guī)則。例如:



      files = foo.elc bar.o lose.o

      $(filter %.o,$(files)): %.o: %.c

      $(CC) -c $(CFLAGS) $< -o $@



      $(filter %.elc,$(files)): %.elc: %.el

      emacs -f batch-byte-compile $<



      其中;$(filter %.o,$(files))的結(jié)果為“bar.o lose.o”。“filter”函數(shù)過濾不符合“%.o”模式的文件名而至返回所有符合此模式的文件列表。第一條靜態(tài)模式規(guī)則描述了這些目標(biāo)文件是通過 編譯對(duì)應(yīng)的.c源文件來重建的。同樣第二條規(guī)則也是使用這種方式。

      我們通過另外一個(gè)例子來看一下自動(dòng)環(huán)變量“$*”在靜態(tài)模式規(guī)則中的使用方法:



      bigoutput littleoutput : %output : text.g

      generate text.g -$* > $@



      當(dāng)執(zhí)行此規(guī)則的命令時(shí),自動(dòng)環(huán)變量“$*”被展開為“莖”。在這里就是“big”和“little”。

      靜態(tài)模式規(guī)則在一個(gè)較大的工程中非常有用的。它可以對(duì)一個(gè)工程中的同類文件的重建規(guī)則進(jìn)行一次定義,而實(shí)現(xiàn)對(duì)整個(gè)工程中此類文件指定相同的重建規(guī)則。比 如,可以用來描述整個(gè)工程中所有的.o文件的依賴規(guī)則和編譯命令。通常的做法是將生成同一類目標(biāo)的模式定義在一個(gè)make.rules的文件中。在工程各 個(gè)模塊的Makefile中包含此文件。

      4.12.2 靜態(tài)模式和隱含規(guī)則
      在Makefile中,靜態(tài)模式規(guī)則和被定義為隱含規(guī)則的模式規(guī)則都是我們經(jīng)常使用的兩種方式。兩者相同的地方都是用目標(biāo)模式和依賴模式來構(gòu)建目標(biāo)的規(guī)則中的文件依賴關(guān)系,兩者不同的地方是make在執(zhí)行時(shí)使用它們的時(shí)機(jī)。

      隱含規(guī)則可被用在任何和它相匹配的目標(biāo)上,在Makefile中沒有為這個(gè)目標(biāo)指定具體的規(guī)則、存在規(guī)則但規(guī)則沒有命令行或者這個(gè)目標(biāo)的依賴文件可被搜尋到。當(dāng)存在多個(gè)隱含規(guī)則和目標(biāo)模式相匹配時(shí),只執(zhí)行其中的一個(gè)規(guī)則。具體執(zhí)行哪一個(gè)規(guī)則取決于定義規(guī)則的順序。

      相反的,靜態(tài)模式規(guī)則只能用在規(guī)則中明確指出的那些文件的重建過程中。不能用在除此之外的任何文件的重建過程中,并且它對(duì)指定的每一個(gè)目標(biāo)來說是唯一的。如果一個(gè)目標(biāo)存在于兩個(gè)規(guī)則,并且每一個(gè)規(guī)則中都定以了命令,make執(zhí)行時(shí)就會(huì)提示錯(cuò)誤。

      靜態(tài)模式規(guī)則相比隱含模式規(guī)則由以下兩個(gè)優(yōu)點(diǎn):

      ² 不能根據(jù)文件名通過詞法分析進(jìn)行分類的文件,我們可以明確列出這些文件,并使用靜態(tài)模式規(guī)則來重建其隱含規(guī)則。

      ² 對(duì)于無法確定工作目錄內(nèi)容,而且不能確定是否此目錄下的無關(guān)文件會(huì)使用錯(cuò)誤的隱含規(guī)則而導(dǎo)致make失敗的情況。當(dāng)存在多個(gè)適合此文件的隱含規(guī)則時(shí),使用 哪一個(gè)隱含規(guī)則取決于其規(guī)則的定義順序。這種情況下我們使用靜態(tài)模式規(guī)則就可以避免這些不確定因素,因?yàn)殪o態(tài)模式中,指定的目標(biāo)文件有特定的規(guī)則來描述其 依賴關(guān)系和重建命令。

      4.13 雙冒號(hào)規(guī)則
      雙冒號(hào)規(guī)則就是使用“::”代替普通規(guī)則的“:”得到的規(guī)則。當(dāng)同一個(gè)文件作為多個(gè)規(guī)則的目標(biāo)時(shí),雙冒號(hào)規(guī)則的處理和普通規(guī)則的處理過程完全不同(雙冒號(hào)規(guī)則允許在多個(gè)規(guī)則中為同一個(gè)目標(biāo)指定不同的重建目標(biāo)的命令)。

      首先需要明確的是:Makefile中,一個(gè)目標(biāo)可以出現(xiàn)在多個(gè)規(guī)則中。但是這些規(guī)則必須是同一種規(guī)則,要么都是普通規(guī)則,要么都是雙冒號(hào)規(guī)則。而不允許一個(gè)目標(biāo)同時(shí)出現(xiàn)在兩種不同的規(guī)則中。雙冒號(hào)規(guī)則和普通規(guī)則的處理的不同點(diǎn)表現(xiàn)在以下幾個(gè)方面:

      1. 雙冒號(hào)規(guī)則中,當(dāng)依賴文件比目標(biāo)更新時(shí)。規(guī)則將會(huì)被執(zhí)行。對(duì)于一個(gè)沒有依賴而只有命令行的雙冒號(hào)規(guī)則,當(dāng)引用此目標(biāo)時(shí),規(guī)則的命令將會(huì)被無條件執(zhí)行。而普通規(guī)則,當(dāng)規(guī)則的目標(biāo)文件存在時(shí),此規(guī)則的命令永遠(yuǎn)不會(huì)被執(zhí)行(目標(biāo)文件永遠(yuǎn)是最新的)。

      2. 當(dāng)同一個(gè)文件作為多個(gè)雙冒號(hào)規(guī)則的目標(biāo)時(shí)。這些不同的規(guī)則會(huì)被獨(dú)立的處理,而不是像普通規(guī)則那樣合并所有的依賴到一個(gè)目標(biāo)文件。這就意味著對(duì)這些規(guī)則的處 理就像多個(gè)不同的普通規(guī)則一樣。就是說多個(gè)雙冒號(hào)規(guī)則中的每一個(gè)的依賴文件被改變之后,make只執(zhí)行此規(guī)則定義的命令,而其它的以這個(gè)文件作為目標(biāo)的雙 冒號(hào)規(guī)則將不會(huì)被執(zhí)行。

      我們來看一個(gè)例子,在我們的Makefile中包含以下兩個(gè)規(guī)則:



      Newprog :: foo.c

      $(CC) $(CFLAGS) $< -o $@

      Newprog :: bar.c

      $(CC) $(CFLAGS) $< -o $@



      如果“foo.c”文件被修改,執(zhí)行make以后將根據(jù)“foo.c”文件重建目標(biāo)“Newprog”。而如果“bar.c”被修改那么 “Newprog”將根據(jù)“bar.c”被重建?;叵胍幌?,如果以上兩個(gè)規(guī)則為普通規(guī)時(shí)出現(xiàn)的情況是什么?(make將會(huì)出錯(cuò)并提示錯(cuò)誤信息)

      當(dāng)同一個(gè)目標(biāo)出現(xiàn)在多個(gè)雙冒號(hào)規(guī)則中時(shí),規(guī)則的執(zhí)行順序和普通規(guī)則的執(zhí)行順序一樣,按照其在Makefile中的書寫順序執(zhí)行。

      GNU make的雙冒號(hào)規(guī)則給我們提供一種根據(jù)依賴的更新情況而執(zhí)行不同的命令來重建同一目標(biāo)的機(jī)制。一般這種需要的情況很少,所以雙冒號(hào)規(guī)則的使用比較罕見。一般雙冒號(hào)規(guī)則都需要定義命令,如果一個(gè)雙冒號(hào)規(guī)則沒有定義命令,在執(zhí)行規(guī)則時(shí)將為其目標(biāo)自動(dòng)查找隱含規(guī)則。

      4.14 自動(dòng)產(chǎn)生依賴
      Makefile中,可能需要書寫一些規(guī)則來描述一個(gè).o目標(biāo)文件和頭文件的依賴關(guān)系。例如,如果在main.c中使用“#include defs.h”,那么我們可能需要如下那樣的一個(gè)規(guī)則來描述當(dāng)頭文件“defs.h”被修改以后執(zhí)行make,目標(biāo)“main.o”應(yīng)該被重建。



      main.o: defs.h



      這樣,在一個(gè)比較大型的工程中。就需要在Makefile中書寫很多條類似于這樣的規(guī)則。并且,當(dāng)在源文件中加入或刪除頭文件后,也需要小心地去修改 Makefile。這是一件很費(fèi)力、也很費(fèi)時(shí)并且容易出錯(cuò)誤的工作。為了避免這個(gè)令人討厭的問題,現(xiàn)代的c編譯器提供了通過查找源文件中的 “#include”來自動(dòng)產(chǎn)生這種依賴的功能。“GCC”支持一個(gè)“-M”的選項(xiàng)來實(shí)現(xiàn)此功能。“GCC”將自動(dòng)找尋源文件中包含的頭文件,并生成一個(gè) 依賴關(guān)系。例如,如果“main.c”只包含了頭文件“defs.h”,那么在Linxu下執(zhí)行下面的命令:



      gcc -M main.c



      其輸出是:



      main.o : main.c defs.h



      既然編譯器已經(jīng)提供了自動(dòng)產(chǎn)生依賴關(guān)系的功能,那么我們就不需要去動(dòng)手寫這些規(guī)則的依賴關(guān)系了。但是需要明確的是:在“main.c”中包含了其他的標(biāo)準(zhǔn) 庫的頭文件,其輸出的依賴關(guān)系中也包含了標(biāo)準(zhǔn)庫的頭文件。當(dāng)不需要依賴關(guān)系中不考慮標(biāo)準(zhǔn)庫頭文件時(shí),需要使用“-MM”參數(shù)。

      需要注意的是,在使用“GCC”自動(dòng)產(chǎn)生依賴關(guān)系時(shí),所產(chǎn)生的規(guī)則中明確的指明了目標(biāo)是“main.o”文件。一次在通過.c文件直接產(chǎn)生可執(zhí)行文件時(shí),作為過程文件的“main.o”的中間過程文件在使用完之后將不會(huì)被刪除。

      在舊版本的make中,使用編譯器此項(xiàng)功能通常的做法是:在Makefile中書寫一個(gè)偽目標(biāo)“depend”的規(guī)則來定義自動(dòng)產(chǎn)生依賴關(guān)系文件的命令。 輸入“make depend”將生成一個(gè)稱為“depend”的文件,其中包含了所有源文件的依賴規(guī)則描述。Makefile使用“include”指示符包含這個(gè)文 件。

      在新版本的make中,推薦的方式是為每一個(gè)源文件產(chǎn)生一個(gè)描述其依賴關(guān)系的makefile文件。對(duì)于一個(gè)源文件“NAME.c”,對(duì)應(yīng)的這個(gè) makefile文件為“NAME.d”。“NAME.d”中描述了文件“NAME.o”所要依賴的所有頭文件。采用這種方式,只有源文件在修改之后才會(huì) 重新使用命令生成新的依賴關(guān)系描述文件“NAME.o”。

      我們可以使用如下的模式規(guī)則來自動(dòng)生成每一個(gè).c文件對(duì)應(yīng)的.d文件:



      %.d: %.c

      $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \

      sed ‘s,\($*\)\.o[ :]*,\1.o $@ : ,g‘ < $@.$$$$ > $@; \

      rm -f $@.$$$$



      此規(guī)則的含義是:所有的.d文件依賴于同名的.c文件。

      第一行;使用c編譯器自自動(dòng)生成依賴文件($<)的頭文件的依賴關(guān)系,并輸出成為一個(gè)臨時(shí)文件,“$$$$”表示當(dāng)前進(jìn)程號(hào)。如果$(CC)為 GNU的C編譯工具,產(chǎn)生的依賴關(guān)系的規(guī)則中,依賴頭文件包括了所有的使用的系統(tǒng)頭文件和用戶定義的頭文件。如果需要生成的依賴描述文件不包含系統(tǒng)頭文 件,可使用“-MM”代替“-M”。

      第二行;使用sed處理第二行已產(chǎn)生的那個(gè)臨時(shí)文件并生成此規(guī)則的目標(biāo)文件。這里sed完成了如下的轉(zhuǎn)換過程。例如對(duì)已一個(gè).c源文件。將編譯器產(chǎn)生的依賴關(guān)系:

      main.o : main.c defs.h

      轉(zhuǎn)成:

      main.o main.d : main.c defs.h



      這樣就將.d加入到了規(guī)則的目標(biāo)中,其和對(duì)應(yīng)的.o文件文件一樣依賴于對(duì)應(yīng)的.c源文件和源文件所包含的頭文件。當(dāng).c源文件或者頭文件被改變之后規(guī)則將會(huì)被執(zhí)行,相應(yīng)的.d文件同樣會(huì)被更新。

      第三行;刪除臨時(shí)文件。

      使用上例的規(guī)則就可以建立一個(gè)描述目標(biāo)文件依賴關(guān)系的.d文件。我們可以在Makefile中使用include指示符將描述將這個(gè)文件包含進(jìn)來。在執(zhí)行 make時(shí),Makefile所包含的所有.d文件就會(huì)被自動(dòng)創(chuàng)建或者更新。Makefile中對(duì)當(dāng)前目錄下.d文件處理可以參考如下:



      sources = foo.c bar.c

      sinclude $(sources:.c=.d)



      例子中,變量“sources”定義了當(dāng)前目錄下的需要編譯的源文件。變量引用變換“$(sources : .c=.d)”的功能是根據(jù)需要.c文件自動(dòng)產(chǎn)生對(duì)應(yīng)的.d文件,并在當(dāng)前Makefile文件中包含這些.d文件。.d文件和其它的makefile文 件一樣,make在執(zhí)行時(shí)讀取并試圖重建他們。其實(shí)這些.d文件也是一些可被make解析的makefile文件。

      需要注意的是include指示符的書寫順序,因?yàn)樵谶@些.d文件中已經(jīng)存在規(guī)則。當(dāng)一個(gè)Makefile使用指示符include這些.d文件時(shí),應(yīng)該 注意它應(yīng)該出現(xiàn)在終極目標(biāo)之后,以免.d文件中的規(guī)則被是Makefile的終極規(guī)則。關(guān)于這個(gè)前面我們已經(jīng)有了比較詳細(xì)的討論。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多