《GNU_Make 中文手冊》筆記之二 ing


第5章  規則的命令


命令回顯


  通常,make在執行命令行之前,會把要執行的命令行輸出到標準輸出設備。我們稱之爲“回顯”,就好像我們在shell環境下輸入命令執行時一樣。

  如果規則的命令行字符“@”開始,則不會回顯命令。另外,如果使用make的命令行參數-n--just-print,那麼make執行時只顯示所要執行的命令包括“@”字符開始的命令),但會真正的去執行這些命令。該選項對於我們調試Makefile非常有用,可以按執行順序打印出Makefile中所有需要執行的命令。make參數“-s”或“--slient”則是禁止所有執行命令的顯示,就好像所有的命令行均使用“@”開始一樣。


命令執行


  規則中,當目標需要被重建時,此規則所定義的命令將會被執行。如果是多行命令,那麼每一行命令將在一個獨立的子shell進程中被執行多行命令之間的執行是相互獨立的。在Makefile中書寫在同一行中的多個命令屬於一個完整的shell命令行,書寫在獨立行的一條命令是一個獨立的shell命令行。

  把多條命令寫在一行上,用分號“;分隔。這樣纔是一個完整的shell命令行。如果希望把一個完整的shell命令行寫在多行上,需要使用反斜槓(\來對處於多行的命令進行連接,表示他們是一個完整的shell命令行。例如:

    foo : bar/lose
      cd bar;gobble lose > ../foo

或者:

    foo : bar/lose
      cd bar; \
      gobble lose > ../foo

上例是在同一行中書寫兩個命令,兩者同屬於一個完整的shell命令行。後者使用反斜槓將一個完整的shell命令行書寫在兩行。


併發執行命令


  通過make的命令行選項-j或者--job,告訴make在同一時刻可以允許多條命令同時被執行並行執行命令所帶來的問題如下:

  a. 多個同時執的命令的輸出信息,將同時被輸出到終端。當出現錯誤時很難根據一大堆凌亂的信息來區分哪條命令執行錯誤

  b. 在同一時刻可能會存在多個命令執行進程同時讀取標準輸入,但是對於標準輸入設備來說,在同一時刻只能存在一個進程訪問它。爲了解決這個問題。我們可以修改Makefile規則的命令,使之在執行過程中避免使用標準輸入。當然也可以只存在一個命令在執行時會訪問標準輸入流的Makefile;

  c. 會導致make的遞歸調用出現問題。

  如果make在執行時,由某種原因(包括信號)被中止,此時它的子進程(那些執行規則命令行的shell子進程)正在運行,那麼make將等到所有子進程結束之後才真正退出


命令執行的錯誤


  通常:規則中的命令在運行結束後,make會檢測命令執行的返回狀態,如果返回成功,那麼就啓動另外一個子shell來執行下一條命令。規則中的所有命令執行完成之後,這個規則就執行完成了。如果一個規則中的某一個命令出錯(返回非0狀態),make就會放棄對當前規則後續命令的執行,也有可能會終止所有規則的執行。

  一些情況下,規則中一個命令的執行失敗,並不代表規則執行的錯誤。爲了忽略一些無關命令執行失敗的情況,我們可以在命令之前加一個減號“-(在[Tab]字符之後)。命令中的“-”號會在shell解析並執行此命令之前被去掉,shell所解釋的只是純粹的命令,“-”字符是由make來處理的。

  在執行make時,如果使用命令行選項“-i”或者“—ignore-errors”, make將忽略所有規則中命令執行的錯誤

  當使用make的“-i”選項或者使用“-”字符忽略命令執行的錯誤時,make始終命令的執行結果作爲成功來對待。但提示錯誤信息,同時提示這個錯誤被忽略。

  make的命令行選項-k或者--keep-going來通知make,在出現錯誤時不立即退出,而是繼續後續命令的執行,直到無法繼續執行命令時才異常退出。一般make的“-k”參數在實際應用中,主要用途是:當同時修改了工程中的多個文件,“-k”參數可以幫助我們確認對哪些文件的修改是正確的(可以被編譯),那些文件的修改是不正確的(不能正確編譯)。例如我們修改了工程中的20個源文件,修改完成之後使用帶“-k”參數的make,它可以一次性找出修改的20個文件中哪些是不能被編譯。

  通常情況下,執行失敗的命令一旦改變了它所在規則的目標文件,則這個改變了的目標可能就不是一個被正確重建的文件,但是這個文件的時間戳已經被更新過了(包括使用信號強制中止命令執行的情況)。因此下一次執行make時,由於時間戳更新它將不會被重建,將最終導致終極目標不能被正確重建。推薦的做法是:在make執行失敗時,修改錯誤之後執行make之前,使用“make clean明確地刪除第一次錯誤重建的所有目標


make的遞歸執行


  make的遞歸過程:在Makefile中使用“make”作爲一個命令,來執行本身或者其它makefile文件的過程。遞歸調用在一個存在多級子目錄的項目中非常有用。

  例如,當前目錄下存在一個“subdir”子目錄,在這個子目錄中有描述此目錄編譯規則的makefile文件,在執行make時需要從上層目錄(當前目錄)開始並完成它所有子目錄的編譯。那麼在當前目錄下可以使用這樣一個規則來實現對這個子目錄的編譯:

    subsystem:
      cd subdir && $(MAKE)

其等價於規則:

    subsystem:
      $(MAKE) -C subdir

規則中“$(MAKE)”是對變量“MAKE”的引用,規則命令的意思是:進入子目錄,然後在子目錄下執行make。

  在make的遞歸調用中,需要了解一下變量“CURDIR”,此變量代表make的工作目錄。當使用-C”選項進入一個子目錄後,此變量將被重新賦值。如果沒有對此變量進行顯式的賦值,那麼它代表make的工作目錄。


變量MAKE

  當規則命令行中變量MAKE時,可以改變make-t”(“--touch”)-n”(“--just-print”)-q”(“--question”)命令行選項的效果。它所實現的功能,和在規則中命令行首使用字符“+”的效果相同。t”(“-t”選項用來更新所有目標的時間戳,而不執行任何規則的命令。如果使“cd subdir && $(MAKE)”作爲規則的命令行,執行“make -t”就可以實現我們的初衷。

  變量“MAKE”的特點是:在規則的命令行中使用變量“MAKE”標誌“-t”、“-n”和“-q”這個命令的執行中不起作用。儘管這些選項是告訴make不執行規則的命令行,但包含變量“MAKE”的命令行除外,它們會被正常執行。同時,執行make的命令行選項參數通過變量“MAKEFLAGS傳遞給子目錄下的make程序


變量和遞歸

  在make的遞歸執行過程中,上層make可以明確指定將一些變量的定義通過環境變量的方式傳遞子make過程。沒有明確指定需要傳遞的變量,上層make不會將其所執行的Makefile中定義的變量傳遞給子make過程。使用環境變量傳遞上層所定義的變量時,上層所傳遞給子make過程的變量定義,不會覆蓋子make過程所執行makefile文件中的同名變量定義除非使用make的-e”選項

  當一個變量使用“export”進行聲明後,變量和它的值將被加入到當前工作的環境變量中。上層make將環境變量明確聲明執行make之前已經存在的環境變量)和使用命令行指定的變量(如命令“make CFLAGS +=-g”或者“make –e CFLAGS +=-g”)傳遞給子make程序,通常這些變量由字符、數字和下劃線組成。

  存在兩個特殊的變量,“SHELL”和“MAKEFLAGS”,除非使用指示符“unexport”對進行聲明,否則在整個make的執行過程中,始終被自動的傳遞給所有的子make。另外一個變量“MAKEFILES”,如果此變量有值(不爲空),那麼同樣被自動的傳遞給子make。

  指示符“export”或者“unexport”的參數(變量部分),如果它是對一個變量或者函數引用,會被立即展開,並賦值給export或者unexport的變量。

  一個不帶任何參數的指示符“export”指示符:
    export
含義是將此Makefile中定義的所有變量傳遞給子make過程。需要說明的是:單獨使用“export”來導出所有變量的行爲,是老版本GNU make所默認的。但是在新版本的GNU make中取消了這一默認的行爲。因此在編寫和老版本GNU make兼容的Makefile時,需要使用特殊目標“.EXPORT_ALL_VARIABLES”來代替“export”,此特殊目標的功和不帶參數的“export”相同。

  在多級遞歸調用的make執行過程中,變量“MAKELEVEL代表調用的深度。在make一級級的執行過程中變量“MAKELEVEL”的值不斷的發生變化,通過它的值我們可以瞭解當前make遞歸調用的深度。最上一級時“MAKELEVEL”的值爲“0”、下一級時爲“1”、再下一級爲“2”。

  “MAKELEVEL”變量,主要用在條件測試指令中。例如:我們可以通過測試此變量的值,來決定是否執行遞歸方式的make調用或者其他方式的make調用。


命令行選項和遞歸

  在make的遞歸執行過程中,最上層(可以稱之爲主控)make的命令行選項“-k”、“-s”等,會被自動的通過環境變量“MAKEFLAGS”傳遞給子make進程。需要注意的是有幾個特殊的命令行選項例外,他們是:“-C”、“-f”、“-o”和“-W”,這些命令行選項是不會被賦值給變量“MAKEFLAGS”的。

  執行多級的make調用時,當不希望傳遞“MAKEFLAGS”的給子make時,需要在調用子make時,對這個變量進行賦空。例如:
    subsystem:
      cd subdir && $(MAKE) MAKEFLAGS=

此規則取消了子make執行時,對父make命令行選項的繼承(將變量“MAKEFLAGS”的值賦爲空)。


-w選項

  在多級make的遞歸調用過程中,選項“-w”或者“--print-directory”可以讓make在開始編譯一個目錄之前完成此目錄的編譯之後給出相應的提示信息,方便開發人員跟蹤make的執行過程。

  通常,選項“-w”會被自動打開。在主控Makefile中當如果使用“-C”參數來爲make指定一個目錄或者使用“cd”進入一個目錄時,“-w”選項會被自動打開。主控make可以使用選項“-s”(“--slient”)來禁止此選項。


定義命令包


  將一組命令進行類似c語言函數一樣的封裝通過名字對這一組命令進行引用。在GNU make中,可以使用指示符“define”來完成這個功能。

  通常,我們把使用“define”定義的一組命令稱爲一個命令包。定義一個命令包的語法以“define”開始以“endef”結束。需要說明的是:使用“define”定義的命令包中,命令體中變量函數的引用不會展開。命令包是使用一個變量來表示的,因此我們就可以按使用變量的方式使用它。命令包中所有命令中對其它變量的引用,在規則被執行時會被完全展開。例如:

    define run-yacc
    yacc $(firstword $^)
    mv y.tab.c $@
    endef

這裏,“run-yacc”是這個命令包的名字。在“define”和“endef”之間的命令就是命令包的主體。例如這樣一個規則:
    foo.c : foo.y
      $(run-yacc)

此規則在執行時,我們來看一下命令包中的變量的替換過程:1. 命令包中的“$^”會被“foo.y”替換;2. “$@”被“foo.c”替換。

  當在一個規則中引用一個已定義的命令包時,命令包中的命令體會被原封不動地展開在引用它的地方(和 c語言中的宏一樣)。這些命令就成爲規則的命令。



第6章  Makfile中的變量


  在Makefile中變量有以下幾個特徵

  a. Makefile中變量和函數的展開規則命令行中的變量和函數以外),是在make讀取makefile文件時進行的,這裏的變量包括了使用“=”定義和使用指示符“define”定義的。

  b. 變量可以是所有我們能夠想到的事物

  c. 變量名是不包括“:”、“#”、“=”、前置空白和尾空白的任何字符串。定義一個變量最好只包含字母數字下劃線。因爲其它字符可能會在make的後續版本中被賦予特殊含義,並且這樣命名的變量對於一些shell來說是不能被作爲環境變量來使用的。

  d. 變量名是大小寫敏感的。推薦的做法:內部定義的一般變量使用小寫方式,而對於一些參數列表(例如:編譯選項CFLAGS)採用大寫方式。

  e. 另外有一些變量名只包含了一個或者很少的幾個特殊的字符(符號),稱它們爲自動化變量,像“$<”、“$@”、“$?”、“$*”等。


變量的引用


  變量的引用方式:“$(VARIABLE_NAME)”或者“${VARIABLE_NAME}”來引用一個變量的定義。例如:“$(foo) ”或者“${foo}”。變量的展開過程和c語言中的宏展開的過程相同,是一個嚴格的文本替換過程。注:美元符號“$”在Makefile中有特殊的含義,所有在命令或者文件名中使用“$”時需要用兩個美元符號“$$”來表示

  Makefile中在單字符變量的引用,可以直接使用“$x”的格式來實現,自動化變量也使用這種格式。例如,“$PATH”在Makefile中實際上是“$(P)ATH”。這一點shell中變量的引用方式不同,shell中變量的引用可以是“${xx}”或者“$xx”格式。但在Makefile中多字符變量名的引用只能是$(xx)”或者“${xx}”格式


兩種變量定義(賦值)


  在GNU make中,變量的定義有兩種方式(或者稱爲風格),區別在於:a. 定義方式;b. 展開時機。


遞歸展開式變量

  遞歸方式擴展的變量的定義,是通過“=或者使用指示符“define”定義的。在變量定義時,變量值中對其他變量的引用不會被替換展開;而是變量引用處替換展開的同時,它所引用的其它變量纔會被一同替換展開

  “遞歸展開”式變量優點是:在定義時,可以引用之前未定義的變量(可能在後續部分定義,或者是通過make的命令行選項傳遞的變量)。其缺點是:可能導致make陷入到無限的變量展開過程中,最終使make執行失敗。例如:

    CFLAGS = $(include_dirs) -O

    include_dirs = -Ifoo -Ibar

“CFLAGS”會在命令中被展開爲“-Ifoo -Ibar -O”,定義中使用了其後定義的變量“include_dirs”。給這個變量追加值:

    CFLAGS = $(CFLAGS) –O

會導致make對變量“CFLAGS”的無限展過程中去(這種定義就是變量的遞歸定義)。

  第二個缺點:如果使用了函數,那麼包含在變量值中的函數總會在變量被引用的地方執行(變量被展開時)。因爲在這種風格中,對函數引用的替換展開發生在變量展開的過程中,而不是在定義這個變量的時候。這樣所帶來的問題是:使make的執行效率降低(每一次在變量被展開時,都要展開他所引用的函數);另外在某些時候會出現一些變量和函數的引用出現非預期的結果。


直接展開式變量

  “直接展開”式變量,使用“:=”定義,變量值中對其他量或者函數的引用定義變量時展開(對變量進行替換)。和遞歸展開式變量不同:此風格變量在定義時,就完成了對所引用變量和函數的展開,因此不能實現對其後定義變量的引用

  在複雜的Makefile中,推薦使用直接展開式變量,儘量避免和減少遞歸式變量的使用


操作符“?=”

  GNU make中,還有一個被稱爲條件賦值的賦值操作符?=。被稱爲條件賦值是因爲:只有此變量在之前沒有賦值的情況下纔會對這個變量進行賦值。例如:
    FOO ?= bar
其等價於:
    ifeq ($(origin FOO), undefined)
    FOO = bar
    endif

含義是:如果變量“FOO”在之前沒有定義,就給它賦值“bar”。否則不改變它的值。


變量的高級用法


變量的替換引用

  對於一個已經定義的變量,可以使用替換引用將其值中的後綴字符(串)使用指定的字符(字符串)替換。格式爲$(VAR:A=B)(或者${VAR:A=B}),意思是,替換變量“VAR”中所有“A”字符結尾的字爲“B”結尾的字。例如:

    foo := a.o b.o c.o
    bar := $(foo:.o=.c)

在這個定義中,變量“bar”的值就爲“a.c b.c c.c”。

  變量的替換引用其實是函數“patsubst”的一個簡化實現。另外一種引用替換的技術使用功能更強大的“patsubst”函數,它的格式和上面“$(VAR:A=B)”的格式相類似,不過需要在“A”和“B”中需要包含模式字符“%。例如:
    foo := a.o b.o c.o
    bar := $(foo:%.o=%.c)

這個例子同樣使變量“bar”的值爲“a.c b.c c.c”。這種格式的替換引用方式比第一種方式更通用


變量取值


一個變量可以通過以下幾種方式來獲得值:

  a. 運行make時,通過命令行選項來取代一個已定義的變量值;

  b. 在makefile文件中,通過賦值或者使用“define”來爲一個變量賦值;

  c. 將變量設爲系統環境變量。所有系統環境變量都可以被make使用。

  d. 自動化變量,在不同的規則中自動化變量會被賦予不同的值。它們每一個都有單一的習慣性用法。

  e. 一些變量具有固定的值(隱含變量)。


變量定義


  Makefile中變量的定義,通過=”(遞歸方式或者:=”(靜態方式來實現的。靜態方式或者說直接展開方式實現時,如果存其他變量或者函數的引用,在定義時被替換展開。定義一個變量時需要明確以下幾點:

a. 變量名之中可以包含函數或者其它變量引用,make在讀入此行時,根據已定義情況,進行替換展開而產生實際的變量名;

b. 變量的定義值在長度上沒有限制。變量定義較長時,一個好的做法就是將比較長的行分多個行來書寫,行與行之間使用反斜槓(\)連接,表示一個完整的行;

c. 當引用一個沒有定義的變量時,默認值爲

d. 一些特殊的變量在make中有內嵌固定的值(隱含變量),允許顯式地重新賦值

e. 自動環變量的值,不能在Makefile中進行顯式的修改

f. 如果你希望僅對一個之前未定義過的變量進行賦值,那麼可以使用速記符?=”(條件方式)來代替“=”或者“:=”來實現。


追加變量值


  通常,一個通用變量在定義之後的其他一個地方,可以對其值進行追加。在Makefile中使用“+=”(追加方式來實現對一個變量值的追加操作,使用空格和原有值分開






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章