Makefile文件语法

Posted kelamoyujuzhen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)[email protected]

[email protected]指代当前目标,就是Make命令当前构建的那个目标。比如,make foo[email protected] 就指代foo。

a.txt b.txt: 
    touch [email protected]

等同于下面的写法。

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

(2)$<

$< 指代第一个前置条件。比如,规则为 t: p1 p2,那么$< 就指代p1。

a.txt: b.txt c.txt
    cp $< [email protected] 

等同于下面的写法。

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) 分别指向 [email protected] 的目录名和文件名。

比如,[email protected]是 src/input.c,那么$(@D) 的值为 src ,$(@F) 的值为 input.c。

(7)$(<D) 和 $(<F)

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

所有的自动变量清单,请看手册。下面是自动变量的一个例子。

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

上面代码将 src 目录下的 txt 文件,拷贝到 dest 目录下。首先判断 dest 目录是否存在,如果不存在就新建,然后,$< 指代前置文件(src/%.txt), [email protected] 指代目标文件(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 。

以上是关于Makefile文件语法的主要内容,如果未能解决你的问题,请参考以下文章

Makefile

erlang.mk和makefile语法剖析

makefile中的Cflags用法

Linux开发基础知识Makefile语法

Linux开发基础知识Makefile语法

makefile学习