Makefile速查筆記

做 Linux C++,一個穩定的工程,Makefile 是很少改動的。但是如果需要修改的時候,Makefile 的語法和用法一時半會就回憶不出來(原諒我記憶力差……)。在此把自己以前的 Makefile 學習筆記記錄一下吧,也作爲分享。本文假設讀者已經懂得了 Makefile,因此主要是作爲備忘和速查用。

全文中尖括號部分表示變量。本文地址:https://segmentfault.com/a/1190000012091117

另外,速查系列還有這一篇:正則表達式速查筆記

Make 介紹

Makefile 的基本規則就是:

target ...: prerequirements ...
    command ...
    ...

其中 target 是目標文件,可以有多個,可以是 .o 文件或者是可執行問價,甚至可以是一個標籤。
Prerequisites 是先決條件,可以是文件,也可以是另一個 target。
這就組成了一個依賴關係target 的先決條件定義在 prerequisites 中,而其生成規則又是由 command 決定的。如果包含多個規則的話,那麼第一條規則就是整個 Makefile 的默認規則


Make 的工作流程

  1. 在當前目錄中查找 Makefile 或者 makefile 文件
  2. 將文件中第一條規則作爲默認規則
  3. 如果目標不存在,則尋找對應的 .o 文件
  4. 如果 .o 文件不存在,則尋找 .o 的依賴關係以生成它

Makefile 有很多默認的生成規則,但是本文我們不關心,因爲絕大部分情況下,我們是需要自行寫規則的。這便於自定義、便於移植、便於交叉編譯、便於調試。


Makefile 中的變量

變量的定義和調用格式:

name = value    # 注意變量的值是允許空格的
$(name)

變量值的部分可以使用換行符 "\" 來做假換行,將兩行內容連接成一行,從而縮短 Makefile 文件的寬度。

Makefile 綜述

Makefile 裏面都有啥?

文件指示:在一個 Makefile 裏面可以制定另一個 makefile,類似於 C 的 include

Makefile 還可以做條件包含動作,類似於 #if。Makefile 可以定一個變量爲一個多行的命令。

Makefile 裏面只有行註釋而沒有段註釋。註釋採用 # 開頭。如果要使用 # 字符,則需要轉義,寫成 “\#”。

Makefile 規則內容裏所有的 shell 命令都要以製表符 Tab 開頭,注意,空格符是不行的。

默認的 make 文件名爲:GNUmakefilemakefileMakefile,當敲入 make 命令時,會自動搜尋這幾個文件。約定俗成使用最後一個。

引用其他 Makefile

語法:

include filename ...        # 不允許 include 失敗
-include filename ...       # 允許 include 失敗

可以包含路徑或者通配符,一行可以包含多個文件。

如果未指定絕對路徑或者相對路徑,那麼 make 會按照一下的順序去尋找:

  1. 當前目錄
  2. 制定 make 時,在 -I 或者 --include-dir 的參數下尋找
  3. <prefix>/include(一般是 /usr/local/bin 或 /usr/include

建議還是手動指定吧,自動搜尋意外可能太多了。

環境變量 MAKEFILES

這裏主要是要提醒:不要設置這個環境變量,否則會影響全局的 make include 動作。

Make 的幾個工作方法

通配符

Make 支持三個通配符:*, ?, [...]。可以用在規則中,也可以用在變量中。

僞目標

僞目標就是 Makefile 裏面頗爲常見的 .PHONY 標識,比如:".PHONY: clean",表示這個規則名並不代表一個真實存在的、需要生成的文件名,而只是一條純粹的規則。

  • 真目標的特點是:如果目標不存在,纔會被執行
  • 僞目標的特點是:無視目標是否存在,必然執行

除了 make clean 之外,僞目標還有另一種使用場景,就是一個 make 動作,實際上生成了多個目標。比如:

.PHONY: all
all: exe install    # 包含了生成目標文件,以及安裝動作

多目標

規則的冒號前面可以有多個 target,表示多個 target 共用這條規則。


自動生成依賴關係

如果我們使用中規中矩的 makefile 寫法,那麼對於每個源文件都要好好寫頭文件依賴關係,從而在頭文件更新的時候,可以自動重新編譯依賴於這個頭文件的源文件。
這實在是太麻煩了。好在 gcc 裏有一個 -MM(注意不是 “-M”) 的選項,可以分析出 .c 文件依賴的頭文件並且打印出來。因此製作 Makefile 的時候,就可以利用這一特性自動生成依賴。

實現方法有很多,這裏貼出我自己使用的例子,也可以參見我的工程代碼

EXCLUDE_C_SRCS =#
C_SRCS = $(filter-out $(EXCLUDE_C_SRCS), $(wildcard *.c))
C_OBJS = $(C_SRCS:.c=.o)

$(C_OBJS): $(C_OBJS:.o=.c)
    $(CC) -c $(CFLAGS) $*.c -o $*.o
    @$(CC) -MM $(CFLAGS) $*.c > $*.d  
    @mv -f $*.d $*.d.tmp  
    @sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d  
    @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $*.d
    @rm -f $*.d.tmp 

書寫命令

這裏的命令,指的是在 Makefile 規則裏的 “command” 部分。

命令執行

將 “@” 放在一條命令的前面,表示實際執行的時候,不打印這條命令語句,可以節省屏幕內容,減少垃圾信息(特別是我的自動生成依賴的命令,調通了之後,那就是一堆無用信息)。如果將 “-” 放在命令前面,則表示無視這條命令的返回值是否爲成功(0).

如果上一條命令的結果需要用於下一條命令時,需要將這些命令寫在一行中。建議用 “\” 分開。這最典型的是 cd 命令及其之後的一連串命令。

Make 的時候加上 -n 選項或 --just-print 選項,則表示不執行 make,而只是把過程打印出來。


嵌套執行 make

在 Makefile 裏可以到另一個目錄下執行 make,執行方式類似於普通的命令調用,但特別的是,make 可以識別出這是一條嵌套 make 指令,從而在 shell 中打印出 “專項哪裏哪裏 make” 的提示語法爲:

subsystem:
    $(MAKE) -C subdir

這個做法的主要好處是可以向下級 Makefile 傳遞變量或者語法:

export VARIABLE ... # 將相應變量變成當前 make 操作的全局變量

也惡意直接指定變量的值:

export VARIABLE = value

如果要傳遞所有變量(不推薦),直接寫 export 就好。

注意由兩個系統變量 SHELL 和 MAKEFLAGS 是永遠傳遞的。
此外還有一個全局變量 MAKELEVEL 用來表示當前的嵌套層數。


定義命令包

命令包類似於宏、子函數等等。使用 define 來定義,以 endif 結束,比如:

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

注意如果是命令的話,需要以製表符開頭。調用這個命令包的方式爲:$(run-yacc)

使用變量

變量賦值

變量定義時必須賦值,至少賦一個空值(只有等號,等號右邊什麼都沒有)

使用變量的時候雖然不強制、但是爲了安全起見,應該使用括號或者打括號把變量包含起來。如果要使用字符 "$",則使用 "$$" 來轉義

賦值時,等號右側可以有未定義的變量,並且在其實際使用時才展開變量的內容。但這可能會導致循環引用。爲了避免這一點,可以使用 ":=" 符號來避免使用未定義的變量

"+=" 的作用是 “追加” 值。如果右側有變量未定義,則等價於 “:=
"?=" 的作用是:如果等號左側的變量未定義,則使用等號右邊內容定義,即:

ifeq ($(some_var), undefined)
some_var = some_val
endif

另外:
$@ 表示當前規則的編譯目標
$^ 表示當前規則的所有依賴文件
$$< 表示當前規則的第一個依賴。


定義一個空格變量

NULL_STR :=#
SPACE_STR := $(NULL_STR) # end of line

注意第二行的註釋與 “)” 之間是包含一個空格的。註釋的 “#” 必須有,否則不會定義一個空格出來。


變量替換

第一個方式爲:$(var: .o = .c),意思是將等號左邊的字符換成右邊的字符
第二個方式爲所謂的 “靜態模式”:$(var: %.o = %.c)


把變量值作爲變量

這很類似於指針,只是地址值變成了變量值。可以用變量值生成變量名,比如:a := $($(var)) 或者是 $($(var)_$(idx)) 之類的寫法。


override

在命令行調用 make 時,可以直接指定某個變量的全局值,使得它在整個 make 的過程中一直不變。爲了防止這個特性,可以使用這個關鍵字來處理:
override <variable> = <value>
等號也可以用 := 和 ?=


目標變量(局部變量)

如果某條約束裏面不想使用已經定義了的全局變量,可以這樣寫:

prog: CFLAGS = -g
prog: a.o b.o
    $(CC) $(CFLAGS) a.o b.o

條件判斷

語法

<條件語句>
<true 執行語句>
else
<false 執行語句>
endif

其中條件語句有四種情形:

1、表示是否相等

    ifeq (<arg1>, <arg2>)    # 推薦
    ifeq '<arg1>' '<arg2>'
    ifeq "<arg1>" "<arg2>"

2、表示是否不等,上面的 ifeq 換成 ifneq
3、ifdef
4、ifndef

使用函數

Make 的所有函數都是內置函數,不能自己定義(命令包除外)。下面列出常用的函數,如果看不懂再詳細查閱。

常規函數

字符串替換

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

模式字符串替換

$(patsubst <pattern>, <replacement>, <text>)

去開頭和結尾的空格

$(strip <string>)

查找字符串

$(findstring <find>, <in>)

反過濾

$(filter-out <pattern_or_string>, <text>)

排序(單詞升序)

$(sort <list>)

取單詞

$(word <n>, <text>)

取單詞串

$(wordlist <n_start>, <n_end>, <text>)

單詞個數統計

$(words <text>)

去掉每個單詞的最後文件名部分,只剩下目錄部分

$(dir <names ...>)

去掉每個單詞的目錄部分,只剩下文件名部分

$(notdir <names ...>)

讀取各文件名的後綴

$(suffix <names ...>)

加後綴

$(addsuffix <suffix>, <names ...>)

加前綴

加前後綴在動態創建局部變量很有用
$(addprefix <prefix>, <names ...>)

連接字符串

$(join <list1>, <list2>)


for 循環

$(foreach <var>, <list>, <text>)
這其實是一個函數,作用是:將 list 的單詞逐一取出,放到 var 指定的變量中,然後執行 text 的表達式。返回值則是 text 的最終執行值。


Shell 函數

執行 shell 命令,並且將 stdout 作爲返回值返回,如:
contents := $(shell ls -la)


控制 make 輸出

$(error <text ...>)
$(warning <text ...>)
這也同時是調試和定位 make 的好方法。


判斷文件是否存在

ifeq ($(FILE), $(wildcard $(FILE)))
...
endif
發佈了3 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章