【整理】Makefile编写规则

Makefile

文章目录

  • Makefile
    • Makefile的规则
      • 语法
      • 通配符
      • 文件搜寻
      • 伪目标
      • 多目标
      • 静态变量
      • 自动生成依赖性
    • 书写命令
      • 嵌套执行make
      • 定义命令包
    • 变量使用
      • 变量基础
      • 变量中的变量
      • 变量的高级用法
      • overide指示符
      • 目标变量
    • 条件判断
    • 函数使用
      • 函数调用语法
      • 字符串处理函数
        • subst
        • patsubst
        • strip
        • findstring
        • filter
        • filter-out
        • sort
        • word
        • wordlist
        • words
        • firstword
      • 文件名操作函数
        • dir
        • notdir
        • suffix
        • basename
        • addsuffix
        • addprefix
        • join
      • foreach 函数
      • if 函数
      • shell 函数
      • 控制make的函数
    • 自动化变量

Makefile的规则

语法

target ... : prerequisites ... 
    command
    ...
    ...

target

可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。对于标签这种特性,在后续的“伪目标”章节中会有叙述。

prerequisites
生成该target所依赖的文件和/或target

command
该target要执行的命令(任意的shell命令)

prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。

使用反斜杠\作为换行符

通配符

make支持三个通配符:*?~

通配符代替了一系列文件,*.c表示所有后缀为c的文件。如果文件名中有通配符,如*,可以使用转义字符\,如\*来表示真实的*字符,而不是任意长度的字符串。

clean:
    cat main.c
    rm -f *.o

这是shell命令中的通配符

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

上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的 .c 文件。其中的 $? 是一个自动化变量

object = *.o

上面这个例子,表示了通配符同样可以用在变量中,并不是说 *.o 会展开,objects的值就是*.o。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有 .o 的文件名的集合,那么,你可以这样:

objects := $(wildcard *.o)
  1. 列出确定文件夹中的所有.c文件

objects := $(wildcard *.c)

  1. 列出所有的.o文件

objects := ( p a t s u b s t (patsubst %.c,%.o, (patsubst(wildcard *.c))

Makefile的关键字:wildcardpatsubst

文件搜寻

makefile中有特殊的变量VPATH指定文件路径,可使make自动去寻找文件间的依赖关系。如果没有指定这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,make就会在当前目录找不到的情况下,到指定的目录中去找寻文件了。

例如:

VPATH = src : …/headers

上面的定义指定两个目录,“src”和“…/headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。当前目录永远是最高优先搜索的地方

另一个设置文件搜索路径的方法是使用make的vpath关键字,这是全小写的,这不是变量,是一个make的关键字。可以指定不同的文件在不同的搜索目录中。这个灵活的功能的使用方法有三种:

vpath
为符合模式的文件指定搜索目录

vpath
清除符合模式的文件的搜索目录

vpath
清除所有已被设置好了的文件搜索目录

vpath %.h ../headers

该语句表示,要求make在“…/headers”目录下搜索所有以 .h 结尾的文件

可以使用连续的语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的 ,或是被重复了的,那么,make会按照vpath语句的先后顺序来执行搜索

vpath %.c foo
vpath %   blish
vpath %.c bar

其表示.c结尾的文件,现在foo目录,然后是blish,最后是bar目录

vpath %.c foo:bar
vpath % blish

.c结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录

Makefile中的%标记和系统通配符 * 的区别在于, * 是应用在系统中的,%是应用在这个Makefile文件中的

伪目标

clean:
    rm *.o tmp

我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。

当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。

.PHONY : clean
clean:
    rm *.o temp

伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:

all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
    cc -o prog1 prog1.o utils.o

prog2 : prog2.o
    cc -o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
    cc -o prog3 prog3.o sort.o utils.o

我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。 .PHONY : all 声明了“all”这个目标为“伪目标”。(注:这里的显式“.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一个伪目标,执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)

.PHONY : cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
    rm program

cleanobj :
    rm *.o

cleandiff :
    rm *.diff

“make cleanall”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。

多目标

Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令不是同一个,这可能会给我们带来麻烦,不过好在我们可以使用一个自动化变量 $@,这个变量表示着目前规则中所有的目标的集合

bigoutput littleoutput : text.g
    generate text.g -$(subst output,,$@) > $@

上述规则等价于

bigoutput : text.g
    generate text.g -big > bigoutput
littleoutput : text.g
    generate text.g -little > littleoutput

-$(subst output,,$@) 中的 $表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是替换字符串的意思,$@表示目标的集合,就像一个数组, $@ 依次取出目标,并执于命令。

静态变量

静态模式可以更加容易的定义多目标的规则,可以让我们的规则变的更加有弹性和灵活。

 :  : 
    
    ...

targets 定义了一系列的目标文件,可以有通配符。是目标的集合。

target-pattern是指明了targets的模式,也就是的目标集模式。

prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义。

如果我们的定义成 %.o ,意思是我们的;集合中都是以 .o 结尾的,而如果我们的定义成 %.c ,意思是对所形成的目标集进行二次定义,其计算方法是,取模式中的 % (也就是去掉了 .o 这个结尾),并为其加上 .c 这个结尾,形成的新集合。

objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@

上面的例子中,指明了我们的目标从$object中获取, %.o 表明要所有以 .o 结尾的目标,也就是 foo.o bar.o ,也就是变量 $object 集合的模式,而依赖模式 %.c 则取模式 %.o 的 % ,也就是 foo bar ,并为其加下 .c 的后缀,于是,我们的依赖目标就是 foo.c bar.c 。而命令中的 $<$@ 则是自动化变量, $< 表示第一个依赖文件, $@ 表示目标集(也就是“foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:

foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o
files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
    emacs -f batch-byte-compile $<

$(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$files”集,只要其中模式为“%.o”的内容。

自动生成依赖性

在大型的工程中,需要知道一些c文件中包含了那些头文件,并且在加入或者修改头文件时,也需要小心的修改makefile,这样很没有维护性。为了避免这个情况,可以使用C/C++编译器的功能,使用-M的选项,自动找到源文件中包含的头文件,并形成依赖关系。GUN使用-M参数会把一些标准库的头文件包含进来,使用-MM则不会。

编译器为每一个源文件自动生成的依赖关系放到一个文件中,为每一个name.c的文件都生成一个name.d的Makefile文件,.d文件就存放在对应.c文件的依赖关系。
可以写出.c文件和.d文件的依赖关系,并让make自动更新或生成.d文件,并把其包含在主makefile中,可自动化的生成每个文件的依赖关系。

一个模式规则来生成.d文件

$@–目标文件,$^–所有的依赖文件,$<–第一个依赖文件。

%.d: %.c
    @set -e; rm -f $@; \
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$

这个规则的意思是,所有的.d文件依赖于.crm -f $@的意思是删除所有的目标,也就是.d文件。第二行的意思,为每个依赖文件$<,也就是.c文件生成依赖文件,$@表示模式%.d文件,如果有一个C文件是name.c,那么%就是name,$$$$是一个随机编号,第二行生成的文件有可能是name.d.12345,第三行使用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。

将这些规则放到主Makefile中,可以使用include来引用别的Makefile文件

sources = foo.c bar.c
include $(sources: .c=.d)

上述语句中的 $(sources:.c=.d) 中的 .c=.d 的意思是做一个替换,把变量 $(sources) 所有 .c 的字串都替换成 .d ,关于这个“替换”的内容,在后面我会有更为详细的讲述。当然,你得注意次序,因为include是按次序来载入文件,最先载入的 .d文件中的目标会成为默认目标。

书写命令

### 显示命令

makefile中#是注释符

make会把其要执行的命令行在命令执行前输出到屏幕上。当使用@字符在命令行前,那么这个命令将不被make显示出来。

@echo 正在编译XXX模块......

当make执行时,会输出“正在编译xxx模块”,但是不会输出命令,如果没有@,make将会输出

echo 正在编译XXX模块......
正在编译XXX模块......

如果make执行时,带入make参数-n或者--just-print,那么只是显示命令,但是不会执行命令,这个命令有利于调试makefile,make参数-s或者--silent--quiet则是全面禁止命令的显示。

### 命令执行

如果想让上一条命令的结果应用在下一条命令时,应该使用分号分隔这两条命令。,并且这两条命令需要放在一行上。

exec:
    cd /home/leo; pwd

### 命令出错

每当命令运行完成后,make会检查每个命令的返回码,如果命令返回成功,make会执行下一条命令,当规则中所有的命令都返回成功后,这个规则算是成功完成了。如果某个命令出错了,make会终止当前的规则,这有可能或终止所有规则的执行。

为了忽略命令出错,可以在Makefile的命令行前加一个-(TAB之后),标记为不管命令是否出错都认为是成功的。

在make后加上-i或者是-ignore-errors,这是一种全局的方法,Makefile中所有命令都会忽略错误。

-k或者-keep-going表示如果某个命令出错了,那么就终止该命令的执行,但继续执行其他规则。

clean:
    -rm -f *.o

嵌套执行make

在大的工程中,把不同模块或者是不同功能的源文件放在不同的目录中,可以在每个目录中都书写一个该目录的Makefile,有利于Makefile更加简洁,不至于把所有东西都写在一个Makefile中,对模块编译和分段编译非常有好处。

我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:

subsystem:
    cd subdir && $(MAKE)

等价于

subsystem:
    $(MAKE) -C subdir

总控的Makefile可以传递到下级的Makefile中,但是不会覆盖下层Makefile中所定义的变量,除非指定了-e参数

如果要传递变量到下级的Makefile中,可以使用这样的声明:

export ;

如果不想要某些变量传递到下级的Makefile,可以这样声明

unexport ;

如果需要传递所有的变量,只需要使用export就行,后面什么也不用跟,表示传递所有的变量。

一个是SHELL一个是MAKEFLAGS,这俩个变量不管是否export,其总是要传递到下层的Makefile中,特别是MAKEFLAGS变量。

如果不想传,可以这样:

subsystem:
    cd subdir && $(MAKE) MAKEFLAGS=

在嵌套执行的过程中,有一些比较有用的参数,-w或者--print-directory可以在make的过程中输出当前工作的目录。

进入目录:

make: Entering directory `/home/hchen/gnu/make'.

离开目录:

make: Leaving directory `/home/hchen/gnu/make'

定义命令包

如果Makefile中出现一些相同的命令序列,可以为这些命令序列定义一个变量。定义命令序列的语法以define开始,以endef结束

define run-yacc
yacc $(firstword &^)
mv y.tab.c &@
endef

run-yacc是命令包的名称,不能和Makefile中的变量重名。
第一个命令是运行yacc程序,因为yacc程序总是生成y.tab.c的文件,所以第二行的命令就是把这个文件改名字。

foo.c : foo.y
    $(run-yacc)

命令包“run-yacc”中的 $^ 就是 foo.y , $@ 就是 foo.c (有关这种以 $ 开头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。

变量使用

  • 变量可以包含字符、数字、下划线,但是不应该包含:#=或者空字符串。

  • 变量大小写敏感

  • $<$@是自动化变量

变量基础

  • 使用时,需要在变量名前加$符号,最好使用小括号()或者是{}把变量包括起来。

  • 使用真实的$字符,可以使用$$来表示。

  • 变量可以使用在文件中的目标依赖命令和新的变量中。

objects = program.o foo.o utils.o
program : $(objects)
    cc -o program $(objects)

$(objects) : defs.h

变量中的变量

  • 在定义变量的值时,可以使用其他的变量来构造变量的值,在Makefile中有两种方式来用变量来定义变量的值。
foo = $(bar)
bar = $(ugh)
ugh = Huh

all:
    echo $(foo)
  • Makefile中的变量是可以把真实性推到后面来定义

  • = : 延时赋值,该变量只用在调用的时候才被赋值

  • := : 直接赋值,与延时赋值相反,使用直接赋值的话,变量的值定义时就已经确定

  • ?= : 若变量的值为空,则进行赋值,通常用于设置默认值

  • += : 追加赋值,可以往变量后增加新的内容

VAR_A = FILEA
VAR_B = $(VAR_A)
VAR_C := $(VAR_A)
VAR_A += FILEB
VAR_D ?= FILED
.PHONY:check
check:
    @echo "VAR_A:"$(VAR_A)
    @echo "VAR_B:"$(VAR_B)
    @echo "VAR_C:"$(VAR_C)
    @echo "VAR_D:"$(VAR_D)

变量的高级用法

  • 变量值替换
    • 可以替换变量中共有的部分:$(var:a=b) 或者 ${var:a=b}。把var变量中以a字串结尾的a换成b。
foo := a.o b.o c.o 
bar := $(foo:.o=.c)

overide指示符

通常在执行make时,如果通过命令行定义了一个变量,那么它将替代在Makefile中出现的同名变量的定义。就是说,对于一个在Makefile中使用常规方式(使用“=”、“:=”或者“define”)定义的变量,我们可以在执行make时通过命令行方式重新指定这个变量的值,命令行指定的值将替代出现在Makefile中此变量的值。如果不希望命令行指定的变量值替代在Makefile中的变量定义,那么我们需要在Makefile中使用指示符“override”来对这个变量进行声明

目标变量

  • 设置局部变量:在Makefile中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量;同样可以为某个目标设置局部变量,可以和"全局变量"同名。但是作用范围只在这条规则和其连带规则中,所以其值只在作用范围内有效,而不会影响规则链外的全局变量。
 : ;
 : overide 

事例:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
    $(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o : prog.c
    $(CC) $(CFLAGS) prog.c

foo.o : foo.c
    $(CC) $(CFLAGS) foo.c

bar.o : bar.c
    $(CC) $(CFLAGS) bar.c

$(CFLAGS) 的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则), $(CFLAGS) 的值都是 -g

条件判断

ifeqelseendif 三个关键字

  • ifeq: 表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起

  • endif: 表示一个条件语句的结束

事例1

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
    $(CC) -o foo $(objects) $(libs_for_gcc)
else
    $(CC) -o foo $(objects) $(normal_libs)
endif

事例2

libs_for_gcc = -lgnu
normal_libs =

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

foo: $(objects)
    $(CC) -o foo $(objects) $(libs)

关键字

  1. ifeq
ifeq (, )
ifeq '' ''
ifeq "" ""
ifeq "" ''
ifeq '' ""
  1. ifneq
ifneq (, )
ifneq '' ''
ifneq "" ""
ifneq "" ''
ifneq '' ""
  1. ifdef

事例一:

bar =
foo = $(bar)
ifdef foo
    frobozz = yes
else
    frobozz = no
endif

事例二:

foo =
ifdef foo
    frobozz = yes
else
    frobozz = no
endif

第一个例子中, $(frobozz) 值是 yes ,第二个则是 no。

  1. ifndef

函数使用

  • 函数的返回值可以当作变量来使用

函数调用语法

函数调用像变量的使用,是以$来标识的

$( ) 或者 ${ }

是函数名,make支持的函数不多,是函数的参数,参数间以逗号来分隔,而函数名和参数之间以空格分隔。函数调用以$开头,以圆括号或者花括号把函数名和参数括起。例如:$(subst a,b,$(x))

实例:

comma:= ,
empty:= 
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))

在这个示例中, $(comma) 的值是一个逗号。 $(space) 使用了 $(empty) 定义了一个空格, $(foo) 的值是 a b c$(bar) 的定义用,调用了函数 subst ,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把 $(foo)中的空格替换成逗号,所以 $(bar) 的值是 a,b,c

字符串处理函数

subst
  • $(subst , , )
  • 名称:字符串替换函数
  • 功能:把字符串text中的from替换成to
  • 返回:函数返回被替换过后的字符串
  • 示例:$(subst ee,EE,feet on the street)
  • 结果:fEEt on the strEEt
patsubst
  • $(patsubst ,,)
  • 名称:模式字符串替换函数
  • 功能:查找 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式 ,如果匹配的话,则以 替换。这里, 可以包括通配符 % ,表示任意长度的字串。如果 中也包含 % ,那么, 中的这个 % 将是 中的那个 % 所代表的字串。(可以用 \来转义,以 \%来表示真实含义的 % 字符)
  • 返回:函数返回被替换过后的字符串
  • 示例:$(patsubst %.c,%.o,x.c.c bar.c)
  • 类似于:$(var:=;) 相当于 $(patsubst ,,$(var))
strip
  • $(strip )
  • 名称:去空格函数
  • 功能:去掉字串中开头和结尾的空字符
findstring
  • $(findstring ,)
  • 查找字符串函数
  • 功能:在字串中查找字串
  • 返回:若找到,返回,否则返回空字符串
  • 示例:$(findstring a,a b c)
filter
  • $(filter ,)
  • 名称:过滤函数
  • 功能:以模式过滤字符串中的单词,保留符合模式的单词。可以有多种模式。
  • 返回:返回符合模式的字串
  • 示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
    cc $(filter %.c %.s,$(sources)) -o foo

$(filter %.c %.s,$(sources))返回的值是foo.c bar.c baz.s

filter-out
  • filter相反
sort
  • $(sort )
  • 名称:排序函数
  • 功能:给字符串中的单词排序(升序),sort会去掉list中相同的单词
  • 返回:返回排序后的字符串
  • 示例:$(sort foo bar lose)返回bar foo lose
word
  • $(word , )
  • 名称:取单词函数
  • 功能:取字符串中第个单词(从1开始)
  • 示例:$(word 2, foo bar baz) 返回值是bar
wordlist
  • $(wordlist ,,)
  • 名称:取单词串
  • 功能:从字符串 中取从 开始到 的单词串
  • 示例:$(wordlist 2, 3, foo bar baz) 返回值是 bar baz
words
  • $(words )
  • 名称:单词个数统计函数
  • 功能:统计中字符串中的单词个数
  • 返回:返回中的单词数
  • 示例:$(words, foo bar baz)返回值是3
firstword
  • $(firstword )
  • 返回出字符串中第一个单词

文件名操作函数

dir
  • $(dir )
  • 名称:取目录名称–dir
  • 功能:从文件名序列 中取出目录部分。目录部分是指最后一个反斜杠( / )之前的部分。如果没有反斜杠,那么返回 ./
  • 示例:$(dir src/foo.c hacks) 返回值是 src/ ./
notdir
  • 与函数dir相反,返回文件名序列的非目录部分
  • 示例:$(notdir src/foo.c hacks) 返回值是 foo.c hacks
suffix
  • $(suffix )
  • 取后缀函数,取出各个文件名的后缀
basename
  • $(basename
  • 取前缀函数,从文件名序列 中取出各个文件名的前缀部分
  • 示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是src/foo src-1.0/bar hacks
addsuffix
  • $(addsuffix ,)
  • 加后缀函数
  • 功能:把后缀 加到 中的每个单词后面
  • 示例:$(addsuffix .c,foo bar) 返回值是 foo.c bar.c
addprefix
  • $(addprefix ,)
  • 名称:加前缀函数
  • 功能:把前缀 加到 中的每个单词后面
  • 示例:$(addprefix src/,foo bar) 返回值是 src/foo src/bar
join
  • $(join ,)
  • 名称:连接函数
  • 功能:把 中的单词对应地加到 的单词后面。如果 的单词个数要比 的多,那么, 中的多出来的单词将保持原样。如果 的单词个数要比 多,那么, 多出来的单词将被复制到
  • 示例:$(join aaa bbb , 111 222 333) 返回值是 aaa111 bbb222 333

foreach 函数

  • $(foreach ,,)
  • 把参数 中的单词逐一取出放到参数 所指定的变量中,然后再执行 所包含的表达式。每一次 会返回一个字符串,循环过程中, 的所返回的每个字符串会以空格分隔,最后当整个循环结束时, 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。
  • 示例:
names := a b c d

files := $(foreach n,$(names),$(n).o)

$(name)中的单词会被挨个取出,并存到变量 n 中, $(n).o 每次根据 $(n) 计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以, $(files) 的值是 a.o b.o c.o d.o

if 函数

  • $(if ,) 或者 $(if ,,)

shell 函数

  • shell函数把执行操作系统命令后的输出作为函数返回,可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量
contents := $(shell cat foo)
files := $(shell echo *.c)

控制make的函数

  • $(error )
  • 产生一个致命的错误, 是错误信息。注意,error函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。
ERR = $(error found an error!)

.PHONY: err

err: $(ERR)
  • $(warning ) 这个函数很像error函数,只是它并不会让make退出,只是输出一段警告信息,而make继续执行。

自动化变量

自动化变量:这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。

  • $@ : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合。
  • $% : 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 foo.a(bar.o) ,那么, $% 就是 bar.o , $@ 就是 foo.a 。如果目标不是函数库文件(Unix下是 .a ,Windows下是 .lib ),那么,其值为空。
  • $< : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
  • $? : 所有比目标新的依赖目标的集合。以空格分隔
  • $^ : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份
  • $+ : 这个变量很像$^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
  • $* : 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b,并且目标的模式是 a.%.b ,那么, $* 的值就是 dir/a.foo 。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么 $* 也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么 $* 就是除了后缀的那一部分。例如:如果目标是 foo.c ,因为 .c 是make所能识别的后缀名,所以, $* 的值就是 foo 。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 $* ,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么 $* 就是空值。

你可能感兴趣的:(C++,Makefile,c++)