天天看點

Make 指令教程Make 指令教程

Make 指令教程Make 指令教程

(題圖來自 appstorm.net)

<a target="_blank"></a>

make這個詞,英語的意思是"制作"。make指令直接用了這個意思,就是要做出某個檔案。比如,要做出檔案a.txt,就可以執行下面的指令。

$ make a.txt

但是,如果你真的輸入這條指令,它并不會起作用。因為make指令本身并不知道,如何做出a.txt,需要有人告訴它,如何調用其他指令完成這個目标。

比如,假設檔案 a.txt 依賴于 b.txt 和 c.txt ,是後面兩個檔案連接配接(cat指令)的産物。那麼,make 需要知道下面的規則。

a.txt: b.txt c.txt

cat b.txt c.txt &gt; a.txt

也就是說,make a.txt 這條指令的背後,實際上分成兩步:第一步,确認 b.txt 和 c.txt 必須已經存在,第二步使用 cat 指令 将這個兩個檔案合并,輸出為新檔案。

像這樣的規則,都寫在一個叫做makefile的檔案中,make指令依賴這個檔案進行建構。makefile檔案也可以寫為makefile, 或者用指令行參數指定為其他檔案名。

$ make -f rules.txt

# 或者

$ make --file=rules.txt

上面代碼指定make指令依據rules.txt檔案中的規則,進行建構。

總之,make隻是一個根據指定的shell指令進行建構的工具。它的規則很簡單,你規定要建構哪個檔案、它依賴哪些源檔案,當那些檔案有變動時,如何重新建構它。

建構規則都寫在makefile檔案裡面,要學會如何make指令,就必須學會如何編寫makefile檔案。

makefile檔案由一系列規則(rules)構成。每條規則的形式如下。

&lt;target&gt; : &lt;prerequisites&gt;

[tab] &lt;commands&gt;

上面第一行冒号前面的部分,叫做"目标"(target),冒号後面的部分叫做"前置條件"(prerequisites);第二行必須由一個tab鍵起首,後面跟着"指令"(commands)。

"目标"是必需的,不可省略;"前置條件"和"指令"都是可選的,但是兩者之中必須至少存在一個。

每條規則就明确兩件事:建構目标的前置條件是什麼,以及如何建構。下面就詳細講解,每條規則的這三個組成部分。

一個目标(target)就構成一條規則。目标通常是檔案名,指明make指令所要建構的對象,比如上文的 a.txt 。目标可以是一個檔案名,也可以是多個檔案名,之間用空格分隔。

除了檔案名,目标還可以是某個操作的名字,這稱為"僞目标"(phony target)。

clean:

rm *.o

上面代碼的目标是clean,它不是檔案名,而是一個操作的名字,屬于"僞目标 ",作用是删除對象檔案。

$ make clean

但是,如果目前目錄中,正好有一個檔案叫做clean,那麼這個指令不會執行。因為make發現clean檔案已經存在,就認為沒有必要重新建構了,就不會執行指定的rm指令。

為了避免這種情況,可以明确聲明clean是"僞目标",寫法如下。

.phony: clean

rm *.o temp

如果make指令運作時沒有指定目标,預設會執行makefile檔案的第一個目标。

$ make

上面代碼執行makefile檔案的第一個目标。

前置條件通常是一組檔案名,之間用空格分隔。它指定了"目标"是否重新建構的判斷标準:隻要有一個前置檔案不存在,或者有過更新(前置檔案的last-modification時間戳比目标的時間戳新),"目标"就需要重新建構。

result.txt: source.txt

cp source.txt result.txt

上面代碼中,建構 result.txt 的前置條件是 source.txt 。如果目前目錄中,source.txt 已經存在,那麼make result.txt可以正常運作,否則必須再寫一條規則,來生成 source.txt 。

source.txt:

echo "this is the source" &gt; source.txt

上面代碼中,source.txt後面沒有前置條件,就意味着它跟其他檔案都無關,隻要這個檔案還不存在,每次調用make source.txt,它都會生成。

$ make result.txt

上面指令連續執行兩次make result.txt。第一次執行會先建立 source.txt,然後再建立 result.txt。第二次執行,make發現 source.txt 沒有變動(時間戳晚于 result.txt),就不會執行任何操作,result.txt 也不會重新生成。

如果需要生成多個檔案,往往采用下面的寫法。

source: file1 file2 file3

上面代碼中,source 是一個僞目标,隻有三個前置檔案,沒有任何對應的指令。

$ make source

執行make source指令後,就會一次性生成 file1,file2,file3 三個檔案。這比下面的寫法要友善很多。

$ make file1

$ make file2

$ make file3

指令(commands)表示如何更新目标檔案,由一行或多行的shell指令組成。它是建構"目标"的具體指令,它的運作結果通常就是生成目标檔案。

每行指令之前必須有一個tab鍵。如果想用其他鍵,可以用内置變量.recipeprefix聲明。

.recipeprefix = &gt;

all:

&gt; echo hello, world

上面代碼用.recipeprefix指定,大于号(&gt;)替代tab鍵。是以,每一行指令的起首變成了大于号,而不是tab鍵。

需要注意的是,每行指令在一個單獨的shell中執行。這些shell之間沒有繼承關系。

var-lost:

export foo=bar

echo "foo=[$$foo]"

上面代碼執行後(make var-lost),取不到foo的值。因為兩行指令在兩個不同的程序執行。一個解決辦法是将兩行指令寫在一行,中間用分号分隔。

var-kept:

export foo=bar; echo "foo=[$$foo]"

另一個解決辦法是在換行符前加反斜杠轉義。

export foo=bar; \

最後一個方法是加上.oneshell:指令。

.oneshell:

export foo=bar;

井号(#)在makefile中表示注釋。

# 這是注釋

cp source.txt result.txt # 這也是注釋

正常情況下,make會列印每條指令,然後再執行,這就叫做回聲(echoing)。

test:

# 這是測試

執行上面的規則,會得到下面的結果。

$ make test

在指令的前面加上@,就可以關閉回聲。

@# 這是測試

現在再執行make test,就不會有任何輸出。

由于在建構過程中,需要了解目前在執行哪條指令,是以通常隻在注釋和純顯示的echo指令前面加上@。

@echo todo

通配符(wildcard)用來指定一組符合條件的檔案名。makefile 的通配符與 bash 一緻,主要有星号(*)、問号(?)和 [...] 。比如, *.o 表示所有字尾名為o的檔案。

rm -f *.o

make指令允許對檔案名,進行類似正則運算的比對,主要用到的比對符是%。比如,假定目前目錄下有 f1.c 和 f2.c 兩個源碼檔案,需要将它們編譯為對應的對象檔案。

%.o: %.c

等同于下面的寫法。

f1.o: f1.c

f2.o: f2.c

使用比對符%,可以将大量同類型的檔案,隻用一條規則就完成建構。

makefile 允許使用等号自定義變量。

txt = hello world

@echo $(txt)

上面代碼中,變量 txt 等于 hello world。調用時,變量需要放在 $( ) 之中。

調用shell變量,需要在美元符号前,再加一個美元符号,這是因為make指令會對美元符号轉義。

@echo $$home

有時,變量的值可能指向另一個變量。

v1 = $(v2)

上面代碼中,變量 v1 的值是另一個變量 v2。這時會産生一個問題,v1 的值到底在定義時擴充(靜态擴充),還是在運作時擴充(動态擴充)?如果 v2 的值是動态的,這兩種擴充方式的結果可能會差異很大。

variable = value

# 在執行時擴充,允許遞歸擴充。

variable := value

# 在定義時擴充。

variable ?= value

# 隻有在該變量為空時才設定值。

variable += value

# 将值追加到變量的尾端。

output:

$(cc) -o output input.c

make指令還提供一些自動變量,它們的值與目前規則有關。主要有以下幾個。

(1)$@

$@指代目前目标,就是make指令目前建構的那個目标。比如,make foo的 $@ 就指代foo。

a.txt b.txt:

touch $@

a.txt:

touch a.txt

b.txt:

touch b.txt

(2)$&lt;

$&lt; 指代第一個前置條件。比如,規則為 t: p1 p2,那麼$&lt; 就指代p1。

cp $&lt; $@

cp b.txt a.txt

(3)$?

$? 指代比目标更新的所有前置條件,之間以空格分隔。比如,規則為 t: p1 p2,其中 p2 的時間戳比 t 新,$?就指代p2。

(4)$^

$^ 指代所有前置條件,之間以空格分隔。比如,規則為 t: p1 p2,那麼 $^ 就指代 p1 p2 。

(5)$*

$* 指代比對符 % 比對的部分, 比如% 比對 f1.txt 中的f1 ,$* 就表示 f1。

(6)$(@d) 和 $(@f)

$(@d) 和 $(@f) 分别指向 $@ 的目錄名和檔案名。比如,$@是 src/input.c,那麼$(@d) 的值為 src ,$(@f) 的值為 input.c。

(7)$(&lt;d) 和 $(&lt;f)

$(&lt;d) 和 $(&lt;f) 分别指向 $&lt; 的目錄名和檔案名。

dest/%.txt: src/%.txt

@[ -d dest ] || mkdir dest

上面代碼将 src 目錄下的 txt 檔案,拷貝到 dest 目錄下。首先判斷 dest 目錄是否存在,如果不存在就建立,然後,$&lt; 指代前置檔案(src/%.txt), $@ 指代目标檔案(dest/%.txt)。

makefile使用 bash 文法,完成判斷和循環。

ifeq ($(cc),gcc)

libs=$(libs_for_gcc)

else

libs=$(normal_libs)

endif

上面代碼判斷目前編譯器是否 gcc ,然後指定不同的庫檔案。

list = one two three

for i in $(list); do \

echo $$i; \

done

# 等同于

for i in one two three; do \

echo $i; \

上面代碼的運作結果。

one

two

three

makefile 還可以使用函數,格式如下。

$(function arguments)

${function arguments}

(1)shell 函數

shell 函數用來執行 shell 指令

srcfiles := $(shell echo src/{00..99}.txt)

(2)wildcard 函數

wildcard 函數用來在 makefile 中,替換 bash 的通配符。

srcfiles := $(wildcard src/*.txt)

(3)替換函數

替換函數的寫法是:變量名 + 冒号 + 替換規則。

min: $(output:.js=.min.js)

上面代碼的意思是,将變量output中的 .js 全部替換成 .min.js 。

(1)執行多個目标

.phony: cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff

rm program

cleanobj :

cleandiff :

rm *.diff

上面代碼可以調用不同目标,删除不同字尾名的檔案,也可以調用一個目标(cleanall),删除所有指定類型的檔案。

(2)編譯c語言項目

edit : main.o kbd.o command.o display.o

cc -o edit main.o kbd.o command.o display.o

main.o : main.c defs.h

cc -c main.c

kbd.o : kbd.c defs.h command.h

cc -c kbd.c

command.o : command.c defs.h command.h

cc -c command.c

display.o : display.c defs.h

cc -c display.c

clean :

rm edit main.o kbd.o command.o display.o

.phony: edit clean

今天,make指令的介紹就到這裡。下一篇文章我會介紹,如何用 make 來建構 node.js 項目。

----------------------------------------------------------------------------------------------------------------------------

原文釋出時間:2015-02-24

本文來自雲栖合作夥伴“linux中國”