Linux 開(kāi)發(fā) | 學(xué)習(xí) Makefile

@(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)
  • 注釋
    以 # 開(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。

基本例子

借用手冊(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) 分別指向 $< 的目錄名和文件名。

手冊(cè)中的詳細(xì)描述


條件判斷

簡(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è)



參考

GNU Make Manual
中文版-跟我一起寫(xiě)makefile

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 來(lái)自陳浩的一片老文,但絕對(duì)營(yíng)養(yǎng)。 示例工程:3 個(gè)頭文件*.h,和 8 個(gè) C 文件*.c。 初 編譯過(guò)程,源文件...
    周筱魯閱讀 4,787評(píng)論 0 17
  • makefile關(guān)系到整個(gè)工程的編譯規(guī)則,一個(gè)工程中的源文件不計(jì)其數(shù),按其類型、功能、模塊分別放在若干的目錄當(dāng)中,...
    Joe_HUST閱讀 1,994評(píng)論 0 3
  • 本文章介紹了makefile跟kconfig文件,包括編譯過(guò)程與makefile編碼規(guī)則。 編譯過(guò)程:我們?cè)谶M(jìn)行l(wèi)...
    超低空閱讀 17,725評(píng)論 0 5
  • linux資料總章2.1 1.0寫(xiě)的不好抱歉 但是2.0已經(jīng)改了很多 但是錯(cuò)誤還是無(wú)法避免 以后資料會(huì)慢慢更新 大...
    數(shù)據(jù)革命閱讀 13,269評(píng)論 2 33
  • 科考隊(duì)員從南極回老家后陸續(xù)自殺,隊(duì)員的鄰居心理醫(yī)生康發(fā)覺(jué)他們交談過(guò)的人也都會(huì)行為古怪,突然樂(lè)于社交,然后控制不住的...
    楊彤宇閱讀 300評(píng)論 0 0

友情鏈接更多精彩內(nèi)容