Makefile文件語法


概述

本文將介紹Makefile種注釋、回顯、通配符、變量、循環判斷、函數

注釋

Makefile中只有單行注釋,沒有多行注釋,注釋以 # 開頭。以下Makefile注釋片段節選自Lua的Makefile

# Makefile for installing Lua
# See doc/readme.html for installation and customization instructions.

# == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT =======================

# Your platform. See PLATS for possible values.
PLAT= none

echoing(回顯)

通常,make在執行命令行之前會把要執行的命令行進行輸出。我們稱之為“回顯”,就好像我們輸入命令執行一樣。

@

如果要執行的命令行以字符“@”開始,則make在執行時這個命令就不會被回顯。典型的用法是我們在使用“echo”命令輸出一些信息時。如:

@echo 開始編譯XXX模塊......

make執行時,將輸出“開始編譯XXX模塊......”這個信息。如果在命令行之前沒有字符“@”,那么,make的輸出就是:

echo編譯XXX模塊......

編譯XXX模塊......

-n”或“--just-print”

如果使用make的命令行參數“-n”或“--just-print”,那么make執行時只顯示所要執行的命令,但不會真正的去執行這些命令。只有在這種情況下make才會打印出所有make需要執行的命令,其中也包括了使用“@”字符開始的命令。這個選項對於我們調試Makefile非常有用,使用這個選項我們可以按執行順序打印出Makefile中所有需要執行的命令。

-s”或“--slient”

make參數“-s”或“--slient”則是禁止所有執行命令的顯示,就好像所有的命令行均使用“@”開始一樣。

.SILENT

這個關鍵字的行為很像.PHONY,.PHONY標記的target可以理解成一個無條件執行的動作。被.SILENT標價的target,為完成該target執行的所有command都是沒有回顯的。

.SILENT:clean
.PHONY:clean
clean:
    rm -rf *.o

很顯然這里面控制回顯最靈活的方式就是@,推薦使用“@”來控制命令的回顯。

通配符(Wildcard 

概述

Makefile中使用的通配符與Bash下面的filename wildcards一樣,但似乎Makefile主要使用‘*’, ‘?’ 和 ‘[…]

~字符也有特殊意義,~ 或者 ~/file name 代表home directory

~       展開為  /home/you         你的家目錄
~/bin   展開為  /home/you/bin     你的加目錄下邊的bin目錄

~后面不接/則表示別人的家目錄

~john       展開為  /home/john         john的家目錄
~john/bin   展開為  /home/john/bin     john的家目錄下邊的bin目錄

targets 和 prerequisites中的通配符,有make工具負責展開。commands中的通配符則由shell負責展開。除此之外的其他情況,要想展開通配符則需要顯式調用wildcard 函數。

由於*默認情況下具有通配符的特殊含義,如果需要按照普通字符理解他,則需要使用 \* 轉義。

通配符舉例

在commands中使用通配符

此時通配符展開工作由shell負責

clean:
        rm -f *.o

在prerequisites 中使用通配符

此時通配符展開由make負責,在target中也可以使用通配符,依然是由amke負責展開。

print: *.c
        lpr -p $?
        touch print

這個例子除了表明通配符用法外,同時展示了empty target的用法。empty target是phony target的變體,empty target中的target可以存在,也可以不存在。其特色是在commands最后會touch更新target。

在定義變量時使用通配符

objects = *.o

  變量objects的值就是*.o,但是當你把objects的值用於target、prerequisite時,make工具會展開;用於commands時,shell會展開。表面看上去,這似乎沒啥問題,看如下代碼

objects = *.o

foo : $(objects)
        cc -o foo $(CFLAGS) $(objects)

objects的值就是*.o,單獨看他就是一個名字古怪的文件。但是用在target、prerequisite、commands時,make或shell會自動將其展開為有意義的具體xxx.o文件名,因此上面代碼看上去沒有問題。但是如果,當前目錄下並沒有.o文件,target、prerequisite、commands展開時也找不到.o文件,他就會找*.o這個文件,當然*.o文件也是沒有的,於是會報"cannot figure out how to make *.o."錯誤。

解決這個陷阱的方法是使用wildcard函數

wildcard函數通常和patsubst函數一起使用,這是因為在函數內部通配符也不會自動展開

objects := $(patsubst %.c,%.o,$(wildcard *.c))

foo : $(objects)
        cc -o foo $(objects)

變量

自定義變量與賦值符

Makefile 允許使用等號自定義變量。

var = Hello World
test:
    @echo $(var)

上面代碼中,變量 var等於 Hello World。調用時,變量需要放在 $( ) 之中。

調用Shell變量,需要在美元符號前,再加一個美元符號,這是因為Make命令會對美元符號轉義。

test:
    @echo $$HOME

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

v1 = $(v2)

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

為了解決類似問題,Makefile一共提供了四個賦值運算符 (=、:=、?=、+=),它們的區別請看StackOverflow

VARIABLE = value
# 在執行時擴展,允許遞歸擴展。

VARIABLE := value
# 在定義時擴展。

VARIABLE ?= value
# 只有在該變量為空時才設置值。

VARIABLE += value
# 將值追加到變量的尾端。

內置變量(Implicit Variables)

Make命令提供一系列內置變量,(感覺上和gcc/g++的預定義宏差不多)比如,$(CC) 指向當前使用的編譯器,$(MAKE) 指向當前使用的Make工具。這主要是為了跨平台的兼容性,詳細的內置變量清單見手冊

output:
    $(CC) -o output input.c

自動變量(Automatic Variables)

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

(1)$@

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

a.txt b.txt: 
    touch $@

等同於下面的寫法。

a.txt:
    touch a.txt
b.txt:
    touch b.txt

(2)$<

$< 指代第一個前置條件。比如,規則為 t: p1 p2,那么$< 就指代p1。

a.txt: b.txt c.txt
    cp $< $@ 

等同於下面的寫法。

a.txt: b.txt c.txt
    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)$(<D) 和 $(<F)

$(<D) 和 $(<F) 分別指向 $< 的目錄名和文件名。

所有的自動變量清單,請看手冊。下面是自動變量的一個例子。

dest/%.txt: src/%.txt
    @[ -d dest ] || mkdir dest
    cp $< $@

上面代碼將 src 目錄下的 txt 文件,拷貝到 dest 目錄下。首先判斷 dest 目錄是否存在,如果不存在就新建,然后,$< 指代前置文件(src/%.txt), $@ 指代目標文件(dest/%.txt)。

判斷和循環

循環和判斷應用在commands處,而commands的解析則有shell負責。因此Makefile的循環和判斷其實也就是shell的循環和判斷,其語法與shell完全一樣。

ifeq ($(CC),gcc)
  libs=$(libs_for_gcc)
else
  libs=$(normal_libs)
endif

上面代碼判斷當前編譯器是否 gcc ,然后指定不同的庫文件。

LIST = one two three
all:
    for i in $(LIST); do \
        echo $$i; \
    done

# 等同於

all:
    for i in one two three; do \
        echo $i; \
    done

上面代碼的運行結果。

one
two
three

函數

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

$(function arguments)
# 或者
${function arguments}

Makefile提供了許多內置函數,可供調用。下面是幾個常用的內置函數。

(1)shell 函數

shell 函數用來執行 shell 命令

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

(2)wildcard 函數

wildcard 函數用來在 Makefile 中,替換 Bash 的通配符。

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

(3)subst 函數

subst 函數用來文本替換,格式如下。

$(subst from,to,text)

下面的例子將字符串"feet on the street"替換成"fEEt on the strEEt"。

$(subst ee,EE,feet on the street)

下面是一個稍微復雜的例子。

comma:= ,
empty:=
# space變量用兩個空變量作為標識符,當中是一個空格
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# bar is now `a,b,c'.

(4)patsubst函數

patsubst 函數用於模式匹配的替換,格式如下。

$(patsubst pattern,replacement,text)

下面的例子將文件名"x.c.c bar.c",替換成"x.c.o bar.o"。

$(patsubst %.c,%.o,x.c.c bar.c)

(5)替換后綴名

替換后綴名函數的寫法是:變量名 + 冒號 + 后綴名替換規則。它實際上patsubst函數的一種簡寫形式。

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

上面代碼的意思是,將變量OUTPUT中的后綴名 .js 全部替換成 .min.js 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM