@(linux 編程)[開(kāi)發(fā)技能, 工具使用]
What is GNU Make
Make 是控制工程中通過(guò)源碼生成可執(zhí)行文件和其他相關(guān)文件的工具。Make 通過(guò) Makefile 獲取如何編譯、鏈接和安裝清理工程的信息。
本文記錄如何為自己的工程編寫(xiě)一個(gè)Makefile,主要參考 GNU Make Manual。獲取詳細(xì)信息請(qǐng)直接閱讀手冊(cè)。
[TOC]
上部分
Makefile 基本語(yǔ)法介紹。
Makefile概述
基本格式
基本上每一個(gè) Makefile 主體就是由若干個(gè)以下規(guī)則模塊組成 : 表明輸出的目標(biāo),輸出目標(biāo)的依賴對(duì)象和生成目標(biāo)需要執(zhí)行的命令。
target ..: prerequisites ...
recipe
....
-
target: 目標(biāo)、標(biāo)記??梢允俏募?,比如下文 基本例子 中的 edit 或者 main.o,也可以是標(biāo)簽,比如 clean。 -
prerequisites: 先決條件,前提依賴,指明想要 target,需要先有哪些依賴。如 基本例子 中,要輸出 edit, 需要先編譯 main.o...等文件。 -
recipe: 執(zhí)行的命令。每行命令以tab開(kāi)頭(.RECIPEPREFIX 默認(rèn)規(guī)定的)
舉個(gè)例子, 我們寫(xiě)了 hello.c 打印可愛(ài)的 hello world,一般編寫(xiě)后,我們?cè)诿钚袌?zhí)行 gcc -o hello hello.c 生成 hello 可執(zhí)行文件。這里, hello 就是我們的目標(biāo) target, 而 gcc -o hello hello.c 就是命令 recipe, 對(duì)應(yīng)的依賴 prerequisites 就是 hello.c。
所以可以寫(xiě)第一個(gè) Makefile:
hello: hello.c
gcc -o hello hello.c
然后直接在目錄下輸入make,就可以得到 hello。
Makefile 主要組成
- 顯式規(guī)則
明確寫(xiě)出來(lái)的依賴關(guān)系,如上述的 prerequisites.... - 隱式規(guī)則
Make 自己推導(dǎo)出來(lái)的規(guī)則,比如目標(biāo)為 main.o 就推出依賴條件中需要 main.c和對(duì)應(yīng)的編譯命令 - 變量定義
類似程序中宏定義, 文本替換。 - 文件指示 (有點(diǎn)像程序中預(yù)編譯涉及到的.)
- include 其他 Makefile :
-include xx.md; - 選擇執(zhí)行(類似選擇編譯 #ifdef...);
- 定義多行命令(define ... endef)
- include 其他 Makefile :
- 注釋
以 # 開(kāi)頭
Make 工作流程
- Make 讀取當(dāng)前目錄下命名為
GNUmakefile/Makefile/makefile或者 -f 直接指定的所有規(guī)則文件。 - 讀入被 include 的其他 Makefile,在對(duì)應(yīng)位置展開(kāi)
- 初始化變量
- 推導(dǎo)隱式規(guī)則;分析所有規(guī)則,創(chuàng)建依賴關(guān)系鏈,決定哪些需要【重新】生成,執(zhí)行命令。
- 從第一個(gè) target(排除以 . 開(kāi)頭的 target,比如 .PHONY)開(kāi)始,這個(gè)就是默認(rèn)目標(biāo),本次任務(wù)的終極目標(biāo)(或者可以顯式設(shè)定
.DEFAULT_GOAL)。 比如在下面的 基本例子 中,edit就是終極目標(biāo)。 - 判斷目標(biāo)是否存在, 依賴的對(duì)象是否有更新
- 根據(jù)依賴關(guān)系一步一步追溯查找,建立依賴關(guān)系鏈,執(zhí)行需要執(zhí)行的命令,最終輸出終極目標(biāo)。
- 沒(méi)有在依賴鏈上的目標(biāo)是不會(huì)被直接執(zhí)行到的,比如 clean。
- 從第一個(gè) target(排除以 . 開(kāi)頭的 target,比如 .PHONY)開(kāi)始,這個(gè)就是默認(rèn)目標(biāo),本次任務(wù)的終極目標(biāo)(或者可以顯式設(shè)定
基本例子
借用手冊(cè)舉的例子,有一個(gè)工程,由幾個(gè).c 和 .h 文件組成,最終輸出可執(zhí)行文件 edit,簡(jiǎn)單(簡(jiǎn)陋..?)地用以下 makefile 描述他們的依賴關(guān)系。
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
# make讀取到的第一個(gè)target, 默認(rèn)設(shè)置為 終極目標(biāo)
edit: $(objects)
cc -o edit $(objects)
# 隱含規(guī)則, make 根據(jù) xx.o 推導(dǎo)出依賴 xx.c, 以及相應(yīng)的編譯命令
# 如 -> main.o: defs.h
# 等同 :
# -> main.o: main.c defs.h
# -> cc -o main.o main.c defs.h
main.o: defs.h
kbd.o: defs.h command.h
command.o: defs.h command.h
display.o: defs.h buffer.h
insert.o: defs.h buffer.h
search.o: defs.h buffer.h
files.o: defs.h buffer.h command.h
utils.o: defs.h
# .PHONY 定義為目標(biāo) clean
# 和 edit 沒(méi)有依賴關(guān)系 直接執(zhí)行 make 是不會(huì)運(yùn)行到的
# 通過(guò) make clean 執(zhí)行
.PHONY: clean
clean:
-rm edit $(objects)**
編寫(xiě)規(guī)則
Make 會(huì)讀取 Makefile 中 第一個(gè)規(guī)則的第一個(gè)目標(biāo), 設(shè)置為要完成的最終目標(biāo)。其他目標(biāo)都是被這個(gè)目標(biāo)的依賴關(guān)系連帶進(jìn)來(lái)的。比如基本例子中最終目標(biāo)是 edit,而 eidt 依賴 main.o...等文件鏈接而來(lái),所以 make 就會(huì)自動(dòng)去執(zhí)行 main.o..等文件的生成規(guī)則,最后再合成 edit。
規(guī)則包含 : 依賴關(guān)系 和 生成目標(biāo)的方法
把上面的 Makefile 修改一下:
# Makefile learn by lcd
SRCS = main.c
SRCS += command.c
SRCS += display.c
SRCS += files.c
SRCS += insert.c
SRCS += kbd.c
SRCS += search.c
SRCS += utils.c
# 語(yǔ)法替換
OBJS = $(SRCS:.c=.o)
DEPS = $(SRCS:.c=.d)
# 第一個(gè)目標(biāo) all,終極目標(biāo)
all : edit
edit : $(OBJS)
$(CC) -o edit $(OBJS)
-include $(DEPS)
# 包含觸發(fā)下面的 DEPS 依賴
$(DEPS) : %.d : %.c
@set -e; rm -f $@;\
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$;\
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;\
rm -f $@.$$$$
.PHONY : clean
clean :
@-rm edit $(OBJS) $(DEPS)
說(shuō)明下規(guī)則
取其中一段
edit : $(OBJS)
$(CC) -o edit $(OBJS)
# 在這里實(shí)際展開(kāi)應(yīng)該是
edit : main.o command.o ...
cc -o edit main.o command.o ...
- 第一行說(shuō)明文件的依賴關(guān)系,edit 是由 main.o command.o... 這幾個(gè)文件鏈接而成的,依賴于他們。如果其中某個(gè)或多個(gè) xx.o... 文件日期比 edit 新或者 edit 不存在,那么依賴關(guān)系就發(fā)生了。
- 發(fā)生依賴關(guān)系,Make 就會(huì)去執(zhí)行下面的命令(tab縮進(jìn)),其說(shuō)明 edit 是如何通過(guò)依賴對(duì)象生成的。Make 會(huì)以 shell(/bin/sh)來(lái)執(zhí)行命令。
- 上面這段規(guī)則,目標(biāo)targets 是 edit, Makefile 中,targets 是文件名也可以是標(biāo)號(hào)(比如clean),多個(gè)用空格分開(kāi),可以使用通配符(shell)。
Make 搜尋文件
實(shí)際中,比較大的工程文件都會(huì)分類放在不同目錄下,當(dāng) Make 需要尋找文件依賴關(guān)系的時(shí)候,需要告知去尋找的路徑,否則 make 只會(huì)查找當(dāng)前目錄。 兩種方法:
- VPATH (變量)
VPATH = src:../inc
如上,指定了 ./src 和 ../inc 兩個(gè)目錄,冒號(hào)分隔,當(dāng)前目錄 搜索不到依賴文件的情況下,Make 就會(huì)依順序進(jìn)行搜索。
- vpath (關(guān)鍵字)
注意:這不是一個(gè)變量,按照使用方式可以多次調(diào)用設(shè)定文件的搜索模式。
vpath 使用的三種方法
1、vpath <pattern> <directories>
為符合模式<pattern>的文件指定搜索目錄<directories>。
2、vpath <pattern>
清除符合模式<pattern>的文件的搜索目錄。
3、vpath
清除所有已被設(shè)置好了的文件搜索目錄。
vapth 使用方法中, <pattern>需要包含“%”字符?!?”的意思是匹配零或若干字符。例如,“%.h”表示所有以“.h”結(jié)尾的文件。<pattern>指定了要搜索的文件集,而 <directories>則指定了<pattern>的文件集的搜索的目錄。
vpath %.c dir1 # 在 dir1 尋找 .c 文件
vpath % dir2 # 在 dir2 尋找 任何需要的文件
vpaht %.c dir3 # 同 1
# 當(dāng)前目錄找不到的情況下i, 按照 dir1.2.3的順序查找 .c 文件
偽目標(biāo)
上面例子中,clean 就是一個(gè)偽目標(biāo)。偽目標(biāo)是一個(gè)標(biāo)簽,執(zhí)行一些動(dòng)作,比如清除文件,安裝程序等。因?yàn)闆](méi)有依賴關(guān)系,所以 make 無(wú)法直接決定是否需要執(zhí)行。我們顯示地用 .PHONY來(lái)告訴 make 這是一個(gè)偽目標(biāo), 避免與實(shí)際目標(biāo)命名沖突。
同運(yùn)行程序的時(shí)候我們給個(gè)參數(shù)讓程序執(zhí)行特定動(dòng)作一樣,運(yùn)行 make 時(shí)指定偽目標(biāo)標(biāo)簽,指定執(zhí)行對(duì)應(yīng)的命令。就如上述例子,執(zhí)行 make clean 時(shí)進(jìn)行清理工作。
靜態(tài)模式
對(duì)應(yīng)多個(gè)目標(biāo)對(duì)象,構(gòu)建每個(gè)對(duì)象對(duì)應(yīng)名稱的依賴關(guān)系的規(guī)則。
舉個(gè)例子怎么用
OBJS = aa.o bb.o cc.o
$(OBJS) : %.o : %.c
cc -c $< -o $@
# 等同:
aa.o : aa.c
cc -c aa.c -o aa.o
bb.o : bb.c
cc -c bb.c -o bb.o
cc.o : cc.c
cc -c cc.c -o cc.o
上述例子,Make 從 OBJS 集合中獲取符合 目標(biāo)模式 %.o 的文件作為目標(biāo),依賴模式 %.c 取前面獲取的“%.o”的“%” 部分作為自己的前綴。在文件很多的情況下,可以大大提高了書(shū)寫(xiě)效率。
自動(dòng)生成依賴關(guān)系
如果在 main.c 中包含了 defs.h 文件,那么依賴關(guān)系上我們需要寫(xiě)上 defs.h,這樣,當(dāng) defs.h 文件修改了(比如新定義了一個(gè)宏..),Make 才會(huì)重新執(zhí)行依賴關(guān)系。但是對(duì)于一個(gè)文件包含什么頭文件,對(duì)應(yīng)修改 Makefile,這樣是很難維護(hù)的。
C/C++ 編譯器 -MM 功能可以自動(dòng)找尋文件的包含 ,生成依賴關(guān)系。
執(zhí)行:
$ gcc -MM mian.c
輸出:
main.o : main.c defs.h
因此,我們借助編譯器幫我們自動(dòng)生成依賴關(guān)系,并包含到 Makefile 中
-include $(DEPS)
$(DEPS) : %.d : %.c
@set -e; rm -f $@;\
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$;\
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;\
rm -f $@.$$$$
上述的 -include 把每個(gè)源文件對(duì)應(yīng)的依賴 [.d] 文件(gcc -MM生成的依賴關(guān)系)包含進(jìn)來(lái),把 [.d] 文件的更新也納入 Makefile 中,修改了某個(gè)文件的依賴關(guān)系,對(duì)應(yīng)命令執(zhí)行生成新的依賴文件。
上面的命令,每個(gè)[.d] 文件依賴對(duì)應(yīng)的[.c]文件,具體說(shuō)明下執(zhí)行命令的作用:
- $@ $* 和 $< 是自動(dòng)變量
-
rm -f $@刪除舊的目標(biāo)文件 - 借助編譯器(-MM)為每個(gè)源文件生成依賴關(guān)系并保存到對(duì)應(yīng)的 name.xxxx (在Makefile中 $ 有特殊含義,如果要表示它的字面意思需要寫(xiě)兩個(gè) $,所以 Makefile 中的四個(gè) $ 傳給Shell變成兩個(gè) $,而兩個(gè) $ 在Shell中表示當(dāng)前進(jìn)程的id,一般用它給臨時(shí)文件起名,以保證文件名唯一。)
- sed 替換輸出文本,達(dá)到的目的類似如下
# 編譯器輸出格式
main.o : main.c defs.h
# 轉(zhuǎn)換為如下格式
main.o main.d : main.c defs.h
并保存到[.d]文件。
- 刪除臨時(shí)文件
最后展開(kāi)就如同開(kāi)頭例子一樣,列出每個(gè)[.o]文件的依賴, 相比前面似乎更加復(fù)雜了,但是想想,在很多源文件的情況下,就會(huì)變得很簡(jiǎn)潔。
編寫(xiě)執(zhí)行的命令
target .. : prerequisites ...
recipe
....
上述 recipe 命令部分由若干條 shell 命令行組成,一般用于生成、更新對(duì)象。 默認(rèn)使用 /bin/sh 執(zhí)行命令。
默認(rèn)每行命令必須以 Tab 開(kāi)頭。規(guī)則下對(duì)應(yīng)的所有以 Tab 開(kāi)頭的指令,會(huì)被傳遞到對(duì)應(yīng)的 shell 執(zhí)行。
Makefile 執(zhí)行指令必須在 recipe 這個(gè)位置。
在命令中使用變量注意
前面提到, $ 在 make 和 shell 中要注意的地方, 再舉個(gè)例子
LIST = one two three
all:
for i in $(LIST); do \
echo $$i; \
done
實(shí)際傳遞給 shell 的模式
for i in one two three; do \
echo $i; \
done
結(jié)果輸出 :
one
two
three
避免錯(cuò)誤使用給自己帶來(lái)的錯(cuò)誤。
命令回響
在 Makefile 中執(zhí)行如下命令,
echo 命令執(zhí)行
終端會(huì)輸出如下 :
echo 命令執(zhí)行
命令執(zhí)行
第一行是執(zhí)行的命令完整打印(回響),第二行才是我們需要的輸出的,關(guān)閉命令回響的方法是在該行命令前添加 @
@echo 命令執(zhí)行
如果 Make 執(zhí)行時(shí),帶參數(shù)“-n”或“--just-print”,那么其只是顯示命令,不會(huì)執(zhí)行命令,這個(gè)功能有利于我們調(diào)試我們的 Makefile,看看我們書(shū)寫(xiě)的命令執(zhí)行起來(lái)是什么樣子的或是什么順序的。 而 Make 帶參數(shù)“-s”或“--slient”則是全面禁止命令的顯示。
命令的依賴
shell 按順序一條條執(zhí)行規(guī)則指定的命令。但是如果需要讓上一條命令的結(jié)果應(yīng)用到下一條,需要用分號(hào)分隔命令并保證命令處于同一行。
假設(shè)在目錄 /home/lcd/mf/ 下執(zhí)行 Makefile
exec1 :
@cd /home/lcd/kk
@pwd
# show : /home/lcd/mf
exec2 :
@cd /home/lcd/kk; pwd
# show : /home/lcd/kk
exec1 顯示的依然是 Makefile 執(zhí)行的所在目錄,因?yàn)樯弦粭l命令的結(jié)果沒(méi)有應(yīng)用到下一條。
忽略出錯(cuò)命令
一般情況,Make 會(huì)一條一條執(zhí)行命令,當(dāng)某條命令執(zhí)行后出錯(cuò), Make 會(huì)終止當(dāng)前規(guī)則,這可能導(dǎo)致整個(gè)任務(wù)終止。
有時(shí)候執(zhí)行一些命令無(wú)需考慮出錯(cuò),比如某文件存在刪除,不存在就不管等。 這種情況下不希望出錯(cuò)終止,可以在任務(wù)前添加一個(gè)減號(hào) -
clean :
-rm -f *.o
一個(gè)全局方法是, Make 運(yùn)行加上“-i”或是“--ignore-errors”參數(shù),那么,
Makefile 中所有命令都會(huì)忽略錯(cuò)誤。
如果一個(gè)規(guī)則是以“.IGNORE”作為目標(biāo)的,那么這個(gè)規(guī)則中的所有命令將會(huì)忽略錯(cuò)誤。
Make 的參數(shù)的是“-k”或是“--keep-going”,這個(gè)參數(shù)的意思是,如果某規(guī)則中的命令出錯(cuò)了,那么就終目該規(guī)則的執(zhí)行,但繼續(xù)執(zhí)行其它規(guī)則。
Makefile 嵌套
對(duì)于一個(gè)比較大的工程,不同模塊分類在不同目錄,分別用一個(gè) Makefile 進(jìn)行管理,模塊化編譯,方便工程維護(hù)和保證 Makefile 的簡(jiǎn)潔。
- 例如,子目錄 subdir 下有一個(gè) Makefile 描述該目錄模塊的編譯規(guī)則, 那么總控 Makefile 中調(diào)用子目錄 Makefile 可以這么寫(xiě):
subsystem :
cd subdir && $(MAKE)
# 等價(jià)
subsystem :
$(MAKE) -C subdir
使用
$(MAKE)宏定義,在某些情況下調(diào)用嵌套我們可以直接修改添加參數(shù)。另外,當(dāng)運(yùn)行 Make 時(shí)候添加諸如 ‘-t’ (‘--touch’), ‘-n’ (‘--just-print’), or ‘-q’ (‘--question’) z這些特殊選線時(shí),使用$(MAKE)可以保證語(yǔ)句相當(dāng)于在前面添加了+號(hào) 的作用(特殊命令,繼續(xù)執(zhí)行)。很正常,希望測(cè)試的時(shí)候命令不是真的執(zhí)行,但是包含其他 Makefile 這種命令是例外,必須執(zhí)行,不然 Makefile 就不完整了, 我是這么理解的。
- 上層 Makefile 中定義的變量是可以在被調(diào)用的下一層 Makefile 中使用的, 前提是該變量在上層中被顯式暴露
export,同理,可以采用unexport取消。
export OBJS # 傳遞 變量 OBJS
export # 不指定,全部傳遞
如此,在下面的 makefile 就可以直接使用了。但是如果下層目錄已經(jīng)定義了該變量,那么下層默認(rèn)使用的是它自己定義的變量值,除非上層 makefile 在調(diào)用下層 makefile 時(shí)給參數(shù) -e,則會(huì)強(qiáng)行覆蓋。
兩個(gè)變量,一個(gè)是 SHELL,一個(gè)是 MAKEFLAGS,這兩個(gè)變量不管你是否 export,其總是要傳遞到下層 Makefile 中。
Make 命令中的有幾個(gè)參數(shù)并不往下傳遞,它們是“-C”,“-f”,“-h”“-o”和“-W”
嵌套執(zhí)行中, “-w”或是“--print-directory”會(huì)在 Make 的過(guò)程中讓你看到目前的工作目錄。
比如,如果我們的下級(jí) Make 目錄是/home/lcd/mf/subdir,如果我們使用“make -w”來(lái)執(zhí)行,那么當(dāng)進(jìn)入該目錄時(shí),我們會(huì)看到:
make: Entering directory '/home/lcd/mf/subdir'
而在完成下層 make 后離開(kāi)目錄時(shí),我們會(huì)看到:
make: Leaving directory `/home/lcd/mf/subdir'
當(dāng)你使用“-C”參數(shù)來(lái)指定 Make 下層 Makefile 時(shí),“-w”會(huì)被自動(dòng)打開(kāi)的。如果參數(shù)中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”總是失效的。
命令組宏定義
和程序中的宏定義,展開(kāi)一樣。因?yàn)橹苯诱归_(kāi),注意縮進(jìn)問(wèn)題。
define xxx_name
xxx
xxx
endef
targe : xx
$(xxx_name)
#等同
targe : xx
xxx
xxx
使用空命令
target : ;
上述規(guī)則, 執(zhí)行了一條空指令。這樣寫(xiě)的一些理由是:
- 避免 Make 自己推測(cè)命令(隱性規(guī)則)
- Make 不會(huì)報(bào)錯(cuò)他不知道該對(duì)象如何生成,并假設(shè)已經(jīng)是最新。
Makefile 中的變量
Makefile 中的變量,就如程序中的宏定義,代表一個(gè)字串,在使用的地方展開(kāi),通過(guò) $(variable) 表示變量的內(nèi)容,和 shell 類似。
變量賦值
foo = $(bar)
bar = $(ugh)
ugh = huh?
all :
echo $(foo)
# 顯示 : huh?
賦值 ?=, +=, = 和 := 的差別
- ?= 如果沒(méi)有被賦值過(guò)就賦予等號(hào)后面的值
- += 添加等號(hào)后面的值
- = 最基本的賦值(最后才展開(kāi))
make會(huì)將整個(gè)makefile展開(kāi)后,再?zèng)Q定變量的值。也就是說(shuō),變量的值展開(kāi)是在最后, 使我們可以在最后才指定變量的值。
x = XXX
y = $(x)
x = YYY
在上例中,y的值將會(huì)是 YYY ,而不是 XXX。
- := 是覆蓋之前的值(類似C中的 = )
變量的值決定于它在makefile中的位置,而不是整個(gè)makefile展開(kāi)后的最終值。
x := XXX
y := $(x)
x := YYY
在上例中,y的值將會(huì)是 XXX ,而不是 YYY了。
變量值替換
foo := a.o b.o c.o
bar := $(foo:.o=.c)
# 等同
foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
# bar : a.c b.c c.c
定義一個(gè)空格
nullstring :=
space := $(nullstring) # end of the line
操作符號(hào)對(duì)右邊一開(kāi)始的空格不做處理,所以很難描述一個(gè)空格,上面的方法實(shí)現(xiàn)了,sapce 保存一個(gè)空格。
override 指示符
如果在運(yùn)行 Make 的時(shí)候在命令參數(shù)設(shè)置了變量,則 Makefile 對(duì)變量的設(shè)置默認(rèn)被忽略,如果不想被忽略,可以使用override。
override <variable> = <value>
override <variable> := <value>
override <variable> += <more text>
模式變量
對(duì)應(yīng)變量只應(yīng)用到符合模式的對(duì)象上
%.o : CFLAGS = -O
# <pattern ...> : override <variable-assignment>
自動(dòng)變量
為了方便擴(kuò)展, 經(jīng)常會(huì)看到類似的一些奇怪符號(hào)。
- $@
$@ 指代當(dāng)前目標(biāo),Make 命令當(dāng)前構(gòu)建的那個(gè)目標(biāo)。比如,make foo 的 $@ 就指代 foo。
a.txt b.txt:
touch $@
#等同于下面的寫(xiě)法。
a.txt:
touch a.txt
b.txt:
touch b.txt
- $<
$< 指代第一個(gè)依賴條件。
如果依賴對(duì)象是一個(gè)序列,依次代表每一個(gè)依賴條件
a.txt: b.txt c.txt
cp $< $@
#等同于下面的寫(xiě)法。
cp b.txt a.txt
cp c.txt a.txt
$^
$^ 指代所有前置條件,之間以空格分隔。
比如,規(guī)則為t: p1 p2,那么 $^ 就指代 p1 p2 。$?
$? 指代比目標(biāo)更新的所有前置條件,之間以空格分隔。
比如,規(guī)則為t: p1 p2,其中 p2 的時(shí)間戳比 t 新,$?就指代p2。$*
$* 指代匹配符 % 匹配的部分,
比如 %.txt 匹配 f1.txt 中的 f1 ,$* 就表示 f1。$(@D)和$(@F)
$(@D)和$(@F)分別指向 $@ 的目錄名和文件名。
比如,$@ 是 src/input.c,那么$(@D)的值為 src ,$(@F)的值為 input.c。$(<D)和$(<F)
$(<D)和$(<F)分別指向 $< 的目錄名和文件名。
條件判斷
簡(jiǎn)述
類比程序中的條件編譯, Make 可以根據(jù)運(yùn)行時(shí)不同情況選擇執(zhí)行不同分支。
libs_for_gcc = -lgnu
normal_libs =
foo : $(OBJS)
ifeq ($(CC), gcc) # 不縮進(jìn)
$(CC) -o foo $(OBJ) $(libs_for_gcc) # 傳遞命令,縮進(jìn)
else
$(CC) -o foo $(OBJ) $(normal_libs)
endif
簡(jiǎn)單地說(shuō),上面表達(dá)的是,如果使用的編譯器是 gcc,則編譯時(shí)添加參數(shù)libs_for_gcc,否則給另一個(gè)參數(shù)normal_libs。
其實(shí)和 C 中的條件編譯差不多
注意
條件語(yǔ)句部分不需要縮進(jìn), 否則會(huì)被認(rèn)為是傳遞給 shell 的命令
Make 條件判斷語(yǔ)法
看起來(lái)和 shell 中的條件判斷差不多,
分支組成
# if-endif
conditional-directive
text-if-true
endif
# if-else-endif
conditional-directive
text-if-true
else
text-if-false
endif
# if-elsif0-elsif2..-elsifn-else-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 $(STRING), 'AA'
echo AA
else ifeq $(STRING), 'BB'
echo BB
else
echo XX
endif
判斷關(guān)鍵詞
-
判斷相等
完全展開(kāi)變量進(jìn)行比較
ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2'
ifeq "ARG1" "ARG2"
ifeq "ARG1" 'ARG2'
ifeq 'ARG1' "ARG2"
# 對(duì)應(yīng)
#ifneq
手冊(cè)舉的例子,用于判斷變量是否為空
ifeq ($(strip $(foo)),)
text-if-empty
endif
- 判斷是否定義
ifdef variable_name
ifndef variable_name
注意例子, 只做第一次展開(kāi)!!!!,體會(huì)下一下例子差別
bar =
foo = $(bar)
ifdef foo
# 展開(kāi) foo -> $(bar)
# 所以認(rèn)為定義了, 即使 foo 最終是空
echo foo def
endif
ifdef bar
# 展開(kāi) bar, 空,認(rèn)為未定義
echo bar def
endif
對(duì)于嵌套 Makefile, 不允許一個(gè)完整的 if-endif 語(yǔ)句跨越兩個(gè) Makefile
例子,判斷執(zhí)行 flag
函數(shù) findstring 用于判斷 A 字符串是否在 B 字符串, 沒(méi)有返回空,有返回 A
下面例子, 根據(jù)是否帶有“-t” 參數(shù)執(zhí)行不同命令。
archive.a: ...
ifneq (,$(findstring t,$(MAKEFLAGS)))
# 如果執(zhí)行make -t, 則執(zhí)行這里
# -t touch, 表示實(shí)際不執(zhí)行命令,但是這里需要這個(gè)參數(shù)后命令
# 依舊執(zhí)行,所以前綴 ``+``
+touch archive.a
+ranlib -t archive.a
else
ranlib archive.a
endif
函數(shù)調(diào)用
調(diào)用語(yǔ)法
函數(shù)可以出現(xiàn)在任何變量可以出現(xiàn)的位置,對(duì)變量進(jìn)行文本處理。
$(function arguments) # 風(fēng)格 1
${function arguments} # 風(fēng)格 2
# 選一種 別混用 統(tǒng)一好看避免不必要麻煩
如上, 對(duì)應(yīng)下面例子中, function 對(duì)應(yīng)“subst”, arguments 對(duì)應(yīng)“$(space),$(comma),$(foo))”, “subst”這個(gè)函數(shù)提供替換字符功能
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# foo 中的空格替換為逗號(hào)
# bar is now ‘a(chǎn),b,c’.
function 后面對(duì)應(yīng)傳遞的參數(shù),第一個(gè)參數(shù)與函數(shù)名通過(guò)空格或者 tab 劃分,如果一個(gè)函數(shù)參數(shù)不止一個(gè),不同參數(shù)通過(guò)逗號(hào)分隔。函數(shù)調(diào)用返回,通過(guò) $ 獲取,和變量使用一致。
字符串處理函數(shù)
文本替換函數(shù)
- 簡(jiǎn)單替換
將“text”中的“from”部分替換為“to”
$(subst from ,to ,text)
# 例子
# 返回 : ‘fEEt on the strEEt’.
$(subst ee, EE, feet on the street)
- 模式替換
讀取函數(shù)名后面的模式, 匹配“text”中符合的部分替換為第二個(gè)參數(shù)指定的內(nèi)容。
$(patsubst pattern ,replacement ,text)
# 例子 1
# 模式替換,同時(shí), 多個(gè)空格會(huì)被折疊為一個(gè)
# 返回 :x.c.o bar.o
$(patsubst %.c, %.o, x.c.c bar.c)
# 例子 2
objects = foo.o bar.o baz.o
$(patsubst %.o, %.c, $(objects))
# equil to
$(objects:.o=.c)
去空格函數(shù)
去除字符串開(kāi)頭和結(jié)尾的空格,同時(shí)對(duì)中間的多個(gè)空格替換為一個(gè)。
$(strip string)
# 例子
# 返回: a b c
$(strip a b c )
在判斷變量是否為空的情況下使用,可以避免多次賦值帶來(lái)的空格影響,提高魯棒性
字符查找函數(shù)
判斷字符中是否包含指定字符串, 有返回查找的字符串,否則返回空。
$(findstring find ,in)
# 例子
# 返回 : a
$(findstring a,a b c)
# 返回 : ""
$(findstring a,b c)
字符串模式過(guò)濾
返回符合或者不符合的字符串, 輸入字符單詞空格區(qū)分
- 返回符合的字符串
$(filter pattern ...,text)
# 例子
# 返回 foo.c bar.c baz.s
sources := foo.c bar.c baz.s ugh.h
$(filter %.c %.s, $(sources))
- 返回不符合的字符串
$(filter-out pattern ...,text)
# 例子
# 返回 foo.o bar.o
objects = main1.o foo.o main2.o bar.o
mains = main1.o main2.o
$(filter-out $(mains), $(objects))
排序、去重函數(shù)
按字母順序?qū)π蛄校崭駝澐郑┻M(jìn)行排序,同時(shí)去除重復(fù)的詞組, 返回按單個(gè)空格進(jìn)行劃分。
$(sort list)
# 例子
# 返回 : bar foo lose
$(sort foo bar lose lose)
字符串切片函數(shù)
數(shù)組數(shù)組
$(word n ,text)
# 返回 : bar
$(word 2, foo bar baz)
$(wordlist s ,e ,text)
# 返回 : bar baz
$(wordlist 2, 3, foo bar baz)
$(words text)
# 字符串成員個(gè)數(shù) 空格劃分
# 返回 : 4
$(words aa bb cc dd)
$(firstword names ...)
# 返回 : foo
$(firstword foo bar)
$(lastword names ...)
# 返回 : bar
$(lastword foo bar)
舉個(gè)實(shí)際例子
C 編譯器編譯參數(shù) -I 后帶路徑,下面例子通過(guò) VPATH 生成 CFLAGS 變量供編譯器使用。
VPATH = src:../headers
# 空格 替換原來(lái)的分隔符號(hào)
# 返回 : src ../headers
$(subst :, ,$(VPATH))
# 加上-I 前綴
# 返回 : -Isrc -I../headers
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
文件名處理函數(shù)
$(dir names ...)
# 返回除去最后文件名的路徑部分, 沒(méi)有路徑直接返回“./”
# 返回 : lcd/src/ ./
$(dir lcd/src/foo.c hacks)
$(notdir names ...)
# 返回不包含目錄的文件名
# 返回 : foo.c hacks
$(notdir src/foo.c hacks lcd/)
$(suffix names ...)
# 返回文件后綴(逆序第一個(gè) . 后面字符串)
# 返回 : .c .c .o
$(suffix src/foo.c src-1.0/bar.c hacks lcd.c.o)
$(basename names ...)
# 去除后綴, 文件目錄內(nèi)的后綴(.)不包括
# 返回 : src/foo src-1.0/bar hacks
$(basename src/foo.c src-1.0/bar hacks)
$(addsuffix suffix, names ...)
# 加后綴
# 返回 : foo.c bar.c
$(addsuffix .c, foo bar)
$(addprefix prefix, names ...)
# 加前綴
# 返回 : src/foo src/bar
$(addprefix src/, foo bar)
$(join list1, list2)
# 對(duì)應(yīng)連接參數(shù)
# 返回 : a.c b.o
$(join a b, .c .o)
$(wildcard pattern )
# 獲取工作目錄下所有符合模式的文件
# 返回所有 .C 文件
$(wildcard *.c)
$(realpath names ...)
# 返回絕對(duì)路徑, 不包含 . 或者 ..
# 如果文件不存在,返回空
$(abspath names ...)
# 返回絕對(duì)路徑, 不包含 . 或者 ..
條件函數(shù)
- if
如果 condition 為真, 返回 then-part 代表的值, 否則放回 else-part 的值(沒(méi)有的話默認(rèn)返回空)。
$(if condition,then-part [,else-part])
# 例子
# 返回 : false
con =
$(if $(conn), "true", "false")
- or
依次展開(kāi)每個(gè)參數(shù),遇到非空的就停止,并返回該值,否則最后返回空。
$(or condition1 [,condition2 [,condition3 ...]])
# 例子
# 返回 : CONN2
conn1 =
conn2 = CONN2
conn3 = CONN3
$(or $(conn1), $(conn2), $(conn3))
- and
展開(kāi)所有參數(shù),如果有一個(gè)為空,返回空,如果全部都不為空,返回最后一個(gè)參數(shù)。
$(and condition1 [,condition2 [,condition3 ...]])
# 例子 1
# 返回 : 空, 因?yàn)橛幸粋€(gè)空
conn1 =
conn2 = CONN2
conn3 = CONN3
$(and $(conn1), $(conn2), $(conn3))
# 例子 2
# 返回 : CONN3
conn1 = CONN3
conn2 = CONN2
conn3 = CONN3
$(and $(conn1), $(conn2), $(conn3))
循環(huán)函數(shù) foreach
這個(gè)函數(shù)執(zhí)行過(guò)程, 按順序依次取出 list 中的單詞逐個(gè)取出放入到臨時(shí)變量 var 中, 返回 text, 每次返回的 text 以空格分開(kāi),遍歷所有單詞后返回完整的組合字符串。
$(foreach var, list, text)
# 例子 1
# 返回 a.c b.c c.c d.c e.c f.c
list = a b c d e f
$(foreach var, list, $(var).c)
#例子 2
dirs := a b c d
files := $(foreach dir, $(dirs), $(wildcard $(dir)/*))
# 等同于
files := $(wildcard a/* b/* c/* d/*)
讀寫(xiě)文件函數(shù)
file 支持讀寫(xiě),通過(guò) op 確定操作, 后跟操作文件和寫(xiě)入文本(讀取的時(shí)候不能包含),寫(xiě)操作,如果文件不存在,會(huì)自動(dòng)創(chuàng)建。
- “>” 覆蓋寫(xiě)
- “>>” 追加寫(xiě)
- “< ” 讀取
$(file op filename[,text])
沒(méi)試過(guò)...
自定義函數(shù)
當(dāng)make執(zhí)行這個(gè)函數(shù)時(shí),variable參數(shù)中的變量,如$(1),$(2),$(3)等,會(huì)被參
數(shù) parm1, parm2,parm3 依次取代。而 variable 的返回值就是call函數(shù)的返
回值。例如:
$(call variable,param1,param2,...)
# 例子
reverse1 = $(1) $(2)
reverse2 = $(2) $(1)
foo1 = $(call reverse1, a, b)
foo2 = $(call reverse2, a, b)
# foo1 == a b
# foo2 == b a
可以通過(guò) call 函數(shù)定義復(fù)雜的函數(shù)組合
value 函數(shù)
value 函數(shù)返回變量未經(jīng)展開(kāi)的值, 如例子
a = aabb
b = $(a)
echo $(b)
# aabb
echo '$(value b)'
# $(a)
shell 函數(shù)
Makefile 中除了命令區(qū)域,是不能直接執(zhí)行 shell 命令,但是可以通過(guò) shell 函數(shù)執(zhí)行,調(diào)用該函數(shù),會(huì)生成一個(gè)新的程序,所以需要注意效率問(wèn)題。
例子, 在 Makefile 中獲取最后一個(gè) git 提交的 SHA 賦值給變量。
FW_VER = $(shell git log -1 --pretty="%h")
Make 控制函數(shù)
用于在運(yùn)行過(guò)程中提供信息, 打印 log
@echo $(info msg)
@echo $(warning msg)
@echo $(error msg)
# error 中斷執(zhí)行
origin 函數(shù)
不操作變量, 返回變量定義的地方
eval 函數(shù)
flavor 函數(shù)
guile 函數(shù)
下部分
運(yùn)行參數(shù)返回值以及隱含規(guī)則等介紹。
具體手冊(cè)