《GNU_Make 中文手冊》筆記之一

文檔下載鏈接《GNU_Make中文手冊》


第一章 概述


  make是一個命令工具,它解釋Makefile中的規則。在Makefile文件中描述了整個工程所有文件的編譯、鏈接等規則。make執行時,根據Makefile的規則檢查文件的修改情況,決定是否執行定義的動作(那些修改過的文件將會被重新編譯),這是GNU make的執行依據。


準備知識


  編譯:把高級語言書寫的代碼轉換爲機器可識別的機器指令。編譯時,編譯器檢查高級語言的語法、函數與變量的聲明是否正確。通常,一個高級語言的源文件都可對應一個目標文件。目標文件在Linux中默認後綴爲“.o”(如“foo.c”的目標文件爲“foo.o”)。

  爲了和規則的目標文件相區別,本文將編譯高級語言後生成的目標文件稱爲.o文件

  鏈接:將多.o文件,或者.o文件和庫文件鏈接成爲可被操作系統執行的可執行程序(Linux環境下,可執行文件的格式爲“ELF”格式)。鏈接器不檢查函數所在的源文件,只檢查所有.o文件中定義的符號。將.o文件中使用的函數和其它.o或者庫文件中的相關符號進行合併,對所有文件中的符號進行重新安排(重定位),並鏈接系統相關文件(程序啓動文件等)最終生成可執行程序。鏈接過程使用GNU 的“ld”工具

  靜態庫:又稱爲文檔文件(Archive File)。它是多個.o文件的集合。Linux中靜態庫文件的後綴爲“.a”。靜態庫中的各個成員(.o文件)沒有特殊的存在格式,僅僅是一個.o文件的集合。使用“ar”工具維護和管理靜態庫。

  共享庫:也是多個.o文件的集合,但是這些.o文件時有編譯器按照一種特殊的方式生成(Linux中,共享庫文件格式通常爲“ELF”格式。共享庫已經具備了可執行條件)。模塊中各個成員的地址(變量引用和函數調用)都是相對地址。使用此共享庫的程序在運行時,共享庫被動態加載到內存並和主程序在內存中進行連接。多個可執行程序可共享庫文件的代碼段(多個程序可以共享的使用庫中的某一個模塊,共享代碼,不共享數據)。另外共享庫的成員對象可被執行(由libdl.so提供支持)。

  參考 info ld瞭解更加詳細的關於ld的說明和用法。



第二章 GNU Make介紹


  當使用make工具進行編譯時,工程中以下幾種文件在執行make時將會被編譯(重新編譯)

  a. 所有的源文件沒有被編譯過,則對各個C源文件進行編譯並進行鏈接,生成最後的可執行程序;

  b. 每一個在上次執行make之後修改過的C源代碼文件在本次執行make時將會被重新編譯

  c. 頭文件在上一次執行make之後被修改,則所有包含此頭文件的C源文件在本次執行make時將會被重新編譯


  Makefile規則包含了文件之間的依賴關係更新此規則目標所需要的命令一個簡單的Makefile描述規則如下表示:
    TARGET... : PREREQUISITES...
      COMMAND
      ...
      ...

  target:規則的目標。可以是.o文件、也可以是可執行程序文件名等。另外,目標也可以是一個make執行的動作的名稱,如目標“clean”,我們稱這樣的目標是“僞目標”。

  prerequisites:規則的依賴。生成規則目標所需要的文件名列表。通常一個目標依賴於一個或者多個文件。

  command:規則的命令行。規則所要執行的動作(任意的shell命令或者是可在shell下執行的程序)。一個規則可以有多個命令行,每一條命令佔一行。

  注意每一個命令行必須以[Tab]字符開始,[Tab]字符告訴make此行是一個命令行。這也是書寫Makefile中容易產生,而且比較隱蔽的錯誤。

  一個Makefile文件中通常還包含了除規則以外的東西。一個最簡單的Makefile可能只包含規則。make程序根據規則的依賴關係,決定是否執行規則所定義的命令的過程我們稱之爲執行規則


  示例:我們將書寫一個簡單的Makefile,來描述如何創建最終的可執行文件“edit”,此可執行文件依賴於8個C源文件和3個頭文件。Makefile文件的內容如下:


  首先,書寫時可以將一個較長行使用反斜線(\)分解爲多行,這樣可以使我們的Makefile書寫清晰、容易閱讀理解。但需要注意:反斜線之後不能有空格(這也是大家最容易犯的錯誤,錯誤比較隱蔽)。

  在這個Makefile中,我們的目標(target)就是可執行文件“edit”和那些.o文件(main.o,kbd.o,…)依賴(prerequisites)就是冒號後面的那些.c 文件 .h文件。所有的.o文件既是依賴(相對於可執行程序edit)又是目標(相對於.c和.h文件)。

  所有的命令行必需以[Tab] 字符開始,但並不是所有的以[Tab]鍵出現行都是命令行。但make程序會把出現在第一條規則之後所有以[Tab]字符開始作爲命令行來處理。

  Makefile中把那些沒有任何依賴只有執行動作的目標稱爲“僞目標”(phony targets)。目標“clean”沒有任何依賴文件,它只有一個目的,就是通過這個目標名來執行它所定義的命令。


Make如何工作


  默認的情況下,make執行的是Makefile中的第一個規則,此規則的第一個目標稱之爲“最終目的”或者“終極目標”(就是一個Makefile最終需要更新或者創建的目標)。就是說,第一個規則的第一個目標稱爲最終目標或者是終極目標

  當在shell提示符下輸入“make”命令以後,make讀取當前目錄下的Makefile文件,並將Makefile文件中的第一個目標作爲其執行的“終極目標”,開始處理第一個規則(終極目標所在的規則)

  make在執行這個規則所定義的命令之前,首先處理目標“edit”的所有的依賴文件(例子中的那些.o文件)的更新規則(以這些.o文件爲目標的規則)。對這些.o文件爲目標的規則處理有下列三種情況:

  a. 目標.o文件不存在,使用其描述規則創建它;
  b. 目標.o文件存在,目標.o文件所依賴的.c源文件、.h文件中的任何一個比目標.o文件“更新”(在上一次make之後被修改)。則根據規則重新編譯生成它;
  c. 目標.o文件存在,目標.o文件比它的任何一個依賴文件(的.c源文件、.h文件)“更新”(它的依賴文件在上一次make之後沒有被修改),則什麼也不做。

  這些.o文件所在的規則之所以會被執行,是因爲這些.o文件出現在“終極目標”的依賴列表中。在Makefile中一個規則的目標如果不是“終極目標”所依賴的(或者“終極目標”的依賴文件所依賴的),那麼這個規則將不會被執行除非明確指定執行這個規則。

  完成了對.o文件的創建(第一次編譯)或者更新之後,make程序將處理終極目標“edit”所在的規則,分爲以下三種情況:
  a. 目標文件“edit”不存在,則執行規則以創建目標“edit”。
  b. 目標文件“edit”存在,其依賴文件中有一個或者多個文件比它“更新”,則根據規則重新鏈接生成“edit”。
  c. 目標文件“edit”存在,它比它的任何一個依賴文件都“更新”,則什麼也不做。


總結一下


  a. 首先對於一個Makefile文件,“make”解析終極目標所在的規則根據其依賴文件依次(按照依賴文件列表從左到右的順序)尋找創建這些依賴文件的規則

  b. 其次爲第一個依賴文件(main.o)尋找創建規則,如果第一個依賴文件依賴於其它文件(main.c、defs.h),則同樣爲這個依賴文件尋找創建規則(創建main.c和defs.h的規則,通常源文件和頭文件已經存在,也不存在重建它們的規則)……,直到爲所有的依賴文件找到合適的創建規則。

  c. 最後,make從最後一層規則(上例目標爲main.o的規則)回退開始執行,最終完成終極目標的第一個依賴文件的創建和更新。之後對第二個、第三個、第四個……終極目標的依賴文件執行同樣的過程(上例的的順序是“main.o”、“kbd.o”、“command.o”……)。

  創建或者更新每一個規則依賴文件的過程,類似於c語言中的遞歸過程。對於任意一個規則執行的過程都是按照依賴文件列表順序,對於規則中的每一個依賴文件,使用同樣方式(按照同樣的過程)去重建它,在完成對所有依賴文件的重建之後,最後一步纔是重建此規則的目標

  更新(或者創建)終極目標的過程中,如果任何一個規則執行出錯,make就立即報錯並退出。整個過程make只是負責執行規則,一個規則的依賴關係是否正確、描述重建目標的規則命令行是否正確,make不做任何錯誤檢查

  因此,要正確的編譯一個工程,需要在提供給make程序的Makefile中來保證其依賴關係的正確性、和執行命令的正確性。


自動推導規則


  在使用make編譯.c源文件時,編譯.c源文件規則的命令可以不用明確給出。make存在一個默認的規則,能夠自動完成對.c文件的編譯並生成對應的.o文件。它執行命令“cc -c”編譯.c源文件。此默認規則稱爲make的隱含規則

  這樣,在書寫Makefile時,就可以省略掉描述.c文件和.o依賴關係的規則,而只需要給出那些特定的規則描述(.o目標所需要的.h文件。因此上邊的例子就可以以更加簡單的方式書寫,其中使用了變量“objects”。Makefile內容如下:


  假如只有一個.c的源文件mtest.c,那麼其Makefile可以簡化爲“mtest:”。更加簡單的方法是:直接在shell裏輸入“make mtest”,無需Makefile文件!

#使用規範寫法
mtest: mtest.c
  cc -o mtest mtest.c

#使用隱含規則
mtest:


清除工作目錄過程文件


  在實際應用時,清除當前目錄中編譯過程中產生的臨時文件使用如下寫法:

    .PHONY : clean

    clean :
      -rm edit $(objects)

    a. 通過“.PHONY”特殊目標將“clean”目標聲明僞目標。避免當磁盤上存在一個名爲“clean”文件時,目標“clean”所在規則的命令無法執行。

    b. 在命令行之前使用“-”,意思是忽略命令“rm”的執行錯誤



第三章 Makefile總述



Makefile的內容


  在一個完整的Makefile中,包含了5個東西:顯式規則、隱含規則、變量定義、指示符和註釋。

  > 顯式規則:它描述了在何種情況下如何更新一個或者多個被稱爲目標的文件(Makefile的目標文件)。書寫Makefile時需要明確地給出目標文件、目標的依賴文件列表以及更新目標文件所需要的命令(有些規則沒有命令,這樣的規則只是純粹的描述了文件之間的依賴關係)。

  隱含規則:它是make根據一類目標文件(典型的是根據文件名的後綴)而自動推導出來的規則。make根據目標文件的名,自動產生目標的依賴文件並使用默認的命令來對目標進行更新(建立一個規則)。

  變量定義:使用一個字符或字符串代表一段文本串,當定義了一個變量以後,Makefile後續在需要使用此文本串的地方,通過引用這個變量來實現對文本串的使用。

  Makefile指示符:指示符指明在make程序讀取makefile文件過程中所要執行的一個動作。其中包括:
    a. 讀取一個文件,讀取給定文件名的文件,將其內容作爲makefile文件的一部分;
    b. 決定(通常是根據一個變量的得值)處理或者忽略Makefile中的某一特定部分;
    c. 定義一個多行變量。

  註釋:Makefile中“#”字符後的內容被作爲是註釋內容(和shell腳本一樣)處理。如果此行的第一個非空字符爲“#”,那麼此行爲註釋行。註釋行的結尾如果存在反斜線(\),那麼下一行也被作爲註釋行。一般在書寫Makefile時推薦將註釋作爲一個獨立的行,而不要和Makefile的有效行放在一行中書寫。當在Makefile中需要使用字符“#”時,可以使用反斜線加“#”(\#)來實現(對特殊字符“#”的轉義),其表示將“#”作爲一字符而不是註釋的開始標誌。


包含其它makefile文件


  Makefile中包含其它文件使用關鍵字“include”,和c語言一致。“include”指示符告訴make暫停讀取當前的Makefile,而轉去讀取“include”指定的一個或者多個文件完成以後再繼續當前Makefile的讀取。Makefile中指示符“include”書寫在獨立的一行,其形式如下:

    include FILENAMES...

FILENAMES是shell所支持的文件名(可以使用通配符)。包含進來的Makefile中,如果存在變量或者函數的引用,它們將會被展開

  一個例子,存在三個.mk文件a.mk、b.mk、c.mk,“$(bar)”被擴展爲“bish bash”,則:

    include foo *.mk $(bar)
等價於
    include foo a.mk b.mk c.mk bish bash


指示符“include”使用場合


  a. 有多個不同的程序,由不同目錄下的幾個獨立的Makefile來描述其重建規則。它們需要使用一組通用的變量定義或者模式規則。通用的做法是將這些共同使用的變量或者模式規則定義在一個文件中,在需要使用的Makefile使用指示符“include”包含此文件。

  b. 當根據源文件自動產生依賴文件時,我們可以將自動產生的依賴關係保存在另外一個文件中,主Makefile使用指示符“include”包含這些文件。這樣的做法比直接在主Makefile中追加依賴文件的方法要明智的多。

  如果指示符“include”指定的文件絕對路徑(如/usr/src/Makefile...)和當前目錄下都找不到,make將試圖在以下目錄下查找:

  首先,查找命令行選項“-I”或者“--include-dir”指定的目錄;如果未找到,則繼續依此搜索以下目錄(如果其存在):“/usr/gnu/include”“/usr/local/include”“/usr/include”

  當在這些目錄下都沒有找到“include”指定的文件時,make將會提示一個包含文件未找到的告警提示,但是不會立刻退出。當完成讀取整個Makefile後,make將試圖使用規則來創建通過指示符“include”指定的但未找到的文件,當不能創建它時(沒有創建這個文件的規則),make將提示致命錯誤並退出,會輸出類似如下錯誤提示:

    Makefile:錯誤的行數:未找到文件名:提示信息(No such file or directory)
    Make: *** No rule to make target ‘<filename>’. Stop


兩種方式的比較


  在Makefile中使用“-include”代替“include”,可忽略由於包含文件不存在或者無法創建時的錯誤提示“-”的意思是告訴make,忽略此操作的錯誤。make繼續執行。

  使用“include FILENAMES...”:make程序處理時,如果“FILENAMES”列表中的任何一個文件不能正常讀取而且不存在一個創建此文件的規則時,make程序將會提示錯誤並退出

  使用“-include FILENAMES...”:當所包含的文件不存在或者不存在一個規則去創建它,make程序會繼續執行,只有真正由於不能正確完成終極目標的重建時(某些必需的目標無法在當前已讀取的makefile文件內容中找到正確的重建規則),纔會提示致命錯誤並退出

  爲了和其它的make程序進行兼容,也可以使用“sinclude”來代替“-include”(GNU所支持的方式)。


變量 MAKEFILES


  如果在當前環境,定義了一個“MAKEFILES”環境變量,make執行時首先將此變量的值作爲需要讀入的Makefile文件,多個文件之間使用空格分開。類似使用指示符“include”包含其它Makefile文件一樣,如果文件名非絕對路徑而且當前目錄也不存在此文件,make會在一些默認的目錄去尋找。它和使用“include”的區別:

a. 環境變量指定的makefile文件中的“目標”不會被作爲make執行的“終極目標”

b. 環境變量所定義的文件列表,在執行make時,如果不能找到其中某一個文件(不存在或者無法創建)。make不會提示錯誤,也不退出。就是說環境變量“MAKEFILES”定義的包含文件是否存在不會導致make錯誤(這是比較隱蔽的地方)。

c. make在執行時,首先讀取的是環境變量“MAKEFILES”所指定的文件列表,之後纔是工作目錄下的makefile文件,“include”所指定的文件是在make發現此關鍵字的時、暫停正在讀取的文件而轉去讀取“include”所指定的文件。

  實際應用中很少設置此變量。因爲一旦設置了此變量,在多級make調用時;由於每一級make都會讀取“MAKEFILES”變量所指定的文件,將導致執行出現混亂。不過,我們可以使用此環境變量指定一個定義了通用“隱含規則”和變量的文件,比如設置默認搜索路徑;通過這種方式設置的“隱含規則”和定義的變量可以被任何make進程使用(有點象C語言中的全局變量)。

  推薦的做法:在需要包含其它makefile文件時,使用指示符“include”來實現


變量 MAKEFILE_LIST


  make程序在讀取多個makefile文件時,在對這些文件進行解析執行之前,make讀取的文件名將會被自動依次追加變量“MAKEFILE_LIST”的定義域中。可以通過測試此變量的最後一個字獲取當前make程序正在處理的makefile文件名


Make如何解析makefile文件


  GUN make的執行過程分爲兩個階段。
  第一階段讀取所有的makefile文件(包括“MAKIFILES”變量指定的、指示符“include”指定的、以及命令行選項“-f(--file)”指定的makefile文件),內建所有的變量、明確規則和隱含規則,並建立所有目標和依賴之間的依賴關係結構鏈表。
  第二階段:根據第一階段已經建立的依賴關係結構鏈表,決定哪些目標需要更新,並使用對應的規則重建這些目標。

  變量和函數的展開問題,是書寫Makefile時容易犯錯和引起大家迷惑的地方之一。

  在make執行的第一階段中如果變量和函數被展開,那麼稱此展開是“立即”的,此時所有的變量和函數被展開在需要構建的結構鏈表的對應規則中(此規則在建立鏈表是需要使用)。

  其他的展開稱之爲“延後”的,這些變量和函數不會被“立即”展開,而是直到後續某些規則須要使用時或者在make處理的第二階段它們纔會被展開。

  > 變量取值 <

  變量定義解析的規則如下:

IMMEDIATE = DEFERRED

IMMEDIATE ?= DEFERRED

IMMEDIATE := IMMEDIATE

IMMEDIATE += DEFERRED or IMMEDIATE

define IMMEDIATE

DEFERRED

Endef

  變量使用追加符(+=)時,如果此前這個變量是一個簡單變量(使用 :=定義的)則認爲它是立即展開的,其它情況時都被認爲是“延後”展開的變量。

  > 條件語句 <

  所有使用到條件語句在產生分支的地方,make程序會根據預設條件將正確地分支展開。就是說,條件分支的展開是“立即”的。其中,包括:“ifdef”、“ifeq”、“ifndef”和“ifneq”所確定的所有分支命令。

  > 規則定義 <

  所有的規則在make執行時,都按照如下的模式展開:

    IMMEDIATE : IMMEDIATE ; DEFERRED
      DEFERRED

  其中,規則中目標和依賴如果引用其他的變量,則被立即展開。而規則的命令行中的變量引用會被延後展開。此模板適合所有的規則,包括明確規則、模式規則、後綴規則、靜態模式規則。


Make執行過程總結


  a. 依次讀取環境變量“MAKEFILES”定義makefile文件列表;

  b. 讀取工作目錄下的makefile文件(依次“GNUmakefile”,“makefile”,“Makefile”,首先找到那個就讀取那個);

  c. 依次讀取工作目錄makefile文件中使用指示符“include”包含的文件;

  d. 查找重建所有已讀取的makefile文件的規則(如果存在一個目標是當前讀取的某一個makefile文件,則執行此規則重建此makefile文件,完成以後從第一步開始重新執行);

  e. 初始化變量值,展開那些需要立即展開的變量和函數,並根據預設條件確定執行分支;

  f. 根據“終極目標”以及其他目標的依賴關係建立依賴關係鏈表;

  g. 執行除“終極目標”以外的所有的目標的規則(規則中如果依賴文件中任一個文件的時間戳比目標文件新,則使用規則所定義的命令重建目標文件);

  h. 執行“終極目標”所在的規則



第四章 Makefile規則


  Makefile文件中第一個規則的第一個目標,將會被作爲make的“終極目標”。有兩種情況的例外

a. 目標名以點號“.”開始且其後不存在斜線“/”(“./”被認爲是當前目錄;“../”被認爲是上一級目錄);

b. 模式規則的目標

當這兩種目標所在的規則是Makefile的第一個規則時,它們並不會被作爲“終極目標”

  “終極目標”是執行make的唯一目的,其所在的規則作爲第一個被執行的規則。而其它的規則是在完成重建“終極目標”的過程中被連帶出來的,所以這些目標所在規則在Makefile中的順序無關緊要。

  因此,我們書寫的makefile的第一個規則應該就是重建整個程序或者多個程序依賴關係和執行命令描述


規則語法


  通常規則的語法格式如下:

    TARGETS : PREREQUISITES
      COMMAND
      ...

或者:

  TARGETS : PREREQUISITES ; COMMAND
    COMMAND
    ...

  規則中“TARGETS”可以是空格分開的多個文件名,也可以是一個標籤(例如:執行清空的“clean”)。“TARGETS”的文件名可以使用通配符,格式“A(M)”表示檔案文件(Linux下的靜態庫.a文件)的成員“M”。。通常,規則只有一個目標文件(建議這麼做),偶爾會在一個規則中需要多個目標。

  書寫規則是我們需要注意的幾點

a. 規則的命令部分有兩種書寫方式

(1). 命令與(目標:依賴)描述在同一行命令使用分號“;”和依賴文件列表分開

(2). 命令與(目標:依賴)描述在不同行。當作爲獨立的命令行時此行必須以[Tab]字符開始

在Makefile中,在第一個規則之後出現的所有以[Tab]字符開始的行,都會被當作命令來處理。

b. Makefile中符號“$”有特殊的含義(表示變量或者函數引用),在規則中需要使用符號“$”的地方,需要書寫兩個連續的(“$$”)。

c. 對於Makefile中一個較長的行建議使用反斜線“\”將其書寫到幾個獨立的物理行上。

  一個規則告訴“make”兩件事:1.目標什麼情況下已經過期; 2. 如果需要重建目標時,如何去重建這個目標規則的中心思想是:目標文件的內容依賴文件決定,依賴文件的任何一處改動,將導致目前已經存在的目標文件的內容過期。規則的命令爲重建目標提供方法。這些命令運行在系統shell之上。


  有時,需要定義一個這樣的規則:在更新目標(目標文件已經存在)時,只需要根據依賴文件中的部分決定目標是否需要被重建,而不是在依賴文件的任何一個被修改後都重建目標。

  爲實現這一目的,對規則的依賴進行分類:一類是依賴文件被更新後,需要更新規則的目標;另一類是更新這些依賴,可不需要更新規則的目標。我們把第二類稱爲:“order-only”依賴

  書寫規則時,“order-only”依賴使用管道符號“|”開始,作爲目標的一個依賴文件。規則依賴列表中管道符號“|”邊的是常規依賴,管道符號邊的就是“order-only”依賴。這樣的規則書寫格式如下:

    TARGETS : NORMAL-PREREQUISITES| ORDER-ONLY-PREREQUISITES


文件名使用通配符


  Makefile中表示文件名時,可使用的通配符有:“*”、“?”和“[…]”。在Makefile中通配符的用法和含義和Linux(unix)的Bourne shell完全相同,但是僅限於以下兩種場合

a. 可以用在規則目標、依賴中,make在讀取Makefile時會自動對其進行匹配處理(通配符展開)

b. 可出現在規則命令中,通配符的通配處理是在shell在執行此命令時完成的。

  除這兩種情況之外,不能直接使用通配符,而是需要通過函數“wildcard。如果規則的一個文件名包含統配字符“*”、“.”等字符),使用反斜線(\)進行轉義處理。

  另外需要注意:在Linux(unix)中,以波浪線“~”開始的文件名有特殊含義。單獨使用它或者其後跟一個斜線(~/),代表當前用戶的宿主目錄。例如“~/bin”代表“/home/username/bin/”(當前用戶宿主目錄下的bin目錄)。

  在規則中,通配符會被自動展開。但在變量定義函數引用時,通配符將失效。這種情況下如果需要通配符有效,就需要使用函數“wildcard”,它的用法是:$(wildcard PATTERN...) 。在Makefile中,它被展開爲已經存在的使用空格分開的匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函數會忽略模式字符並返回空


目錄搜索


  在一個較大的工程中,一般會將源代碼和二進制文件(.o文件和可執行文件)安排在不同的目錄來進行區分管理。這種情況下,我們可以使用make提供的目錄搜索依賴文件功能(在指定的若干個目錄下自動搜索依賴文件)。


一般搜索(變量VPATH)

  通過變量“VPATH”,可指定依賴文件的搜索路徑,當規則的依賴文件在當前目錄不存在時,make會在此變量所指定的目錄下去尋找這些依賴文件。其實“VPATH”變量所指定的是Makefile中所有文件的搜索路徑,包括了規則的依賴文件目標文件

  定義變量“VPATH”時,使用空格或者冒號(:)將多個需要搜索的目錄分開。make搜索目錄的順序是按照變量“VPATH”定義中的目錄順序進行的。例如對變量的定義如下:

    VPATH = src:../headers

這樣我們就爲所有規則的依賴指定了兩個搜索目錄,“src”和“../headers”。


選擇性搜索(關鍵字vpath)

  當需要爲不類型的文件指定不同的搜索目錄時,使用make的“vpath”關鍵字(全小寫的)。它不是一個變量,而是一個make的關鍵字。它可以爲不同類型的文件(由文件名區分)指定不同的搜索目錄,使用方法有三種:

a. vpath PATTERN DIRECTORIES

  爲所有符合模式“PATTERN”的文件,指定搜索目錄“DIRECTORIES”。多個目錄使用空格或者冒號(:)分開。

b. vpath PATTERN

  清除之前爲符合模式“PATTERN”的文件設置的搜索路徑。

c. vpath

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

  “PATTERN”表示了具有相同特徵的一類文件,使用模式字符“%”表示匹配一個或者多個字符,而“DIRECTORIES”則指定了搜索此類文件目錄。例如:

    vpath %.h ../headers

其含義是:Makefile中出現的.h文件;如果不能在當前目錄下找到,則到目錄“../headers”下尋找。注意:這裏指定的路徑僅限於Makefile文件內容中出現的.h文件。並不能指定源文件中包含的頭文件所在的路徑(在.c源文件中所包含的頭文件路徑需要使用gcc的“-I”選項指定,可參考gcc的info文檔)。


目錄搜索的機制

  規則中一個依賴文件可以通過目錄搜尋找到,可能得到的是文件的完整路徑名,它卻並不是規則中列出的文件名。因此,使用目錄搜索所到的完整的文件路徑名可能需要廢棄。make在解析Makefile文件執行規則時對文件路徑保存或廢棄所依據的算法如下:

a. 首先,如果規則的目標文件在Makefile文件所在的目錄(工作目錄)下不存在,那麼就執行目錄搜尋;

b. 如果目錄搜尋成功,在指定的目錄下存在此規則的目標。那麼搜索到的完整的路徑名就被作爲臨時的目標文件被保存;

c. 對於規則中的所有依賴文件使用相同的方法處理。

d. 完成第三步的依賴處理後,make程序就可以決定規則的目標是否需要重建,兩種情況時後續處理如下:

a) . 規則的目標不需要重建:那麼通過目錄搜索得到的所有完整的依賴文件路徑名有效,同樣,規則的目標文件的完整的路徑名同樣有效。就是說,當規則的目標不需要被重建時,規則中的所有的文件完整的路徑名有效。已經存在的目標文件所在的目錄不會被改變。

b) . 規則的目標需要重建:那麼通過目錄搜索所得到的目標文件的完整的路徑名無效規則中的目標文件將會被在工作目錄下重建。就是說,當規則的目標需要重建時,規則的目標文件會在工作目錄下被重建,而不是在目錄搜尋時所得到的目錄。這裏,必須明確:此種情況只有目標文件的完整路徑名失效依賴文件的完整路徑名不會失效的,否則將無法重建目標。

  如果需要make在執行時,無論該目標是否需要重建都使用搜索到的目標文件完整路徑名,將目標文件在已存在的目錄存下進行重建,我們可以使用“GPATH”變量來指定這些目標所在的目錄。“GPATH”變量和“VPATH”變量具有相同的語法格式。


命令行和搜索目錄

  書寫命令時,我們必須保證,當依賴文件在其它目錄下被發現時,規則的命令能夠正確執行。解決這個問題的方式是在規則的命令行中使用“自動化變量”

  a. 自動化變量“$^”:代表所有通過目錄搜索得到依賴文件的完整路徑名(目錄 + 一般文件名)列表

  b. 自動化變量“$@”:代表規則的目標

  c. 自動化變量“$<”:代表規則中通過目錄搜索得到依賴文件列表第一個依賴文件

  對於一個規則我們可以如下描述:

    foo.o : foo.c
      cc -c $(CFLAGS)$^ -o$@

變量“CFLAGS”是編譯.c文件時gcc的編譯選項,可以在Makefile中給它指定明確的值、也可以使用隱含的定義值。

  規則的依賴文件列表中可以包含頭文件,而在命令行中不需要使用這些頭文件(這些頭文件,只有在make程序決定目標是否需要重建纔有意義)。這時,我們可以使用“$<”

    VPATH = src:../headers
    foo.o : foo.cdefs.h hack.h
      cc -c $(CFLAGS)$< -o$@


隱含規則和搜索目錄

  通過變量“VPATH”、或者關鍵字“vpath”指定的搜索目錄,對於隱含規則同樣有效。如果能夠在一個可以搜索的目錄中找到文件,make會使用根據搜索到的文件完整的路徑名去重建目標,相應的命令中的文件名都是使用目錄搜索得到的完整的路徑名。就是說,搜索到的目錄中去創建目標,而不是當前目錄


庫文件和搜索目錄

  Makefile中程序鏈接的靜態庫共享庫,同樣也可以通過搜索目錄得到。在書寫規則的依賴時,指定一個類似“-lNAME”的依賴文件名

  搜索名爲“libNAME.so”共享庫文件的過程:

a. 首先,在當前工作目錄下搜索;

b. 接着,在VPATH或者vpath”指定的目錄下搜索;

c. 然後,在系統庫文件所在目錄下搜索,順序是:“/lib”、“/usr/lib”和“PREFIX/lib”(在Linux系統中爲“/usr/local/lib”)。

如果在上述目錄都未找到“libNAME.so”,那麼make將會按照以上的搜索順序查找名字爲“libNAME.a”的文件。

  需要注意的是“-lNAME”只是告訴了鏈接器在生成目標時,需要鏈接的庫文件並沒有告訴make程序其依賴的庫文件應該如何重建。當所有的搜索目錄中不存在庫“libFoo”時,Make將提示沒有規則可以創建目標“XXX”需要的目標“-lFoo”

  在規則的依賴列表中如果出現“-lNAME”格式的依賴時,表示需要搜索的依賴文件名爲“libNAME.so”和“libNAME.a”,這是由變量“.LIBPATTERNS”指定的。默認情況時,“.LIBPATTERNS”的值爲:“lib%.so lib%.a”。


Makefile僞目標


  使用僞目標有兩點原因:a. 避免定義的只執行命令的目標和實際文件出現名字衝突;b. 提高執行make時的效率。

  對於第一點,將它作爲特殊目標“.PHONY”的依賴。無論在當前目錄下是否存在“clean”這個文件,輸入“make clean”之後,“rm”命令都會被執行。而且,被聲明爲僞目標後,make不會試圖查找隱含規則來創建它。這就提高了make的執行效率。

  在書寫僞目標規則時,首先需要聲明目標是一個僞目標,之後纔是僞目標的規則定義。目標“clean”的完整書寫格式應該如下:
    .PHONY: clean
    clean:
      rm *.o temp

  僞目標的另一種使用場合是在make的並行和遞歸執行過程中

  在一個目錄下如果需要創建多個可執行程序,我們可以將所有程序的重建規則在一個Makefile中描述。因爲Makefile中第一個目標是“終極目標”,約定的做法是使用一個稱爲“all”的僞目標來作爲終極目標,它的依賴文件就是那些需要創建的程序。下邊就是一個例子:

    #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

  執行make時,目標“all”被作爲終極目標。爲了完成對它的更新,make會創建(不存在)或者重建(已存在)目標“all”的所有依賴文件(prog1、prog2和prog3)。當需要單獨更新某一個程序時,我們可以通過make的命令行選項來明確指定需要重建的程序(例如:“make prog1”)。

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

  另外make存在一個內嵌隱含變量“RM”,它被定義爲:“RM = rm –f”。因此在書寫“clean”規則的命令行時可以使用變量“$(RM)”來代替“rm”。


Makefile的特殊目標


  在Makefile中,有一些名字,當它們作爲規則的目標時,具有特殊含義。它們是一些特殊的目標,GNU make所支持的特殊的目標有:

.PHONY

  目標“.PHONY”的所有依賴被作爲僞目標。僞目標所在規則定義的命令,無論目標文件是否存在都會被無條件執行

.SUFFIXES

  特殊目標“SUFFIXES”的所有依賴,指出了一系列在後綴規則中需要檢查後綴名(就是當前make需要處理的後綴)。

.DEFAULT

  Makefile中,目標“.DEFAULT”所在規則定義的命令,被用在重建那些沒有具體規則的目標(明確規則和隱含規則),使用“.DEFAULT”所指定的命令。

.PRECIOUS

  目標“.PRECIOUS”的所有依賴文件執行過程中被中斷時,make不會刪除它們。而且如果目標的依賴文件是中間過程文件同樣不會被刪除。這一點,目標“.PRECIOUS”和目標“.SECONDAY”實現的功能相同。

另外,目標“.PRECIOUS”的依賴文件也可以是一個模式,例如“%.o”。這樣可以保留有規則創建的中間過程文件。

.INTERMEDIATE

  目標“.INTERMEDIATE”的依賴文件在make時被作爲中間過程文件對待,沒有任何依賴文件的目標“.INTERMEDIATE”沒有意義。

.SECONDARY

  目標“.SECONDARY”的依賴文件作爲中間過程文件對待並且不會被自動刪除沒有任何依賴文件的目標“.SECONDARY”的含義是:將所有的文件作爲中間過程文件(不會自動刪除任何文件)。

.DELETE_ON_ERROR

  如果在Makefile中存在特殊目標“.DELETE_ON_ERROR”,如果規則的命令執行錯誤,將刪除已經被修改的目標文件

.IGNORE

  目標“.IGNORE”指定的依賴文件,則忽略創建這個文件所執行命令的錯誤。給此目標指定命令是沒有意義的。當此目標沒有依賴文件時,將忽略所有命令執行的錯誤。

.LOW_RESOLUTION_TIME

  目標“.LOW_RESOLUTION_TIME”的依賴文件被make認爲是低分辨率時間戳文件。給目標“.LOW_RESOLUTION_TIME”指定命令是沒有意義的。

  通常文件的時間輟都是高分辨率的,make在處理依賴關係時、對規則目標-依賴文件的高分辨率的時間戳進行比較,判斷目標是否過期。類似“cp -p”這樣的命令,在根據源文件創建目的文件時,所產生的目的文件的高分辨率時間輟的細粒度部分被丟棄(來源於源文件)。這個特殊的目標主要作用是,彌補系統在沒有提供修改文件高分辨率時間戳機制的情況下某些命令在make中的一些缺陷

  對於靜態庫文件(文檔文件)成員的更新也存在這個問題。make在創建或者更新靜態庫時,會自動將靜態庫的所有成員作爲目標“.LOW_RESOLUTION_TIME”的依賴。

.SILENT

  出現在目標“.SILENT”的依賴列表中的文件,make在創建這些文件時,不打印出重建此文件所執行的命令。同樣,給目標“.SILENT”指定命令行是沒有意義的。現行版本make支持目標“.SILENT”的功能和用法是爲了和舊版本的兼容。在當前版本中如果需要禁命令執行過程的打印,可使用make的命令行參數“-s”或者“--silent”

.EXPORT_ALL_VARIABLES

  此目標應該作爲一個簡單的沒有依賴的目標,其功能含義之後所有的變量傳遞給子make進程

.NOTPARALLEL

  Makefile中,如果出現目標“.NOPARALLEL”,則所有命令按照串行方式執行,即使存在make的命令行參數“-j”。但在遞歸調用的子make進程中,命令可以並行執行。此目標不應該有依賴文件,所有出現的依賴文件將被忽略。

  所有定義的隱含規則後綴作爲目標出現時,都被視爲特殊目標,兩個後綴串聯起來也是如此,例如“.c.o”。這樣的目標被稱爲後綴規則的目標,這種定義方式是已經過時的定義隱含規則的方法(目前,這種方式還被用在很多地方)。原則上,如果將其分爲兩個部分、並將它們加到後綴列表中,任何目標都可採用這種方式來表示。實際中,後綴通常以“.”開始,因此,以上的這些特別目標同樣是以“.”開始。


多目標與多目標規則


  一個規則中可以有多個目標,規則所定義的命令對所有的目標有效。一個具有多目標的規則相當於多個規則。規則的命令不同的目標執行效果不同,因爲在規則的命令中可能使用了自動環變量“$@”。多目標規則意味着所有的目標具有相同的依賴文件。多目標通常用在以下兩種情況:

a. 僅需要一個描述依賴關係的規則,不需要在規則中定義命令。例如

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

這個規則實現了同時給三個目標文件指定一個依賴文件。

b. 對於多個具有類似重建命令的目標。重建這些目標的命令並不需要是完全相同,因爲可以在命令行中使用自動環變量$@來引用具體的目標,完成對它的重建。例如規則:

bigoutput littleoutput : text.g

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

其等價於:

bigoutput : text.g

generate text.g -big > bigoutput

littleoutput : text.g

generate text.g -little > littleoutput

  例子中的“generate”根據命令行參數來決定輸出文件的類型。使用了make的字符串處理函數“subst來根據目標產生對應的命令行選項。

  雖然在多目標的規則中,可以根據不同的目標使用不同的命令(在命令行中使用自動化變量“$@”)。但是,多目標的規則並不能做到,根據目標文件自動改變依賴文件。需要實現這個目的是,要用到make的靜態模式

  Makefile中,一個文件可以作爲多個規則的目標(多個規則中只能有一個規則定義命令)。這種情況時,以這個文件爲目標的規則的所有依賴文件將會被合併成此目標一個依賴文件列表。

  對於一個多規則的目標,重建此目標的命令只能出現在一個規則中(可以是多條命令)。某些情況,需要相同的目標使用不同的規則中所定義的命令,我們需要使用另外一種方式——“雙冒號”規則來實現。

  一個僅僅描述依賴關係的規則,可用來給出一個或做多個目標文件的依賴文件。例如,我們使用多目標的方式來書寫Makefile:

objects = foo.o bar.o

foo.o : defs.h

bar.o : defs.h test.h

$(objects) : config.h

  Makefile中變量“objects”,定義爲所有的需要編譯生成的.o文件的列表。源文件增加或者刪除了包含的頭文件以後,不用修改已經存在的Makefile規則只需要增加或者刪除某一個.o文件依賴的頭文件。在一個大的工程中,對於一個單獨目錄下的.o文件的依賴規則,建議使用此方式。規則中頭文件的依賴描述規則也可以使用gcc自動產生。

  我們也可通過一個變量增加目標的依賴文件,使用make的命令行來指定某一個目標的依賴頭文件,例如:

extradeps=

$(objects) : $(extradeps)

它的意思是:如果我們執行“make extradeps=foo.h,那麼foo.h”將作爲所有的.o文件的依賴文件。當然我們只執行“make”的話,就沒有指定任何文件作爲.o文件的依賴文件。

  在多規則的目標中,如果目標的任何一個規則沒有定義重建此目標的命令,make將會尋找一個合適的隱含規則來重建此目標


靜態模式


  靜態模式規則:規則存在多個目標,並且不同的目標可以根據目標文件的名字自動構造依賴文件。靜態模式規則比多目標規則更通用,它不需要多個目標具有相同的依賴。但是靜態模式規則中的依賴文件必須是相類似的而不是完全相同的

  首先,我們來看一下靜態模式規則的基本語法:
    TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...
      COMMANDS
      ...

目標模式(TAGET-PATTERN)的目標名字中抽取一部分字符串(稱爲“莖”)。使用“莖”替代依賴模式(PREREQ-PATTERNS)中的相應部分產生對應目標的依賴文件

  看一個例子,根據相應的.c文件來編譯生成“foo.o”和“bar.o”文件:

objects = foo.o bar.o

all: $(objects)

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

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

例子中,規則描述了所有的.o文件的依賴文件爲對應的.c文件,對於目標“foo.o”,取其莖“foo”替代對應的依賴模式“%.c”中的模式字符“%”之後可得到目標的依賴文件“foo.c”。

  如果一個文件列表中的一部分符合某一種模式,而另外一部分符合另外一種模式,這種情況下我們可以使“filter”函數來對這個文件列表進行分類,在分類之後對確定的某一類使用模式規則。例如:

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))的結果爲“bar.o lose.o”。“filter”函數過濾不符合“%.o”模式的文件名而返回所有符合此模式的文件列表。

  我們通過另外一個例子來看一下自動環境變量“$*靜態模式規則中的使用方法

bigoutput littleoutput : %output : text.g

generate text.g -$* > $@

當執行此規則的命令時,自動環變量“$*”被展開爲“莖”,在這裏就是“big”和“little”。

  靜態模式規則對一個較大工程的管理非常有用。它可以對整個工程的同一類文件的重建規則進行一次定義,從而實現對整個工程中此類文件指定相同的重建規則通常的做法將生成同一類目標的模式定義在一個make.rules的文件中,在工程各個模塊的Makefile中包含此文件


靜態模式和隱含規則

  Makefile中,靜態模式規則被定義爲隱含規則的模式規則經常使用。兩者相同的地方:都用目標模式和依賴模式構建目標的規則中的文件依賴關係兩者不同的地方:在執行時使用它們的時機

  隱含規則:可被用在任何和它相匹配的目標上,當存在多個隱含規則和目標模式相匹配時只執行其中的一個規則,具體執行哪一個規則取決於定義規則的順序

  靜態模式規則只能用在規則中明確指出的那些文件的重建過程中。不能用在除此之外的任何文件的重建過程中,並且它對指定的每一個目標來說是唯一的。

  靜態模式規則相比隱含模式規則,有以下兩個優點

  a. 不能根據文件名通過詞法分析進行分類的文件,我們可以明確列出這些文件,並使用靜態模式規則重建其隱含規則

  b. 對於無法確定工作目錄內容,並且不能確定是否此目錄下的無關文件會使用錯誤的隱含規則而導致make失敗的情況。當存在多個適合此文件的隱含規則時,使用哪一個隱含規則取決於其規則的定義順序。這種情況下我們使用靜態模式規則就可以避免這些不確定因素,因爲靜態模式中,指定的目標文件有明確的規則來描述其依賴關係和重建命令。


雙冒號規則


  雙冒號規則使用“::”代替普通規則的“:”得到的規則。雙冒號規則允許在多個規則中同一個目標指定不同的重建目標的命令

  首先需要明確:Makefile中,一個目標可以出現在多個規則,但必須是同一類型的規則(要麼都是普通規則,要麼都是雙冒號規則)。雙冒號規則和普通規則的處理的不同點表現在以下幾個方面:

  a. 對於一個沒有依賴而只有命令行的雙冒號規則,當引用此目標時,規則的命令將會被無條件執行;而普通規則,當規則的目標文件存在時,此規則的命令永遠不會被執行(目標文件永遠是最新的)。

  b. 當同一個文件作爲多個雙冒號規則的目標時,這些不同的規則會被獨立的處理;而不是像普通規則那樣,合併所有的依賴到一個目標文件。

  我們來看一個例子,在我們的Makefile中包含以下兩個規則:

    Newprog :: foo.c
      $(CC) $(CFLAGS) $< -o $@
    Newprog :: bar.c
      $(CC) $(CFLAGS) $< -o $@

  如果“foo.c”文件被修改,執行make以後將根據“foo.c”文件重建目標“Newprog”;而如果“bar.c”被修改那麼“Newprog”將根據“bar.c”被重建。回想一下,如果以上兩個規則爲普通規時出現的情況是什麼?(make將會出錯並提示錯誤信息)。

  GNU make的雙冒號規則,給我們提供一種根據依賴的更新情況執行不同的命令,來重建同一目標的機制。一般這種需要的情況很少,所以雙冒號規則的使用比較罕見


自動產生依賴


  Makefile中,有時需要書寫一些規則來描述一個.o文件和頭文件的依賴關係。gcc通過“-M”選項來實現此功能,使用“-M”選項gcc將自動找尋源文件中包含的頭文件,並生成文件的依賴關係

  需要明確的是:如果在“main.c”中包含了標準庫的頭文件,使用gcc的“-M”選項時,其輸出結果中也包含對標準庫的頭文件的依賴關係描述。不需要考慮標準庫頭文件,對於gcc需要使用“-MM”選項

  舊版本的make中,使用編譯器此項功能通常的做法是:在Makefile中書寫一個僞目標“depend”的規則,來定義自動產生依賴關係文件的命令。輸入“make depend”將生成一個稱爲“depend”的文件,其中包含了所有源文件的依賴規則描述。Makefile中使用“include”指示符包含這個文件。

  新版本的make中,推薦的方式是:每一個源文件產生一個描述其依賴關係的makefile文件。對於一個源文件“NAME.c”,對應的這個makefile文件爲“NAME.d”。“NAME.d”中描述了文件“NAME.o”所要依賴的所有頭文件。採用這種方式,只有源文件在修改之後纔會重新使用命令生成新的依賴關係描述文件“NAME.d”。

  我們可以使用如下的模式規則,來自動生成每一個.c文件對應的.d文件

%.d: %.c

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

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

rm -f $@.$$$$

此規則的含義是:所有的.d文件依賴於同名的.c文件。

  第一行:使用c編譯器自動生成依賴文件($<)的頭文件的依賴關係,並輸出爲一個臨時文件,“$$$$”表示當前進程號。

  第二行使用sed處理第二行已產生的那個臨時文件,並生成此規則的目標文件。這裏sed完成了如下的轉換過程。例如對已一個.c源文件,將編譯器產生的依賴關係:
  main.o : main.c defs.h
轉成:
  main.o main.d : main.c defs.h

  第三行:刪除臨時文件。

  Makefile中對當前目錄下.d文件處理可以參考如下:
    sources = foo.c bar.c
    sinclude $(sources:.c=.d)

  變量“sources”定義了當前目錄下的需要編譯的源文件。變量引用置換“$(sources : .c=.d)”的功能是根據變量“source”指定的.c文件自動產生對應的.d文件,並在當前Makefile文件中包含這些.d文件。.d文件和其它的makefile文件一樣,make在執行時讀取並試圖重建它們。

  需要注意的是:include指示符的書寫順序,因爲在這些.d文件中已經存在規則。當一個Makefile使用指示符include這些.d文件時,應該注意它應該出現在終極目標之,以免.d文件中的規則被是Makefile的終極規則。





發佈了33 篇原創文章 · 獲贊 8 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章