GNU工程管理器make與Makefile教程

藍月心語發表於2018-05-18

  版權宣告本文章參考了陳皓先生的《跟我一起寫makefile》,並根據最新的《GNU make手冊》(截止2018年5月),以及《Linux man pages》做了修改,增添了一部分內容。未經作者允許,嚴禁用於商業出版,否則追究法律責任。網路轉載請註明出處,這是對原創者的起碼的尊重!!!


1 工程管理器make

1.1 make簡介

  • make是什麼?
    Make是GCC提供的一種半自動化的工程管理器。所謂的半自動化是指在使用工程管理器之前需要人工編寫程式的編譯規則,所有的編譯規則都儲存在Makefile檔案中,全自動化工程管理器會在編譯程式前自動生成Makefile檔案。

  • 為什麼要有工程管理器Make?
    因為在實際的開發過程中,僅僅通過gcc命令對程式進行編譯時非常低效的,原因主要有兩點:

    • 程式往往是由多個原始檔組成的,原始檔的個數越多,那麼gcc的命令列就會越長。此外,各種編譯規則也會加大gcc命令列的複雜度,所以在開發除錯的過程中,通過輸入gcc命令列來編譯程式是很麻煩的。
    • 在程式的整個開發過程中,除錯的工作量佔到了整體工作量的70%以上。在除錯程式的過程中,每次除錯一般只會修改部分原始檔。而在使用gcc命令列編譯程式時,gcc會把那些沒有被修改的原始檔一起編譯,這樣就會影響變異的總體效率。
  • 工程管理器Make有哪些優越性?

    • 使用方便。通過命令“make”就可以啟動MAKE工程管理器對程式進行編譯,所以不再需要每次都輸入GCC命令列。MAKE啟動後會根據Makefile檔案中的編譯規則命令自動對原始檔進行編譯和連結,最終生成可執行檔案。
    • 除錯效率高。為了提高編譯程式的效率,Make會檢查每個原始檔的修改時間(時間戳)。只有在上次編譯之後被修改的原始檔才會在接下來的編譯過程中被編譯和連結,這樣就能避免多餘的編譯工作量。為了保證原始檔具有正確的時間戳,必須保證作業系統時間的準確性。

1.2 make的退出碼

退出碼 描述
0 表示成功執行。
1 使用make的“-q”選項時,如果指定目標需要更新,make退出碼1,否則為0。
2 如果make執行時出現任何錯誤

1.3 make命令詳解

命令:make [options] [target] …
描述:根據Makefile自動編譯。預設執行第一個目標。

短選項 長選項 描述
-b, -m 為了相容性,忽略這些選項
-B –always-make 無條件重建所有目標,即使目標是最新的
-C dir –directory=dir 切換到dir目錄,讀取並執行makefile檔案。預設當前目錄。使用多個-C命令列選項時,後一個指定的目錄是前一個指定的目錄的子目錄。
-d 列印大量除錯資訊。除錯資訊指出正在考慮重新建立哪些檔案,正在比較哪些檔案時間以及結果如何,哪些檔案真的需要重編譯,哪些隱式規則會被考慮並應用
–debug[=FLAGS] 列印各種除錯資訊。如果FLAGS被省略,則與-d相同。FLAGS為b適用於基本除錯,v適用於更詳細的基本除錯,i適用於顯示隱式規則,j適用於呼叫命令的詳細資訊,m適用於除錯時重新生成檔案。n禁用所有以前的除錯標誌。
-e –environment-overrides 系統環境變數將覆蓋 makefile 中定義的同名變數
-f FILE –file=file, –makefile=FILE 讀取FILE作為一個makefile。預設讀取GNUmakefile、makefile、Makefile
-h 列印幫助訊息。
-i –ignore-errors 執行某個命令時如果發生錯誤則忽略並繼續往下執行,不指定該選項則停止
-I dir –include-dir=dir 指定makefile中引入的其它makefile檔案的搜尋目錄,使用多個-I 選項時,make 將按照順序依次在 dir目錄下搜尋
-j [N] –jobs[=jobs] 同時允許N個任務;無參數列明允許無限個任務。如果多個-j選項,則最後一個有效
-k –keep-going 當某些目標無法建立時儘可能多的繼續。
-l [N] –load-average[=load] 不開始新的作業除非系統負載低於N。省略N則刪除之前ed負載限制
-L –check-symlink-times 在符號連結和目標之間使用最新的mtime。
-n –just-print, –dry-run, –recon 僅顯示要執行的命令,但不執行命令。
-o FILE –old-file=file, –assume-old=file 強制將FILE認作非常老,不要重新 make 它。
-O[type] –output-sync[=type] 當與-j並行執行多個作業時,確保每個作業的輸出都被收集在一起,而不是散佈在其他作業的輸出中。如果type被省略或是target,則將每個目標的輸出組在一起。如果type是line,每個命令的輸出將被組在一起。如果type是recurse,則整個遞迴make的輸出被組在一起。如果type是none,則禁用輸出同步。
-p –print-data-base 列印讀取makefile所產生的資料庫(規則和變數值)
-q –question 不執行任何命令;退出狀態為0則目標已全部更新。
-r –no-builtin-rules 禁用內建隱含規則。 同時清除字尾規則的預設字尾列表。
-R –no-builtin-variables 禁用內建變數設定。
-s –silent, –quiet 執行時不列印執行的命令。
-S –no-keep-going, –stop 關閉 -k。除非在遞迴make中-k可能通過MAKEFLAGS從頂層make繼承,或者在環境中的MAKEFLAGS中設定-k,否則該選項是不需要的。
-t –touch 使用touch將目標的時間標記為最新,而不是重新建立它們。
–trace 列印每個目標的處置資訊(為什麼要重建目標以及執行哪些命令來重建目標)。
-v –version 列印 make 的版本號並退出。
-w –print-directory 在其它處理前後列印工作目錄。
–no-print-directory 即使 -w 隱式開啟,也要關閉 -w。
-W FILE –what-if=file, –new-file=file, –assume-new=file 將 FILE 認作無限新。
–warn-undefined-variables 當引用未定義的變數列印警告資訊。
                             

2 Makefile簡介

  Makefile儲存著工程管理器Make進行工作所需的編譯規則命令。

2.1 簡單示例

  例如,有 Makefile 檔案,內容如下:

main.exe:main.o func.o //有標頭檔案時要加入標頭檔案
    g++ -o main.exe main.o func.o 
main.o:main.cpp 
    g++ -c main.cpp 
func.o:func.cpp 
    g++ -c func.cpp

  對於該 Makefile 檔案,程式 make 處理過程如下:

  1. make程式首先讀到第1行的目標檔案main.exe和它的兩個依賴檔案main.ofunc.o;然後比較檔案main.exemain.ofunc.o的產生時間,如果 main.exemain.ofunc.o 舊的話,則執行第2行命令,以產生目標檔案main.exe
  2. 在執行第2行的命令前,它首先會檢視makefile中的其他定義,看有沒有以第1行main.ofunc.o為目標檔案的依賴檔案,如果有的話,繼續按照1、2的方式匹配下去。
  3. 根據2的匹配過程,make 程式發現第3行有目標檔案main.o 依賴於main.cpp,則比較目main.o與它的依賴檔案 main.cpp 的檔案新舊,如果main.omain.cpp舊,則執行第4行的命令以產生目標檔案main.o
  4. 在執行第 4 條命令時,main.cpp在檔案makefile不再有依賴檔案的定義,make程式不再繼續往下匹配,而是執行第4條命令,產生目標檔案main.o。目標檔案 func.o按照上面的同樣方式判斷產生。
  5. 執行3、4產生完main.ofunc.o以後,則第 2 行的命令可以順利地執行了,最終產生了第 1 行的目標檔案main.exe

2.2 Makefile組成

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

2.3 MakeFile長行分割

  • Makefile使用\來進行長行分割。

2.4 Makefile檔名

  預設的情況下,make命令會在當前目錄下按順序找尋檔名為“GNUmakefile”、“gnumakefile”,“makefile”、“Makefile”的檔案。
  當然,你可以使用別的檔名來書寫Makefile,比如:“xxxx”,但要用用make的“-f FILE”選項指定該檔案為Makefile檔案,如:make -f xxxx

2.5 引入其它的Makefile

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

include filename1 filename2... //filename可以是當前作業系統Shell的檔名(可以保含路徑和萬用字元)

  make命令開始時,會把找尋include所指出的其它Makefile,並把其內容安置在當前的位置。就好像C/C++的#include一樣。如果檔案都沒有指定絕對路徑或是相對路徑的話,make會在當前目錄下首先尋找,如果當前目錄下沒有找到,那麼,make還會在下面的幾個目錄下找:
  (1)如果make執行時,有-I--include-dir引數,那麼make就會在這個引數所指定的目錄下去尋找。
  (2)如果目錄<prefix>/include(一般是:/usr/local/bin/usr/include)存在的話,make也會去找。
  如果有檔案沒有找到的話,make會生成一條警告資訊,但不會馬上出現致命錯誤。它會繼續載入其它的檔案,一旦完成makefile的讀取,make會再重試這些沒有找到,或是不能讀取的檔案,如果還是不行,make才會出現一條致命資訊。如果你想讓make不理那些無法讀取的檔案,而繼續執行,你可以在include前加一個減號“-”,表示無論include過程中出現什麼錯誤,都不要報錯繼續執行,如 -include <filename>

2.6 環境變數MAKEFILES

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

2.7 覆蓋其它Makefile的部分

有兩種方式:

  • 對於inlclude引入的其它makefile,相同的規則(目標與依賴都相同),在後面展開的的會覆蓋在前面展開的()
  • GNUmakefile,gnumakefile,makefile,Makefile。優先順序由高到低,因此前面的會覆蓋後面的檔案。

2.8 make如何讀取Makefile

GNU的make工作時的執行步驟入下:

  • 第一階段:
    1. 讀入所有的Makefile。
    2. 讀入被include的其它Makefile。
    3. 初始化檔案中的變數。
    4. 推導隱晦規則,並分析所有規則。
    5. 為所有的目標檔案建立依賴關係鏈。
  • 第二階段
    1. 根據依賴關係,決定哪些目標要重新生成。
    2. 執行生成命令。

展開:將變數、函式的值放在對應的位置。

  • 立即展開:在第一段中發生的展開。
  • 延後展開:直到構建上下文出現或者第二階段才發生的展開。

  • 變數賦值

immediate = deferred
immediate ?= deferred
immediate := immediate
immediate ::= immediate
immediate += deferred or immediate //右值是簡單變數立即展開,否則延後展開
immediate != immediate

define immediate
  deferred
endef

define immediate =
  deferred
endef

define immediate ?=
  deferred
endef

define immediate :=
  immediate
endef

define immediate ::=
  immediate
endef

define immediate +=
  deferred or immediate
endef

define immediate !=
  immediate
endef
  • 條件指令:immediate
  • 規則定義:顯式規則,模式規則,字尾規則,靜態模式規則和簡單的先決條件定義
immediate : immediate ; deferred
    deferred

2.9 再次展開

  make可對依賴項在立即展開後再次展開,再次展開發生在第二階段前。一般是因為第一次展開後make將其解釋為字串而不是make語法。.SECONDEXPANSION關鍵字表示將後面的內容多次展開。

.SECONDEXPANSION:
...
#不能執行
objs1:=main.o
objs2:=add.o
main.exe:$(objs1) $$(objs2)
gcc -o $@ $^

#可以執行
.SECONDEXPANSION:
objs1:=main.o
objs2:=add.o
main.exe:$(objs1) $$(objs2)
gcc -o $@ $^

3 顯式規則

target: dependency  //目標項:依賴項
    command1           
    command2  //必須以Tab鍵開頭,command為更新目標的命令,支援shell指令碼

#或

target: dependency_files; command1   
    command2

規則規定了兩件事:

  • 如何判斷目標項是否過期:如果目標不存在或者依賴項新於目標項
  • 怎樣更新目標項。
  • 依賴關係中最好加入標頭檔案。原因在於標頭檔案改變,實現檔案未改變時,不會重新編譯目標。

3.1 目標項和依賴項

3.1.1 依賴項的型別

  • 普通依賴項
    當普通依賴項新於目標項時,預設情況下目標項要更新。
  • 順序依賴項
    普通依賴項|順序依賴項表示,其僅表示一個先執行的操作,當順序依賴項新於目標項時,目標項不需要更新。例如:將目標檔案放在單獨的目錄中,因此需要先建立目錄,但是任何對目錄項的操作都會引起目錄時間更新,如果目錄不是順序依賴項,則會導致目標由於目錄的更新而重新編譯。
OBJDIR := objdir                                                       OBJS := objdir/add.o objdir/main.o
$(OBJDIR)/main.exe:$(OBJS)
    g++ -o  $@ $^
$(OBJDIR)/%.o:%.cc | $(OBJDIR)
    g++ -c $^ -o $@
$(OBJDIR):
    mkdir $(OBJDIR)

3.1.2 使用萬用字元

符號 含義
* 匹配0個或多個字元
? 匹配1個字元
[abc] 匹配abc中的任意1個字元
[!abc]或[^abc] 匹配除abc之外的任意1個字元
{*.c,ab} 匹配以.c結尾的字串和字元ab
  • 萬用字元可用於規則中,但是如果一階段展開時,沒有檔案與之匹配時就解析為字串
  • 用通配對變數賦值時解析為字串
  • 萬用字元展開時,是把所有匹配的結果都展開。

3.1.3 設定依賴項的搜尋路徑

  • 使用make環境變數VPATH=dir1:dir2
  • 使用vpath關鍵字(注意,它是全小寫的),它可以指定不同的檔案在不同的搜尋目錄中。它的使用方法有三種:
    • vpath <pattern> <directories>
      為符合模式的檔案指定搜尋目錄。
    • vpath <pattern>
      清除符合模式的檔案的搜尋目錄。
    • vpath
      清除所有已被設定好了的檔案搜尋目錄。
    • 我們可以連續地使用vpath語句,以指定不同搜尋策略。如果連續的vpath語句中出現了語義重複的<pattern>,那麼,make會按照vpath語句的先後順序來執行搜尋。如:
  • 搜尋路徑也適用於隱式規則
  • 如果依賴項是庫-lxxx,則先搜尋-libxxx.so再搜尋-libxxx.a

3.1.4 偽目標

  • 偽目標就是指不是檔名的目標。
  • .PHONY關鍵字表示它後面列表中的依賴項均為偽目標。
  • 偽目標可以有依賴項
  • 偽目標和相同的普通目標區別在於:
    • 偽目標的執行需要顯式指定(除非只有偽目標),而後者則是預設執行
    • 當存在同名檔案時,後者會認為目標始終最新而不執行命令,偽目標則會執行命令。
//例main.c中使用了fun.c中的函式,Makefile如下:

main.exe:main.o func.o 
    gcc  main.o func.o -o main.exe
main.o:main.c       
    gcc -c main.c -o main.o       //可以寫作gcc -c main.c
func.o:func.c        
    gcc -c func.c    
.PHONY:rebuild clean              //表示後面的是偽目標
rebuild: clean main.exe           //先執行清理,在執行重新編譯main.exe
clean: 
    rm –rf main.o func.o main.exe //最後刪除.o.exe 的檔案


$make         //直接 make,即從預設檔案(Makefile)的第一行開始執行
$make clean   //表示執行目標 clean: 開始的命令段
$make func.o  //表示執行目標 func.o: 開始的命令段
$make rebuild //則先執行目標rebuild,先清除clean,再重新編譯連線main.exe

3.1.5 不帶依賴項或命令

  • 普通目標和偽目標都可以不帶依賴項
  • 普通目標和偽目標都可以不帶依賴項和命令
  • 普通目標絕不能只帶依賴項不帶命令(除非使用了.DEFAULT)

3.1.6 特殊目標

名字 描述
.PHONY 依賴項被視為偽目標
.SUFFIXES 其依賴項被用於定義隱式規則所需的字尾
.DEFAULT 為所有不是顯式或隱式規則的目標的依賴項設定預設命令
.PRECIOUS 如果在命令執行過程中make被終止,其依賴項不會被刪除。如果其依賴項是中介檔案也不會刪除
.INTERMEDIATE 他的依賴項被視為中介檔案,會自動刪除,如果沒有依賴項則無效
.SECONDARY 他的依賴項被視為中介檔案,但不是自動刪除,如果沒有依賴項則將所有的目標都是中介檔案
.SECONDEXPANSION 將其後的所有規則的依賴項和目標項再次展開
.DELETE_ON_ERROR 如果其依賴項的命令非0退出(出錯)則刪除
.IGNORE 如果其依賴項的命令非0退出(出錯)則忽略
.LOW_RESOLUTION_TIME 由依賴項的命令建立的檔案使用低精度時間戳
.SILENT 依賴項的命令執行時不顯示
.EXPORT_ALL_VARIABLES 將所有變數輸出給下一層make
.NOTPARALLEL 即使make指定-j選項,仍序列執行
.ONESHELL 呼叫一次shell就執行多行命令,而不是每行呼叫一次shell
.POSIX makefile將以符合POSIX的模式進行解析和執行

3.1.7 一個規則多個目標

target1 tarhet2 : dependency
    command

#等價於

target1 : dependency
    command
tarhet2 : dependency
    command

3.1.8 一個目標多個規則

  一個目標可有可以有多個規則,所有的依賴項都合併為依賴項列表,但是如果有多個命令則只有最後一個命令會執行。

target1 : dependency
    command

target1 : dependency
    command

3.1.9 靜態模式

targets...: target-pattern: prereq-patterns
   command
  • 設定匹配target-pattern的目標的依賴項為 prereq-patterns
    • targets定義了一系列的目標檔案,可以有萬用字元。是目標的一個集合。
    • target-parrtern是指明瞭targets的模式,也就是的目標集模式。
    • prereq-parrterns是目標的依賴模式,它對target-parrtern形成的模式再進行一次依賴目標的定義。
  • 與隱式規則的區別在於: 隱式規則適用於任何與目標模式匹配的目標,但要求有依賴項且沒有命令。

3.1.10 雙冒號規則

target::dependency
   command
  • 雙冒號規則是在目標名稱之後用’::’而不是’:’寫的顯式規則。
  • 當同一個目標出現在多個規則中時
    • 普通規則只執行最後出現的規則的命令
    • 雙冒號規則則會執行每個規則的命令,即使目標已經存在。

3.1.11 自動生成依賴項

  • gcc/g++會自動生成依賴關係
    • -M:生成依賴關係包括庫檔案
    • -MM:生成依賴關係不包括庫檔案

3.2 命令

target: dependency  //目標項:依賴項
    command1           
    command2  //必須以Tab鍵開頭,command為更新目標的命令,支援shell指令碼

#或

target: dependency_files; command1   
    command2
  • 基本語法
    • 必須以TAB鍵開頭
    • 連續多行一TAB鍵開頭的命令為同一規則的命令
    • 命令的基本語法和shell指令碼一樣
      • 註釋用#
      • 長行分割用\
    • 命令中可以使用makefile變數
  • 命令顯示
    • 命令以@開頭表示只執行但不顯示命令
    • make的-n選項表示只顯示而不執行命令
    • -s選項表示執行時禁止顯示所有命令
  • 命令執行
    • 當依賴項新於目標項時(時間上新於或者依賴項不存在),就執行命令
    • make呼叫子shell執行命令
    • 每行命令呼叫一個shell(除非使用了.ONESHELL特殊目標)
    • 如果你要讓上一條命令的結果應用在下一條命令,要將命令放在同一行,用分號分隔
    • 可以使用預定義變數SHELL來指定shell,用.SHELLFLAGS指定shell選項
    • 並行執行
      • 正常情況,每次執行一條命令
      • make的-j n選項可以設定一次執行的命令數,-l n 選項可以設定最大負載
      • 特殊目標.NOTPARALLEL禁止並行執行命令
      • 並行執行時,結果一產生就輸出,因此可能變得混亂,可以通過make 的-O[type]選項執行輸出方式
        • 如果type被省略或type是target,則一但一個規則的所有命令執行完畢,則輸出
        • 如果type是line,則一行的所有命令執行完畢,則輸出
        • 如果type是recurse,則一但遞迴呼叫的所有命令執行完畢,則輸出
        • 如果不指定-O選項或type是none,則禁用輸出同步,每個命令的結果一產生就輸出。
  • 命令出錯
    • 每當命令執行完後,make會檢測每個命令的返回碼,如果命令返回成功,那麼make會執行下一條命令,當規則中所有的命令成功返回後,這個規則就算是成功完成了。如果一個規則中的某個命令出錯了(命令退出碼非零),那麼make就會終止執行當前規則,這將有可能終止所有規則的執行。
    • 命令以-開頭表示忽略命令執行過程中的錯誤
    • make選項-i,表示忽略所有命令執行過程中的錯誤
    • make選項-k,表示如果某規則中的命令出錯了,那麼就終目該規則的執行,但繼續執行其它規則。
    • 特殊目標.IGNORE,表示忽略其依賴項所在規則的命令執行錯誤
  • 遞迴執行make
    • 在一些大的工程中,我們會把我們不同模組或是不同功能的原始檔放在不同的目錄中,我們可以在每個目錄中都書寫一個該目錄的Makefile,然後在工程目錄上設定一個總控makefile
    • make呼叫
      • 遞迴使用make意味著使用make作為makefile中的命令,但不是直接使用make而是使用預定義變數MAKE
      • “-t”,“-n”,和“-q”會失效。
    • 變數傳遞
      • 總控Makefile的變數可以傳遞到下級的Makefile中,但是不會覆蓋下層的Makefile中所定義的變數,除非指定了“-e”引數。
      • export <variable...>:傳遞變數到下級Makefile
      • unexport <variable...>:禁止變數傳遞到下級Makefile
      • export:傳遞所有的變數到下級Makefile
    • make選項傳遞
      • 通過預定義變數MAKEFLAGS傳遞
      • 選項“-C”,“-f”,“-h”“-o”和“-W”不會往下層傳遞
      • 選項“-w”輸出執行前後的目錄。
      • 選項“-C”來指定make下層Makefile,且“-w”會被自動開啟的。
      • 選項“-s”或“–no-print-directory”自動關閉“-w”。
  • 多行命令
define cmd_lable[=|+=|:=|?=]
    cmd1
    cmd2
    ...
endef

4 特殊字元

字元 描述
\ 換行符或者轉義符號
# 註釋符,一定要另起一行不然容易出錯
@ 如果命令以@開頭則不顯示當前執行的命令
% 匹配1個或多個字元,匹配結果可以引用
~ 使用者主目錄或者宿主目錄
- 命令以-打頭,在執行時如果發生錯誤時,不中斷make過程
+ 命令以+打頭,無論make命令後面是否跟著-n,-t,-q三個引數,命令都會被執行。
$$ $字元
$(var) 取變數var的值

5 變數

  在Makefile中的定義的變數,就像是C/C++語言中的巨集一樣,他代表了一個文字字串,在Makefile中執行的時候其會自動原模原樣地展開在所使用的地方。在Makefile中,變數可以使用在“目標”,“依賴目標”,“命令”或是Makefile的其它部分中。

  變數的命名字可以包含字元、數字,下劃線(可以是數字開頭),但不應該含有“:”、“#”、“=”或是空字元(空格、回車等)。推薦使用大小寫搭配的變數名,可以避免和系統變數衝突。

5.1 變數定義和引用

  • 定義變數和賦值
    • 變數名=值:遞規變數展開(幾個變數共享一個值),如a=b;b=c;c=10;,則執行時a就會與c相同
    • 變數名:=值變數名::=值:簡單變數展開。
    • 變數名?=值:如果變數之前定義過,則什麼也不做,否則,定義變數並賦值
  • 使用變數

    • $(變數名)${變數名} :取某個變數的值
  • 變數值替換,不會改變原來變數的值

    • $(var:.a=.b)${var:.a=.b},變數“var”中所有以“.a”結尾的字串中的“.a”替換成“.b”。
    • 使用模式規則:$(var:%.a=%.b),變數“var”中所有和%.a匹配的字串替換為和%.b匹配的字串。
  • 把變數的值再當成變數:var1 := $($(var2))
  • 變數追加:var+=valuevar:=$(var) value
    • 如果變數之前沒有定義過,那麼,“+=”會自動變成“=”,
    • 如果前面有變數定義,那麼“+=”會繼承於前次操作的賦值符。
    • 如果前一次的是“:=”,那麼“+=”會以“:=”作為其賦值符。
  • 覆蓋make命令列變數:在變數定義前加上關鍵字override
  • 多行變數
define var_lable[=|+=|:=|?=]
var1
var2
...
endef
  • 取消變數定義:undefine var
  • 禁止繼承變數:
    • target:private 變數賦值禁止依賴項繼承目標變數,
    • private 變數賦值禁止任何規則和目標繼承全域性變數

5.2 變數分類

  • 普通變數:使用variable-assignment定義的變數,一般為全域性變數
  • 特定目標變數:只在指定目標的規則中有效的變數。target:[override] variable-assign
  • 特定模式變數:就是將模式規則用於目標變數,pattern: [override] variable-assignment
  • 命令列變數:變數由make命令列傳入,使用$make 變數賦值定義
  • 自動變數:指在使用的時候,自動用特定的值替換。常用的有:
變數 說明
$@ 當前規則的目標檔案。在多目標的模式規則中,它代表的是觸發規則被執行的目標檔名
$* 不包含副檔名的目標檔名稱
$% 規則的目標檔案是一個庫檔案時,代表庫的第一個成員名,如果目標不是函式庫檔案,其值為空。
$< 當前規則的第一個依賴檔案,如果是隱含規則,則它代表通過目標指定的第一個依賴檔案
$^ 當前規則的所有依賴檔案,以空格分隔。如果目標是靜態庫檔名,它所代表的只能是所有庫成員(.o檔案)名。變數“$^”會去掉規則中重複的依賴檔案。
$? 當前規則中日期新於目標檔案的所有依賴檔案列表,空格分隔,如果目標是靜態庫檔名,代表的是庫成員(.o檔案)的更新情況。
$+ 類似“$^”,但是它保留了依賴檔案中重複出現的檔案。主要用在程式連結時,庫的交叉引用場合。
$(@D) 目標檔案的目錄部分。如果$@不存在斜槓,其值就是.(當前目錄)。等價於函式$(dir $@)
$(@F) 目標檔案的檔名。$(@F)等價於函式$(notdir $@)
$(*D) 不包含副檔名的目標檔名稱的目錄部分。如果$@不存在斜槓,其值就是.(當前目錄)。
$(*F) 不包含副檔名的目標檔名稱的檔名部分。
$(%D) 當以如archive(member)形式靜態庫為目標時,分別表示庫檔案成員member名中的目錄部分
$(%F) 當以如archive(member)形式靜態庫為目標時,分別表示庫檔案成員member名中的檔名部分。
$(< D) 規則中第一個依賴檔案的目錄部分
$(< F) 規則中第一個依賴檔案的檔名部分
$(^D) 所有依賴檔案的目錄部分(不存在同一檔案)
$(^F) 所有依賴檔案的檔名部分(不存在同一檔案)。
$(+D) 所有依賴檔案的目錄部分(可存在重複檔案)。
$(+F) 所有依賴檔案的檔名部分(可存在重複檔案)。
$(?D) 當前規則中日期新於目標檔案的所有依賴檔案的目錄部分。
$(?F) 當前規則中日期新於目標檔案的所有依賴檔案的檔名部分。
  • 預定義(預設)變數:內部事先定義好的變數,但是它的值是固定的,並且有些的值是為空的。
變數 說明 變數 說明
AR 函式庫打包程式,預設“ar”。 ARFLAGS 庫打包程式引數,預設是“rv”。
AS 編譯程式,預設是“as” ASFLAGS 彙編程式引數
CC c編譯器,預設cc CFLAGS c編譯器引數
CXX c++編譯器,預設為 g++ CXXFLAGS c++編譯器引數
CPP c預編譯器,預設為$(CC) –E CPPFLAGS c預編譯器引數
CO 從RCS檔案中擴充套件檔案程式。預設為co。 COFLAGS RCS命令引數
FC Fortran 和 Ratfor的編譯器和前處理器。預設是f77 FFLAGS Fortran編譯器引數
GET 從SCCS檔案中擴充套件檔案的程式。預設是get GFLAGS SCCS “get”程式引數
LEX C或Ratfor的Lex方法分析器程式。預設是lex LFLAGS Lex文法分析器引數
PC Pascal語言編譯程式。預設命令是pc PFLAGS Pascal編譯器引數
YACC C程式的Yacc文法分析器。預設是yacc YFLAGS Yacc文法分析器引數
YACCR Ratfor程式的Yacc文法分析器。預設是yacc –r
MAKEINFO 轉換Texinfo原始檔到Info檔案程式。預設是makeinfo
TEX 從TeX原始檔建立TeX DVI檔案的程式。預設是tex
TEXI2DVI 從Texinfo原始檔建立TeX DVI檔案的程式。預設是texi2dvi
WEAVE 轉換Web到TeX的程式。預設是weave
CWEAVE 轉換C Web 到 TeX的程式。預設是“cweave
TANGLE 轉換Web到Pascal語言的程式。預設是tangle
CTANGLE 轉換C Web 到 C。預設是ctangle
RM 刪除檔案命令。預設是rm –f
LDLIBS 庫檔案 LDFLAGS 連結程式引數。
RFLAGS Ratfor的Fortran編譯器引數
LINTFLAGS lint引數
  • 環境變數:設定make執行環境的變數,包括系統環境變數。
變數 說明
VPATH 依賴項查詢路徑
MAKEFILES 引入的makefile所在路徑
MAKE make程式
SHELL shell程式
  • 特殊變數
變數 說明
MAKEFILE_LIST make解析的makefile檔名列表
MAKE_RESTARTS make重新開始的次數
MAKE_TERMOUT 標準輸出裝置
MAKE_TERMERR 標準出錯輸出裝置
.DEFAULT_GOAL make的預設目標,預設為第一個目標
.VARIABLES 該makefile中的全域性變數,不包括特定目標變數和特定模式變數
.FEATURES 該版本make支援的功能
.RECIPEPREFIX 指令行字首,表示以此開頭的行為指令,預設為TAB
.INCLUDE_DIRS 設定make搜尋引入的其它makefile檔案的路徑

6 條件語句

conditional-directive
text-if-one-is-true
endif

//或

conditional-directive
text-if-true
else
text-if-false
endif

//或

conditional-directive-one
text-if-one-is-true
else conditional-directive-two
text-if-two-is-true
else
text-if-one-and-two-are-false
endif
  • 條件指令
    • ifeq|ifneq (arg1, arg2)
    • ifeq|ifneq 'arg1' 'arg2'
    • ifeq|ifneq "arg1" "arg2"
    • ifeq|ifneq "arg1" 'arg2'
    • ifeq|ifneq 'arg1' "arg2"
    • ifdef|ifndef variable-name

7 函式

7.1 函式呼叫語法

一、函式的呼叫語法

$(function arg1,arg2...)

#或

${function arg1,arg2...}

7.2 字串處函式

  • $(subst <from>,<to>,<text> )

    • 名稱:字串替換函式——subst。
    • 功能:把字串<text>中的<from>字串替換成<to>
    • 返回:函式返回被替換過後的字串。
    • 示例:$(subst ee,EE,feet on the street),把“feet on the street”中的“ee”替換成“EE”,返回結果是“fEEt on the strEEt”。
  • $(patsubst <pattern>,<replacement>,<text> )

    • 名稱:模式字串替換函式——patsubst。
    • 功能:查詢<text>中的單詞(單詞以“空格”、“Tab”或“回車”“換行”分隔)是否符合模式<pattern>,如果匹配的話,則以<replacement>替換。這裡,<pattern>可以包括萬用字元“%”,表示任意長度的字串。如果<replacement>中也包含“%”,那麼,<replacement>中的這個“%”將是<pattern>中的那個“%”所代表的字串。(可以用“\”來轉義,以“\%”來表示真實含義的“%”字元)
    • 返回:函式返回被替換過後的字串。
    • 示例:$(patsubst %.c,%.o,x.c.c bar.c),把字串“x.c.c bar.c”符合模式[%.c]的單詞替換成[%.o],返回結果是“x.c.o bar.o”
  • $(strip <string>)
    • 名稱:去空格函式——strip。
    • 功能:去掉<string>字串中開頭和結尾的空字元。
    • 返回:返回被去掉空格的字串值。
    • 示例:$(strip a b c ),把字串“a b c ”去到開頭和結尾的空格,結果是“a b c”。
  • $(findstring <find>,<in> )
    • 名稱:查詢字串函式——findstring。
    • 功能:在字串<in>中查詢<find>字串。
    • 返回:如果找到,那麼返回<find>,否則返回空字串。
    • 示例:$(findstring a,a b c)返回“a”字串。
  • $(filter <pattern...>,<text> )
    • 名稱:過濾函式——filter。
    • 功能:以<pattern>模式過濾<text>字串中的單詞,保留符合模式<pattern>的單詞。可以有多個模式。
    • 返回:返回符合模式<pattern>的字串。
    • 示例:$(filter %.c %.s,foo.c bar.c baz.s ugh.h) 返回“foo.c bar.c baz.s”。
  • $(filter-out <pattern...>,<text> )
    名稱:反過濾函式——filter-out。
    功能:以<pattern>模式過濾<text>字串中的單詞,去除符合模式<pattern>的單詞。可以有多個模式。
    • 返回:返回不符合模式<pattern>的字串。
    • 示例:$(filter-out %.c %.s,foo.c bar.c baz.s ugh.h) 返回“ugh.h”。
  • $(sort <list>)
    • 名稱:排序函式——sort。
    • 功能:給字串<list>中的單詞排序(升序)。
    • 返回:返回排序後的字串。
    • 示例:$(sort foo bar lose)返回“bar foo lose” 。
    • 備註:sort函式會去掉<list>中相同的單詞。
  • $(word <n>,<text> )
    • 名稱:取單詞函式——word。
    • 功能:取字串<text>中第<n>個單詞。(從一開始)
    • 返回:返回字串<text>中第<n>個單詞。如果<n><text>中的單詞數要大,那麼返回空字串。
    • 示例:$(word 2, foo bar baz)返回值是“bar”。
  • $(wordlist <s>,<e>,<text> )
    • 名稱:取單詞串函式——wordlist。
    • 功能:從字串<text>中取從<s>開始到<e>的單詞串。<s><e>是一個數字。
    • 返回:返回字串<text>中從<s><e>的單詞字串。如果<s><text>中的單詞數要大,那麼返回空字串。如果<e>大於<text>的單詞數,那麼返回從<s>開始,到<text>結束的單詞串。
    • 示例:$(wordlist 2, 3, foo bar baz)返回值是“bar baz”。
  • $(words <text> )
    • 名稱:單詞個數統計函式——words。
    • 功能:統計<text>中字串中的單詞個數。
    • 返回:返回<text>中的單詞數。
    • 示例:$(words, foo bar baz)返回值是“3”。
    • 備註:如果我們要取<text>中最後的一個單詞,我們可以這樣:$(word
      $(words<text>),<text> )
  • $(firstword <text>)
    • 名稱:首單詞函式——firstword。
    • 功能:取字串<text>中的第一個單詞。
    • 返回:返回字串<text>的第一個單詞。
    • 示例:$(firstword foo bar)返回值是“foo”。
    • 備註:這個函式可以用word函式來實現:$(word 1,<text> )
  • $(lastword <text>)
    • 名稱:末單詞函式——lastword。
    • 功能:取字串<text>中的最後一個單詞。
    • 返回:返回字串<text>的最後一個單詞。
    • 示例:$(lastword foo bar)返回值是“bar”。
    • 備註:這個函式可以用word函式來實現:$(word $(words <text>),<text> )

7.3 檔名操作函式

  • $(dir <names...> )
    • 名稱:取目錄函式——dir。
    • 功能:從檔名序列<names>中取出目錄部分。目錄部分是指最後一個反斜槓(“/”)之前的部分。如果沒有反斜槓,那麼返回“./”。
    • 返回:返回檔名序列<names>的目錄部分。
    • 示例: $(dir src/foo.c hacks)返回值是“src/ ./”。
  • $(notdir <names...> )
    • 名稱:取檔案函式——notdir。
    • 功能:從檔名序列<names>中取出非目錄部分。非目錄部分是指最後一個反斜槓(“/”)之後的部分。
    • 返回:返回檔名序列<names>的非目錄部分。
    • 示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。
  • $(suffix <names...> )
    • 名稱:取字尾函式——suffix。
    • 功能:從檔名序列<names>中取出各個檔名的字尾。
    • 返回:返回檔名序列<names>的字尾序列,如果檔案沒有字尾,則返回空字串。
    • 示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。
  • $(basename <names...> )
    • 名稱:取字首函式——basename。
    • 功能:從檔名序列<names>中取出各個檔名的字首部分,包括目錄。
    • 返回:返回檔名序列<names>的字首序列,如果檔案沒有字首,則返回空字串。
    • 示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar”。
  • $(addsuffix <suffix>,<names...> )
    • 名稱:加字尾函式——addsuffix。
    • 功能:把字尾<suffix>加到<names>中的每個單詞後面。
    • 返回:返回加過字尾的檔名序列。
    • 示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。
  • $(addprefix <prefix>,<names...> )

    • 名稱:加字首函式——addprefix。
    • 功能:把字首<prefix>加到<names>中的每個單詞後面。
    • 返回:返回加過字首的檔名序列。
    • 示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。
  • $(wildcard <patter>)

    • 名稱:加字首函式——addprefix。
    • 功能:把字首<prefix>加到<names>中的每個單詞後面。
    • 返回:返回加過字首的檔名序列。
    • 示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。
  • $(join <list1>,<list2> )

    • 名稱:連線函式——join。
    • 功能:把<list2>中的單詞對應地加到<list1>的單詞後面。如果<list1>的單詞個數要比<list2>的多,那麼,<list1>中的多出來的單詞將保持原樣。如果<list2>的單詞個數要比多,那麼,<list2>多出來的單詞將被複制到<list2>中。
    • 返回:返回連線過後的字串。
    • 示例:$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。
  • $(wildcard <pattern>)

    • 名稱:當前目錄檔案搜尋函式——addprefix。
    • 功能:使用萬用字元搜尋當前目錄下匹配<pattern>的檔案,檔名以空格間隔。
    • 返回:返回匹配<pattern>的檔名。
    • 示例:假設當前目錄下有main.c add.c,$(wildcard m*.c)返回值是“main.c”。
  • $(realpath <name>)

    • 名稱:真實路徑函式——realpath。
    • 功能:計算<name>的真實路徑(絕對路徑且跟隨符號連結),以空格間隔。<name>支援萬用字元。
    • 返回:返回<name>的真實路徑,以空格間隔。
    • 示例:假設使用者主目錄下有main.c連結到該目錄下的test.c , $(realpath main.c)返回值是“/home/usrname/test.c”。
  • $(abspath <name>)
    • 名稱:絕對路徑函式——abspath。
    • 功能:計算<name>的絕對路徑(不跟隨符號連結),以空格間隔。<name>支援萬用字元。
    • 返回:返回<name>的絕對路徑,以空格間隔。
    • 示例:假設使用者主目錄下有main.c連結到該目錄下的test.c , $(realpath main.c)返回值是“/home/usrname/main.c”。

7.4 條件函式

  • $(if <condition>,<then-part>[,<else-part>])

    • 名稱:如果函式——if。
    • 功能:if函式可以包含“else-part”部分,或是不含。<condition>引數是if的表示式,如果其返回的為非空字串,那麼這個表示式就相當於返回真,於是,<then-part>會被計算,否則<else-part> 會被計算。
    • 返回:如果<condition>為真(非空字串),那個<then- part>會是整個函式的返回值,如果<condition>為假(空字串),那麼<else-part>會是整個函式的返回值,此時如果<else-part>沒有被定義,那麼,整個函式返回空字串。
    • 示例:$(if a,b,c)返回”b”。
  • $(or condition1[,condition2[,condition3…]])

    • 名稱:或函式——or。
    • 功能:對每個進行邏輯或操作,可短路。
    • 返回:如果某個<condition>為非空則返回該<condition>,否則返回空字串
    • 示例:$(or a,b,c)返回”a”。
  • $(and condition1[,condition2[,condition3…]])
    • 名稱:且函式——and。
    • 功能:對每個進行邏輯且操作,可短路。
    • 返回:如果某個<condition>為空則返回空字串,否則返回最後一個<condition>
    • 示例:$(and a,b,c)返回”c”。

7.5 foreach函式

  • $(foreach <var>,<list>,<text> )
    • 名稱:遍歷函式——foreach。
    • 功能:把引數<list>中的單詞逐一取出放到引數<var>所指定的變數中,然後再執行<text>所包含的表示式。每一次<text>會返回一個字串,迴圈過程中,<text>的所返回的每個字串會以空格分隔,最後當整個迴圈結束時,<text>所返回的每個字串所組成的整個字串(以空格分隔)將會是foreach函式的返回值。所以,<var>最好是一個變數名,<list>可以是一個表示式,而<text>中一般會使用<var>這個引數來依次列舉<list>中的單詞。
    • 返回:<text>每次返回的值得組合,以空格隔開。
    • 示例:$(foreach n,a b c d,$(n).o)返回“a.o b.o c.o d.o”。
    • 備註:foreach中的引數是一個臨時的區域性變數,foreach函式執行完後,引數的變數將不在作用,其作用域只在foreach函式當中。

7.6 file函式

  • $(file op filename[,text])
    • 名稱:檔案讀寫函式——file。
    • 功能:進行檔案讀寫。
      • op為<,讀檔案filename,text應省略;
      • op為>,將text寫入到filename,且filename中的內容將清除
      • op為>>,將text追加到filename。
    • 返回:讀取時,返回檔案內容,如果filename不存在,返回空字串;寫入時返回空字串。
    • 示例:$(file >>,main.c,int main(void))返回空字串。

7.7 call函式

  • $(call <expression>,<parm1>,<parm2>,<parm3>...)
    • 名稱:引數化函式——call。
    • 功能:用來建立新的引數化的函式。當 make執行這個函式時,<expression>引數中的變數,$(1),$(2),$(3)等,會被引數<parm1><parm2><parm3>依次取代。
    • 返回:<expression><parm2>取代後的值。
    • 示例:ret=$(1) $(2);$(call $(1) $(2),love,you)返回”love you”。

7.8 取值函式

  • $(value varname)
    • 名稱:取值函式——value。
    • 功能:獲取varname的值。$(value varname)等價於$(varname)
    • 返回:varname的值。
    • 示例:$(value CXX)返回”g++”。

7.9 eval函式

  • $(eval string)
    • 名稱:——eval。
    • 功能:將string作為Makefile語法進行解析並執行。“eval”函式執行時會對它的引數進行兩次展開。第一次展開過程發是由函式本身完成的,第二次是函式展開後的結果被作為Makefile內容時由make解析時展開的。明確這一過程對於使用“eval”函式非常重要。理解了函式“eval”二次展開的過程後。實際使用時,如果在函式的展開結果中存在引用(格式為:(x)使
      (x)),那麼在函式的引數中應該使用“
      ”來代替“
      ”。
    • 返回:空,也可以說沒有返回值。
    • 示例:$(eval main:$$(OBJ);gcc -o main $$(OBJ))

7.10 origin函式

  • $(origin <variable> )
    • 名稱:原始函式——origin。
    • 功能:獲取變數的來源。
    • 返回:
      • “undefined”:未定義變數。
      • “default”:預定義變數。
      • “environment”:環境變數
      • “environment override”,被環境變數覆蓋的同名變數
      • “file”,定義在Makefile中的變數。
      • “command line”,變數是被命令列定義的。
      • “override”,變數被override定義的。
      • “automatic”,自動化變數。
    • 示例:$(origin CXX)返回。
    • 備註:<variable>是變數的名字,不應該是引用。

7.11 falvor函式

  • $(flavor <variable> )
    • 名稱:型別函式——flavor。
    • 功能:獲取變數的型別。
    • 返回:
      • “undefined”:未定義變數。
      • “recursive”:遞迴展開的變數。
      • “simple”:簡單變數。
      • 示例:$(flavor CXX)返回“recursive”。
    • 備註:<variable>是變數的名字,不應該是引用。

7.12 make控制函式

make提供了一些函式來控制make的執行。通常,你需要檢測一些執行Makefile時的執行時資訊,並且根據這些資訊來決定,你是讓make繼續執行,還是停止。

  • $(error <text ...> )

    • 名稱:錯誤函式——error。
    • 功能:獲取變數的型別。
    • 返回:
    • 備註:<variable>是變數的名字,不應該是引用。產生一個致命的錯誤,

7.13 shell函式

  • $(shell shell_cmd)
    • 名稱:shell函式——shell。
    • 功能: 生成一個shell來執行shell_cmd,等效於命令替換。
    • 返回:shell_cmd的輸出
    • 示例:$(shell date)返回“018年 05月 09日 星期三 23:01:42 CST”

7.14 guile函式

  • $(guile guile_expr)
    • 名稱:guile函式——guile。
    • 功能: 執行GNU guile表示式。
    • 返回:執行結果
    • 備註:需要make支援才行

8 隱式規則

  • 隱式規則即不需要顯示指出的規則。
  • 隱含規則鏈

    • 有些時候,一個目標可能被一系列的隱含規則所作用。例如,一個[.o]的檔案生成,可能會是先被Yacc的[.y]檔案先成[.c],然後再被C的編譯器生成。我們把這一系列的隱含規則叫做“隱含規則鏈”。
    • 在預設情況下,對於中間目標,它和一般的目標有兩個地方所不同:第一個不同是除非中間的目標不存在,才會引發中間規則。第二個不同的是,只要目標成功產生,那麼,產生最終目標過程中,所產生的中間目標檔案會被以“rm -f”刪除。
    • 通常,一個被makefile指定成目標或是依賴目標的檔案不能被當作中介。然而,你可以明顯地說明一個檔案或是目標是中介目標,你可以使用特殊目標“.INTERMEDIATE”來強制宣告。(如:.INTERMEDIATE : mid )

    • 你也可以阻止make自動刪除中間目標,要做到這一點,你可以使用特殊目標“.SECONDARY”來強制宣告(如:.SECONDARY:sec)。你還可以把你的目標,以模式的方式來指定(如:%.o)成特殊目標“.PRECIOUS”的依賴目標,以儲存被隱含規則所生成的中間檔案。

    • 在“隱含規則鏈”中,禁止同一個目標出現兩次或兩次以上,這樣一來,就可防止在make自動推導時出現無限遞迴的情況。

    • Make會優化一些特殊的隱含規則,而不生成中間檔案。如,從檔案“foo.c”生成目標程式“foo”,按道理,make會編譯生成中間檔案“foo.o”,然後連結成“foo”,但在實際情況下,這一動作可以被一條“cc”的命令完成(cc –o foo foo.c),於是優化過的規則就不會生成中間檔案。

  • 編譯C程式的隱含規則:

    • name.o自動依賴name.c
    • 生成命令:$(CC) –c $(CPPFLAGS) $(CFLAGS)
  • 編譯C++程式的隱含規則:

    • name.o目標自動依賴name.cc、name.cpp、name.C
    • 生成命令:$(CXX) –c $(CPPFLAGS) $(CXXFLAGS)
    • 建議使用.cc作為C++原始檔的字尾,而不是.C
  • 編譯Pascal程式的隱含規則:

    • name.o目標自動依賴name.p
    • 生成命令:$(PC) $(PFLAGS) -c
  • 編譯Fortran/Ratfor程式的隱含規則:
    • name.o目標自動依賴name.r、name.F、name.f
    • 生成命令
      • “.f”: $(FC) –c $(FFLAGS)
      • “.F”: $(FC) –c $(FFLAGS) $(CPPFLAGS)
      • “.f”: $(FC) –c $(FFLAGS) $(RFLAGS)
  • 預處理Fortran/Ratfor程式的隱含規則:

    • name.f目標自動依賴name.r 、name.F
    • 生成命令
      • ‘.F’:$(FC) $(CPPFLAGS) $(FFLAGS) -F
      • ‘.r’:$(FC) $(FFLAGS) $(RFLAGS) -F
  • 編譯Modula-2程式的隱含規則:

    • name.sym目標自動依賴name.def,
    • 生成命令:$(M2C) $(M2FLAGS) $(DEFFLAGS)
    • name.o目標自動依賴name.mod
    • 生成命令是$(M2C) $(M2FLAGS) $(MODFLAGS)
  • 彙編和彙編預處理的隱含規則:

    • name.o目標自動依賴name.s,預設使用編譯器“as”
    • 生成命令:$(AS) $(ASFLAGS)
    • name.s目標自動依賴name.S,預設使用C預編譯器“cpp”
    • 生成命令:$(CPP) $(CPPFLAGS)
  • 連結Object檔案的隱含規則:

    • name目標自動依賴name.o,通過執行C的編譯器來執行連結程式(一般是“ld”)來生成目標
    • 生成命令:$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)
  • Yacc C程式時的隱含規則:
    • name.c目標自動依賴name.y(yacc生成的檔案)
    • 生成命令:$(YACC) $(YFALGS)
  • Lex C程式時的隱含規則:

    • name.c目標自動依賴name.l(Lex生成的檔案)
    • 生成命令:$(LEX) $(LFALGS)
  • Lex Ratfor程式時的隱含規則:

    • name.r目標自動依賴name.l(Lex生成的檔案)
    • 生成命令:$(LEX) $(LFALGS)
  • 從C程式、Yacc檔案或Lex檔案建立Lint庫的隱含規則:
    • name.ln(lint生成的檔案)目標自動依賴name.c
    • 生成命令是:$(LINT) $(LINTFALGS) $(CPPFLAGS) -i
    • 對於“.y”和“.l”也是同樣的規則。
  • TeX and Web
    • name.dvi目標自動依賴name.tex
    • 生成命令:$(TEX)
    • name.tex目標自動依賴name.web、name.ch
    • 生成命令:
      • ’.web’: $(WEAVE)
      • ‘.ch’: $(CWEAVE)
    • name.p目標自動依賴name.web
    • 生成命令:$(TANGL)
    • name.c目標自動依賴name.w、name.ch
    • 生成命令:$(CTANGL)
  • Texinfo and Info
    • name.dvi目標自動依賴name.texinfo、name.texi、name.txinfo
    • 生成命令:$(TEXI2DVI) $(TEXI2DVI_FLAGS)
    • name.info目標自動依賴name.texinfo, name.texi, name.txinfo
    • 生成命令:$(MAKEINFO) $(MAKEINFO_FLAGS)

9 模式規則

模式規則:在規則的目標定義中包含”%”的規則。

  • 目標中的”%”定義表示對檔名的匹配,”%”表示長度任意的非空字串。
  • 如果”%”定義在目標中,那麼,目標中的”%”的值決定了依賴項中的”%”的值
  • “%”的展開發生在變數和函式的展開之後,變數和函式的展開發生在make載入Makefile時,而模式規則中的”%”則發生在執行時。

10 字尾規則

  • 字尾規則是一個比較老式的定義隱含規則的方法。

    • 雙字尾規則定義了一對字尾:依賴項字尾和目標字尾。如”.c.o”相當於”%o : %c”。
    • 單字尾規則只定義一個字尾,也就是原始檔的字尾。如”.c”相當於”% : %.c”。
  • 字尾規則中所定義的字尾應該是make所認識的,如果不認識可以使用特殊目標”.SUFFIXES”來定義或是刪除

  • 字尾規則不允許任何的依賴檔案,如果有依賴檔案的話,那就不是字尾規則,那些字尾統統被認為是檔名。

11 過載隱式規則

  • 使用模式規則來過載隱式規則。如:
%.o : %.c
            $(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)
  • 使用字尾規則來過載隱式規則。如:
.SUFFIXES:.c .o     #自定義字尾
.c.o:
    $(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)
  • 取消內建的隱含規則,只要不在後面寫命令就行。如:
%.o : %.s

   # 或

.c.o:

12 更新庫檔案

libname1.a(name2.o name3.o):name.o
    command
  • $@為libname1.a
  • $%為name2.o
  • 使用ar打包靜態庫檔案時要用”-s”選項或者打包完成後使用ranlib archivefile更新檔案符號表。

13 makefile約定

  • 每個makefile應該包括SHELL=/bin/sh
  • makefile的命令應該執行在sh中
  • 為了構建和安裝程式的configure指令碼和Makefile規則 不應直接使用除以下程式:
awk cat cmp cp diff echo egrep expr false grep install-info ln ls
mkdir mv printf pwd rm rmdir sed sleep sort tar test touch tr true`
  • 壓縮程式(如gzip)可用於dist規則。
  • 儘可能使用通用的選項
  • 不要在makefile中建立符號連結
  • 編譯程式及選項不應該直接使用,而應該通過預定義變數
  • 分階段安裝
    • 在實際安裝路徑前使用變數 DESTDIR,這樣可以安裝到其它目錄
    • 不要在makefile檔案中設定DESTDIR的值,而應由使用者在make命令列用絕對路徑指定
    • DESTDIR應該只在install和uninstall目標中有效
    • 安裝目錄變數
    • 安裝根目錄
      • prefix:資料檔案根目錄,預設為/usr/local/
      • exec_prefix:可執行檔案根目錄,預設為/usr/local/
    • 可執行檔案目錄
      • bindir:安裝普通使用者可執行程式,預設為$(exec_prefix)/bin
      • sbindir:安裝非正常狀態時可執行程式,預設為$(exec_prefix)/sbin
      • libexecdir:安裝由程式而不是使用者呼叫的可執行檔案,預設為$(exec_prefix)/libexec
    • 資料檔案目錄
      • datarootdir:安裝與架構無關的只讀資料,預設為$(prefix)/share
      • datadir:安裝與架構無關的只讀資料,預設為$(datarootdir)
      • sysconfdir:安裝只讀配置檔案,該目錄的檔案應為文字檔案,預設為$(prefix)/etc
      • sharedstatedir:安裝與架構無關且執行時可修改資料,預設為$(prefix)/com
      • localstatedir:安裝與特定機器有關的執行時可修改資料,$(prefix)/var
      • runstatedir:安裝與特定機器有關且不需要持久儲存的執行時可修改資料,預設為$(localstatedir)/run
      • includedir:安裝GCC的C標頭檔案,預設為$(prefix)/include
      • oldincludedir:安裝非GCC的標頭檔案,預設為/usr/include
      • docdir:安裝文件,預設為$(datarootdir)/doc/yourpkg
      • infodir:安裝Info檔案,預設為$(datarootdir)/info
      • htmldir、dvidir、pdfdir、psdir:安裝特殊格式的文件,預設為$(docdir)
      • libdir:安裝庫檔案,預設為$(exec_prefix)/lib
      • lispdir:安裝Emacs Lisp 檔案,預設為$(datarootdir)/emacs/site-lisp
    • man文件目錄
      • mandir:man手冊頂層目錄, 預設為$(datarootdir)/man
      • man1dir: 預設為$(mandir)/man1
      • man2dir: 預設為$(mandir)/man2
      • manext:man檔名擴充套件,預設為.1
      • man1ext:man 1檔名擴充套件
      • man2ext:man 2檔名擴充套件
    • 原始檔目錄:srcdir
  • 標準目標

    • all:編譯所有目標
    • install:安裝程式
    • install-html、install-dvi、install-pdf、install-ps:以指定格式安裝文件
    • uninstall:解除安裝程式
    • install-strip:安裝程式,但是跳過可執行檔案
    • clean:清除構建程式是建立的檔案
    • distclean:清除配置或構建程式建立的檔案
    • mostlyclean:類似clean,但是保留一些不想刪除的檔案
    • maintainer-clean:刪除所有可以用該Makefile構建的檔案
    • TAGS:更新標籤列表
    • info:生成需要的info檔案
    • dvi、html、pdf、ps:生成指定格式的文件
    • dist:建立程式的發行版本
    • check:執行測試
    • installcheck:執行安裝測試
    • installdirs:新增名為installdirs的目標來建立安裝目錄
  • 安裝命令分類

    • 普通命令:移動檔案到合適位置,並設定mode。
    • 安裝前命令
    • 安裝後命令

14 示例

14.1 普通makefile檔案

test:main.o add.o sub.o mul.o div.o
    gcc $^  -o $@    
%.o:%.c
    gcc $^  -o $@  
.PHONY:clean
clean:
        rm -f *.o test

14.2 另一種makefile檔案

  • 目錄分類:

    1. 功能目錄,有幾個建立幾個,之下建立src,src之中放.c檔案,在每個功能目錄下都建一個makefile
    2. include目錄放標頭檔案
    3. scripts目錄放指令碼檔案 在它之下也要放makefile
    4. bin 目錄放可執行檔案
    5. lib 目錄放庫檔案
    6. doc 目錄放文件資訊 readme 使用說明
    7. 頂層目錄下放一個總控makefile檔案
  • 總控makefile檔案

include scripts/Makefile

modules_make = $(MAKE) -C $(1);
modules_clean = $(MAKE) clean -C $(1);

.PHONY: all mm mc clean

all: $(Target)
mm:
    @$(foreach n,$(Modules),$(call modules_make,$(n)))
mc:
    @$(foreach n,$(Modules),$(call modules_clean,$(n)))

$(Target) : mm
    $(CC) $(CFLAGS) -o $(Target) $(AllObjs) $(Libs)
    @echo $(Target) make done!

clean : mc
    rm -rf $(Target)
    @echo clean done!                                            '
  • scripts下的makefile檔案
CC := gcc
CFLAGS := -Wall -O3
Libs = -lpthread
Target := client
Source := $(wildcard src/*.c)
Objs := $(patsubst %.c,%.o,$(Source))
Modules += check_putin pack_message main
AllObjs := $(addsuffix /src/*.o,$(Modules))
  • 使用時,主控makefile複製到自己的工程目錄下,根據自己的工程目 錄修改scripts下的makefile檔案中的Modules += check_putin pack_message main這一行中的變數名。並複製到相應位置,子目錄下的makefile也通過複製完成。

  版權宣告本文章參考了陳皓先生的《跟我一起寫makefile》,並根據最新的《GNU make手冊》(截止2018年5月),以及《Linux man pages》做了修改,增添了一部分內容。未經作者允許,嚴禁用於商業出版,否則追究法律責任。網路轉載請註明出處,這是對原創者的起碼的尊重!!!


相關文章