makefile

程序的编译和链接

编译的定义

无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是.obj文件,UNIX下是.o文件,即ObjectFile,这个动作叫做编译(compile)。

链接的定义

然后再把大量的ObjectFile合成执行文件,这个动作叫作链接(link)。

编译成功的条件

编译时,编译器需要的是语法的正确,函数与变量的声明的正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。

链接的作用

链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。

库文件

由于源文件太多,编译生成的中间目标文件太多,要给中间目标文件打个包,在Windows下这种包叫“库文件”(LibraryFile),也就是.lib文件,在UNIX下,是ArchiveFile,也就是.a文件。

makefile介绍

make命令执行时,需要一个Makefile文件,以告诉make命令需要怎么样的去编译和链接程序。

1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。

2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。

3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

makefile规则

target...:prerequisites...

command

target

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

目标文件(target)包含:执行文件edit和中间目标文件(*.o)。

prerequisites

要生成那个target所需要的文件或是目标。依赖文件(prerequisites)就是冒号后面的那些.c文件和.h文件。

每一个.o文件都有一组依赖文件,而这些.o文件又是执行文件又是edit的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的。

command

make需要执行的命令。(任意的Shell命令)

make如何工作?

在默认的方式下,也就是我们只输入make命令。那么,

make会在当前目录下找名字叫“Makefile”或“makefile”的文件。

如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。

如果edit文件不存在,或是edit所依赖的后面的.o文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。

如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。

当然,你的C文件和H文件是存在的啦,于是make会生成.o文件,然后再用.o文件声明make的执行文件edit了。

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性即,如果在找了依赖关系之后,冒号后面的文件还是不在,则停止工作。

makefile中使用变量

makefile中以**“$([变量名])”**的方式来使用这个变量

objects=  main.o kbd.o command.o display.o\

insert.osearch.o files.o utils.o

edit: $(objects)

cc–o edit$(objects)

main.o :   main.cdefs.h

cc –c main.c

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

cc–c kbd.c

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

cc–c command.c

display.o :display.c defs.hbuffer.h

cc-c display.c

insert.o :insert.c defs.hbuffer.h

cc–c insert.c

search.o :search.c defs.hbuffer.h

cc–c search.c

files.o :files.c defs.hbuffer.hcommand.h

cc–c files.c

utils.o :utils.c defs.h

cc– c utils.c

clean:

rmedit$(objects)

自动推导

makefile可以自动推导文件以及文件依赖关系后面的命令

如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且cc-cwhatever.c也会被推导出来

这种方法,也就是make的“隐晦规则”。“.PHONY”表示,clean是个伪目标文件。

objects = main.okbd.o command.o display.o \

insert.osearch.o files.o utils.o

edit : $(objects)

cc-o edit $(objects)

main.o : defs.h

kbd.o : defs.hcommand.h

command.o : defs.hcommand.h

display.o : defs.hbuffer.h

insert.o : defs.hbuffer.h

search.o : defs.hbuffer.h

files.o : defs.hbuffer.h command.h

utils.o : defs.h

.PHONY : clean

clean :

rmedit $(objects)

简化[.o]和[.h]的依赖

objects = main.okbd.o command.o display.o \

insert.osearch.o files.o utils.o

edit : $(objects)

cc -o edit $(objects)

$(objects) : defs.h

kbd.o command.ofiles.o : command.h

display.o insert.osearch.o files.o : buffer.h

.PHONY : clean

clean :

rm edit $(objects)

清空目标文件

每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。

.PHONY: clean

clean:

-rm edit $(objects)

.PHONY意思表示clean是一个“伪目标”。

而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标。

makefile总述

makefile里有什么?

显式规则、隐晦规则、变量定义、文件指示和注释。

显式规则

如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

隐晦规则

由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

变量的定义

在Makefile中我们要定义一系列的变量,变量一般都是字符串,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

文件指示

其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;

另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;

还有就是定义一个多行的命令。

注释

Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,如果要你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。

文件起始

在Makefile中的命令,必须要以[Tab]键开始。

makefile文件名

默认的情况下,make命令会在当前目录下按顺序找寻文件名为**“GNUmakefile”、“makefile”、“Makefile”**的文件,找到了解释这个文件。

最好使用“Makefile”这个文件名,因为这个文件名第一个字符为大写醒目。

好不要用“GNUmakefile”,这个文件是GNU的make识别的。

有另外一些make只对全小写的“makefile”文件名敏感

大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

如果要指定特定的Makefile,你可以使用make的“-f”和“–file”参数,如:make –f Make.Linux或make –file Make.AIX。

引用其它makefile

在Makefile使用include关键字可以把别的Makefile包含进来,include的语法是

include <filename>

filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)

在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和可以用一个或多个空格隔开。

如有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:

include foo.make *.mk$(bar)

#等价于:

include foo.make a.mkb.mk c.mk e.mk f.mk

include寻找文件的过程

make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。

如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

1.如果make执行时,有“-I”或“–include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。

2.如果目录/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

3.如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。

忽略无法读取文件

如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:

-include <filename>

和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。

环境变量MAKEFILES

如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。

从这个环境变中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。

建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。

make工作流程

GNU的make工作时的执行步骤入下:

1.读入所有的Makefile。

2.读入被include的其它Makefile。

3.初始化文件中的变量。

4.推导隐晦规则,并分析所有规则。

5.为所有的目标文件创建依赖关系链。

6.根据依赖关系,决定哪些目标要重新生成。

7.执行生成命令。

makefile书写规则

在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。

规则举例

foo.o:      foo.c defs.h #foo模块

cc–c –g foo.c

foo.o是我们的目标,foo.c和defs.h是目标所依赖的源文件;命令“cc –c –g foo.c”说明如何生成foo.o文件。

规则的语法

targets:prerequisites

command

...

#或是这样:

targets:prerequisites;command

command

...

targets是文件名,以空格分开,可以使用通配符。可以是一个或多个文件。

command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以[Tab键]开头,如果和prerequisites在一行,那么可以用分号做为分隔。

如果命令太长,你可以使用反斜框(‘\’)作为换行符。make对一行上有多少个字符没有限制。

一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。

通配符

make支持三各通配符:“*”,“?”和“[…]”。

波浪号(“~”)

字符在文件名中也有比较特殊的用途。如果是“~/test”,这就表示当前用户的$HOME目录下的test目录。而“~hchen/test”则表示用户hchen的宿主目录下的test目录。

而在Windows或是MS-DOS下,用户没有宿主目录,那么波浪号所指的目录则根据环境变量“HOME”而定。

“*”

通配符代替了你一系列的文件,如“*.c”表示所以后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:“*”,那么可以用转义字符“\”,如“\*”来表示真实的“*”字符,而不是任意长度的字符串。

如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的文件名的集合,那么,你可以这样:

objects:=$(wildcard *.o)

这种用法由关键字“wildcard”指出

文件搜索

VPATH的作用

make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。

VPATH=src:…/headers

vpath关键字

设置文件搜索路径的方法

1.vpath <pattern> <directories>为符合模式<pattern>的文件指定搜索目录<directories>

2.vpath <pattern>清除符合模式<pattern>的文件的搜索目录。

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

vapth包含%

vapth使用方法中的<pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符

  • 例如,“%.h”表示所有以“.h”结尾的文件。

<pattern>指定了要搜索的文件集。

<directories>则指定了的文件集的搜索的目录。

vpath %.h ../headers
#要求make在“../headers”目录下搜索所有以“.h”结尾的文件。

指定不同搜索策略

如果连续的vpath语句中出现了相同的<pattern>,或是被重复了的<pattern>,那么,make会按照vpath语句的先后顺序来执行搜索。

vpath %.c foo

vpath % blish

vpath %.c bar

#其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。

vpath %.c foo:bar

vpath % blish

#而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。

伪目标

定义

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

.PHONY

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

.PHONY:clean

clean:

rm*.otemp

伪目标作为“默认目标”

只要将其放在第一个。如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:

all:prog1 prog2 prog3
#“all”的伪目标,其依赖于其它三个目标。

.PHONY:all
#声明了“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

由于伪目标的特性是,总是被执行的,所以其依赖的那三个目标就总是不如“all”这个目标新。所以,其它三个目标的规则总是会被编译。也就达到了我们一口气生成多个目标的目的。

伪目标变为依赖

.PHONY:cleanall cleanobj cleandiff

cleanall:cleanobj cleandiff

rmprogram

cleanobj:

rm*.o

cleandiff:

rm*.diff

makecleanmake cleanall将清除所有要被清除的文件。

make cleanobj清楚目标文件

make cleandiff清除.diff文件

多目标

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,output为参数。

$@表示目标的集合,就像一个数组,$@依次取出目标,并执行命令。

静态模式

语法

<targets...>:<target-pattern>:<prereq-patterns...>

<commands>

...

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

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

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

  • 如果我们的<prereq-parrterns>定义成“%.c”,意思是对<target-parrtern>所形成的目标集进行二次定义,其计算方法是,取<target-parrtern>模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。

举例:

objects=foo.o bar.o

all:$(objects)

$(objects):%.o:%.c

$(CC)–c $(CFLAGS) $< -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

上面的例子中,指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object集合的模式.

而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。

而命令中的“$<”和“$@”则是自动化变量,“​$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(foo.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函数,过滤“$filter”集,只要其中模式为“%.o”的内容。

自动生成依赖性

GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中。如为每一个“name.c”的文件都生成一个“name.d”的Makefile文件,[.d]文件中就存放对应[.c]文件的依赖关系。

自动生成依赖文件

我们给出了一个模式规则来产生[.d]文件:

%.d: %.c

@set -e;rm -f $@;\

$(CC) -M $(CPPFLAGS) $<> $@.;\
#为每个目标文件自动生成依赖文件

sed's,$*\.o[:]*,\1.o $@:,g'< $@. > $@;\
#用sed命令做替换,因为生成的文件有可能是“name.d.12345”

rm -f $@.
#删除临时文件

这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖,即把依赖关系:

main.o:main.c defs.h

转成:

main.o main.d:main.c defs.h

依赖文件

sources = foo.c bar.c

include $(sources:.c=.d)

上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d]

makefile书写命令

make的命令默认是被“/bin/sh”——UNIX的标准Shell解释执行的。

make会一按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头,除非,命令是紧跟在依赖规则后面的分号后的。

在命令行之间中的空格或是空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make会认为其是一个空命令。

显示命令

当我们用“@”字符在命令行前,那么,这个命令将不被make显示出来,如

@echo 正在编译XXX模块......
#输出:
#正在编译XXX模块......

如果make执行时,带入make参数“-n”或“–just-print”,那么其只是显示命令,但不会执行命令。

make参数“-s”或“–slient”则是全面禁止命令的显示。

命令执行

如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。

cd frameworks/;pwd
#输出结果如下
#/home/liaoxiaoyin/project/plat-aml-android-4-20190613/frameworks

命令出错

我们可以在Makefile的命令行前加一个减号“-”(在Tab键之后),标记为不管命令出不出错都认为是成功的

clean:

-rm-f *.o

–ignore-errors

全局的办法是,给make加上“-i”或是“–ignore-errors”参数,那么,Makefile中所有命令都会忽略错误。而如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。

–keep-going

make的参数的是“-k”或是“–keep-going”,这个参数的意思是,如果某规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则。

–print-directory

-w”或是“–print-directory”会在make的过程中输出一些信息,让你看到目前的工作目录。

嵌套执行makefile

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

先进入“subdir”目录,然后执行make命令。

subsystem:

cd subdir &&$(MAKE)

#其等价于:

subsystem:

$(MAKE) -C subdir

变量传递

总控Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了“-e”参数。

要传递变量到下级Makefile中:

export <variable...>

不想让变量传递到下级Makefile中:

unexport <variable...>

举例

export variable=value

#其等价于:

variable=value

export variable

SHELL MAKEFLAGS

有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中。

MAKEFILES变量,由“总控Makefile”中的make参数或是在上层Makefile中定义了这个变量。这是一个系统级的环境变量。

不向下传递的参数

make命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”。

如果你不想往下层传递参数,那么,你可以这样来:

subsystem:

cd subdir && $(MAKE)MAKEFLAGS=

定义命令包

定义这种命令序列的语法以“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”

变量

变量基础

变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。

如果要使用真实的“$”字符,那么你需要用“$$”来表示。

变量中的变量

变量由后面变量定义

foo=$(bar)

bar=$(ugh)

ugh=Huh?

all:

echo $(foo)

“make all”将会打出变量$(foo)的值是“Huh?”

但这种形式也有不好的地方,那就是递归定义,如

CFLAGS=$(CFLAGS) -O

:=

为了避免上面的这种方法,使用的是“:=”操作符。前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。

x:=foo

y:=$(x)bar

x:=later

#其等价于:

y:=foobar

x:=later

定义空格

nullstring:=

space:=$(nullstring) #endof the line

nullstring是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。

因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止。

dir:=/foo/bar #directoryto put the frobs in

dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我们这样使用这样变量来指定别的目录——“$(dir)/file”则出错。

?=

FOO ?=bar

如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做。

变量值的高级用法

变量值替换

其格式是$(var:a=b)或是${var:a=b},其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。

foo:=a.o b.o c.o

bar:=$(foo:.o=.c)

我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。

静态模式变量替换

foo:=a.o b.o c.o

bar:=$(foo:%.o=%.c)

这依赖于被替换字串中的有相同的模式,模式中必须包含一个“%”字符.

变量值作为变量

x=y

y=z

a:=$($(x))

在变量定义中使用变量

x=variable1

variable2:=Hello

y=$(subst 1,2,$(x))

z=y

a:= $($($(z)))

“$($($(z)))”扩展为“$($(y))”;

而其再次被扩展为“ ( ( ((subst 1,2,$(x)))”。​$(x)的值是“variable1”,subst函数把“variable1”中的所有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”;

再取其值,所以,最终,​$(a)的值就是$(variable2)的值“Hello”。

多变量组成变量名,取值

这里的“$a_​$b”组成了“first_second”,于是,$(all)的值就是“Hello”。

first_second=Hello

a=first

b=second

all=$($a_$b)

变量的值再当成变量可以用在操作符的左边

dir =foo

$(dir)_sources:=$(wildcard$(dir)/*.c)

define $(dir)_print

lpr $($(dir)_sources)

endef

追加变量

可以使用“+=”操作符给变量追加值

objects=main.o foo.o bar.outils.o

objects+=another.o

我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”

如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符。

如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符,如

variable :=value

variable +=more

#等价于:

variable:=value

variable:=$(variable)more

override

如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符.

override <variable>=<value>

override <variable>:=<value>

override <variable>+= <moretext>

对于多行的变量定义,我们用define指示符,在define指示符前,也同样可以使用override指示符,如

override define foo

bar

endef

多行变量

使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令。

define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束。

变量的值可以包含函数、命令、文字,或是其它变量。

define two-lines

echo foo

echo $(bar)

endef

目标变量

定义

我们同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。

语法

<target...> :<variable-assignment>

<target...> :override<variable-assignment>

<variable-assignment>可以是前面讲过的各种赋值表达式,如“=”、“:=”、“+=”或是“?=”。

第二个语法是针对于make命令行带入的变量,或是系统环境变量。

举例:

在这个示例中,不管全局的$(CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是“-g”

prog:CFLAGS = -g

prog:prog.o foo.o bar.o

$(CC) $(CFLAGS) prog.ofoo.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

模式变量

make的“模式”一般是至少含有一个“%”的。

%.o:CFLAGS =-O

<pattern...>:<variable-assignment>

<pattern...>:override<variable-assignment>

override同样是针对于系统环境传入的变量,或是make命令行指定的变量。

环境变量

环境变量载入

make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中。

环境变量的覆盖

但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入(-e参数),那么系统的环境变量的值将被覆盖。

CFLAGS取值-环境变量?Makefile?

如果Makefile中定义了CFLAGS,那么则会使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值。

条件判断

三个关键字:ifeq、else和endif。ifeq的意思表示条件语句的开始。else表示条件表达式为假的情况。endif表示一个条件语句的结束,任何一个条件表达式都应该以endif结束。

语法

<conditional-directive>这一行上,多余的空格是被允许的,但是不能以[Tab]键做为开始(不然就被认为是命令)。而注释符“#”同样也是安全的。“else”和“endif”也一样,只要不是以[Tab]键开始就行了。

make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,你最好不要把自动化变量(如“$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。

ifeq <conditional-directive>

<text-if-true>

endif

以及:

ifeq <conditional-directive>

<text-if-true>

else

<text-if-false>

endif

ifeq

ifeq(<arg1>,<arg2>)

ifeq'<arg1>''<arg2>'

ifeq"<arg1>""<arg2>"

ifeq"<arg1>"'<arg2>'

ifeq'<arg1>'"<arg2>"

比较参数“arg1”和“arg2”的值是否相同。当然,参数中我们还可以使用make的函数。如

ifeq($(strip $(foo)),)

<text-if-empty>

endif

这个示例中使用了“strip”函数,如果这个函数的返回值是空(Empty),那么<text-if-empty>就生效。

ifneq

其比较参数“arg1”和“arg2”的值是否相同,如果不同,则为真。

ifneq(<arg1>,<arg2>)

ifneq'<arg1>''<arg2>'

ifneq"<arg1>""<arg2>"

ifneq"<arg1>"'<arg2>'

ifneq'<arg1>'"<arg2>"

ifdef

ifdef <variable-name>

如果变量<variable-name>的值非空,那到表达式为真。否则,表达式为假。当然,<variable-name>同样可以是一个函数的返回值。

ifndef

ifndef <variable-name>

如果变量<variable-name>的值为空,那到表达式为真。否则,表达式为假。

函数

函数调用语法

函数调用,很像变量的使用,也是以“$”来标识的,其语法如下

$(<function><arguments>)

或是

${<function><arguments>}

<function>就是函数名,make支持的函数不多。

<arguments>是函数的参数,参数间以逗号“,”分隔。

而函数名和参数之间以“空格”分隔。

函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。

举例:

comma:=,

empty:=

space:=$(empty)$(empty)

foo:=a b c

bar:=$(subst $(space),$(comma),$(foo))

$(comma)的值是一个逗号。​$(space)使用了​$(empty)定义了一个空格,$(foo)的值是“abc”;

$(bar)的定义用,调用了函数“subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。

这个函数也就是把$(foo)中的空格替换成逗号,所以$(bar)的值是“a,b,c”。

字符串处理函数

$(subs <from>,<to>,<text>)

名称:字符串替换函数——subst。

功能:把字串<text>中的<from>字符串替换成<to>

返回:函数返回被替换过后的字符串。

$(patsubst <pattern>,<replacement>,<text>)

名称:模式字符串替换函数——patsubst。

功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。

<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。

可以用“\”来转义,以“%”来表示真实含义的“%”字符

返回:函数返回被替换过后的字符串。

示例:

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

把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”

$(var:<pattern>=<replacement>)

#相当于

$(patsubst <pattern>,<replacement>,$(var))



$(var:<suffix>=<replacement>)

#相当于

$(patsubst %<suffix>,%<replacement>,$(var))

$(strip <string>)

名称:去空格函数——strip。

功能:去掉<string>字串中开头和结尾的空字符。

返回:返回被去掉空格的字符串值。

$(findstring<find>,<in>)

名称:查找字符串函数——findstring。

功能:在字串<in>中查找<find>字串。

返回:如果找到,那么返回<find>,否则返回空字符串。

$(filter <pattern…>,<text>)

名称:过滤函数——filter。

功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。

返回:返回符合模式<pattern>的字串。

示例:

sources:=foo.c bar.c baz.sugh.h

foo:$(sources)

cc $(filter %.c %.s,$(sources))-o foo

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

$(filter-out <pattern…>,<text>)

名称:反过滤函数——filter-out。

功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可以有多个模式。

返回:返回不符合模式<pattern>的字串。

示例:

objects=main1.o foo.o main2.o bar.o

mains=main1.o main2.o

$(filter-out $(mains),$(objects))

返回值是“foo.o bar.o”。

$(sort <list>)

名称:排序函数——sort。

功能:给字符串<list>中的单词排序(升序)。

返回:返回排序后的字符串。

示例:$(sort foobarlose)返回“barfoolose”。

备注:sort函数会去掉<list>中相同的单词。

$(word <n>,<text>)

名称:取单词函数——word。

功能:取字符串<text>中第<n>个单词。(从一开始)

返回:返回字符串<text>中第<n>个单词。如果<n><text>中的单词数要大,那么返回空字符串。

示例:$(word 2,foo bar baz)返回值是“bar”。

$(wordlist\ <s>,<e>,<text>)

名称:取单词串函数——wordlist。

功能:从字符串<text>中取从<s>开始到<e>的单词串。<s><e>是一个数字。

返回:返回字符串<text>中从<s><e>的单词字串。如果<s><text>中的单词数要大,那么返回空字符串。

如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单词串。

示例:$(wordlist 2,3,foo bar baz)返回值是“bar baz”。

$(words <text>)

名称:单词个数统计函数——words。

功能:统计<text>中字符串中的单词个数。

返回:返回<text>中的单词数。

示例:$(words,foo bar baz)返回值是“3”。

备注:如果我们要取<text>中最后的一个单词,我们可以这样:$(word $(words <text>),<text>)

$(firstword <text>)

名称:首单词函数——firstword。

功能:取字符串<text>中的第一个单词。

返回:返回字符串<text>的第一个单词。

示例:$(firstword foo bar)返回值是“foo”。

文件名操作函数

$(dir <names…>)

名称:取目录函数——dir。

功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。

返回:返回文件名序列<names>的目录部分。

示例:$(dir src/foo.chacks)返回值是“src/./”。

$(notdir <names…>)

名称:取文件函数——notdir。

功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分。

返回:返回文件名序列<names>的非目录部分。

示例:$(notdir src/foo.chacks)返回值是“foo.chacks”。

$(suffix<names…>)

名称:取后缀函数——suffix。

功能:从文件名序列<names>中取出各个文件名的后缀。

返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。

示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c.c”。

$(basename <names…>)

名称:取前缀函数——basename。

功能:从文件名序列<names>中取出各个文件名的前缀部分。

返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。

示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar hacks”。

$(addsuffix <suffix>,<names…>)

名称:加后缀函数——addsuffix。

功能:把后缀<suffix>加到<names>中的每个单词后面。

返回:返回加过后缀的文件名序列。

示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。

$(addprefix <prefix>,<names…>)

名称:加前缀函数——addprefix。

功能:把前缀<prefix>加到<names>中的每个单词后面。

返回:返回加过前缀的文件名序列。

示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。

$(join<list1>,<list2>)

名称:连接函数——join。

功能:把<list2>中的单词对应地加到<list1>的单词后面。

  • 如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。
  • 如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。

返回:返回连接过后的字符串。

示例:$(join aaa bbb,111 222 333)返回值是“aaa111 bbb222 333”。

foreach函数

$(foreach<var>,<list>,<text>)

把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。

每一次<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔.

最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

例如:

names:=a b c d

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

$(files)的值是“a.o b.o c.o d.o”

if函数

$(if<condition>,<then-part>)

或是

$(if<condition>,<then-part>,<else-part>)

if函数的返回值是,如果<condition>为真(非空字符串),那个<then-part>会是整个函数的返回值;

如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值;

此时如果<else-part>没有被定义,那么,整个函数返回空字串。

call函数

call函数是唯一一个可以用来创建新的参数化的函数。这个表达式中,你可以定义许多参数,然后你可以用call函数来向这个表达式传递参数。其语法是

$(call <expression>,<parm1>,<parm2>,<parm3>...)

reverse=$(1) $(2)

foo=$(call reverse,a,b)

foo的值就是“a b”

参数的次序是可以自定义的,不一定是顺序的,如

reverse=$(2) $(1)

foo=$(call reverse,a,b)

foo的值就是“b a”。

origin函数

origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是:

$(origin <variable>)

注意,<variable>是变量的名字,不应该是引用。所以你最好不要在<variable>中使用“$”字符。

Origin函数会以其返回值来告诉你这个变量的“出生情况”

  • 如果<variable>从来没有定义过,origin函数返回这个值“undefined”。
  • “default”如果<variable>是一个默认的定义,比如“CC”这个变量
  • “environment”如果<variable>是一个环境变量,并且当Makefile被执行时,“-e”参数没有被打开。
  • “file”如果<variable>这个变量被定义在Makefile中。
  • “command line”如果<variable>这个变量是被命令行定义的。
  • “override”如果<variable>是被override指示符重新定义的。
  • “automatic”如果<variable>是一个命令运行中的自动化变量

shell函数

shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如

contents:=$(shell catfoo)

files:=$(shell echo *.c)

这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。

特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。

控制make的函数

make提供了一些函数来控制make的运行。通常,你需要检测一些运行Makefile时的运行时信息,并且根据这些信息来决定,你是让make继续执行,还是停止。

$(error <text...>)

产生一个致命的错误,<text…>是错误信息。注意,error函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如

#在变量ERROR_001定义了后执行时产生error调用
ifdef ERROR_001

$(error error is $(ERROR_001))

endif

#目录err被执行时才发生error调用
ERR=$(error found an error!)

.PHONY:err

err:;$(ERR)

$(warning <text...>)

这个函数很像error函数,只是它并不会让make退出,只是输出一段警告信息,而make继续执行。

make运行

make的退出码

make命令执行后有三个退出码:

0——表示成功执行。

1——如果make运行时出现任何错误,其返回1。

2——如果你使用了make的“-q”选项,并且make使得一些目标不需要更新,那么返回2。

指定Makefile

默认文件

GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件——“GNUmakefile”、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行。

特殊文件

我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能,我们要使用make的“-f”或是“–file”参数(“–makefile”参数也行)

  • make –f hchen.mk
  • 如果在make的命令行是,你不只一次地使用了“-f”参数,那么,所有指定的makefile将会被连在一起传递给make执行。

指定目标

你的makefile中的第一个目标是由许多个目标组成,你可以指示make,让其完成你所指定的目标。

如何指定目标?

要达到这一目的很简单,需在make命令后直接跟目标的名字就可以完成(如前面提到的“make clean”形式)任何在makefile中的目标都可以被指定成终极目标,但是除了以“-”打头,或是包含了“=”的目标,因为有这些字符的目标,会被解析成命令行参数或是变量。

存储指定目标环境变量

有一个make的环境变量叫“MAKECMDGOALS”,这个变量中会存放你所指定的终极目标的列表,如果在命令行上,你没有指定目标,那么,这个变量是空值。

例如:

sources=foo.c bar.c

ifneq ($(MAKECMDGOALS),clean)

include $(sources:.c=.d)

endif

只要我们输入的命令不是“makeclean”,那么makefile会自动包含“foo.d”和“bar.d”这两个makefile

.PHONY:all

all:prog1 prog2 prog3prog4

这个makefile中有四个需要编译的程序——“prog1”,“prog2”,“prog3”和“prog4”,我们可以使用“make all”命令来编译所有的目标

makefile中的目标

**“all”**这个伪目标是所有目标的目标,其功能一般是编译所有的目标。

**“clean”**这个伪目标功能是删除所有被make创建的文件。

**“install”**这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。

**“print”**这个伪目标的功能是例出改变过的源文件。

**“tar”**这个伪目标功能是把源程序打包备份。也就是一个tar文件。

**“dist”**这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。

**“TAGS”**这个伪目标功能是更新所有的目标,以备完整地重编译使用。

**“check”和“test”**这两个伪目标一般用来测试makefile的流程。

检查规则

我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用make命令的下述参数

-n

“-n”

“–just-print”

“–dry-run”

“–recon”

不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行

-t

“-t”

“–touch”

这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标

-q

“-q”

“–question”

这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。

-W

“-W <file>”

“--what-if=<file>”

“--assume-new=<file>”

“--new-file=<file>”

这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令.

一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。

make参数

-b,-m

“-b”

“-m”

这两个参数的作用是忽略和其它版本make的兼容性。

-B

“-B”

“–always-make”

认为所有的目标都需要更新(重编译)。

-C <dir>

-C <dir>

--directory=<dir>

指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。

如:

make –C ~hchen/test–C prog
#等价于
make –C ~hchen/test/prog

—debug[=<options>]

输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。

下面是<options>的取值:

a——也就是all,输出所有的调试信息。(会非常的多)

b——也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。

v——也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。

i——也就是implicit,输出所以的隐含规则。

j——也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。

m——也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。

-d

“-d”相当于“–debug=a”

-e

“–environment-overrides”

指明环境变量的值覆盖makefile中定义的变量的值。

-f

-f=<file>

--file=<file>

--makefile=<file>

#指定需要执行的makefile

-h

“–help”

显示帮助信息。

-i

“–ignore-errors”

在执行时忽略所有的错误。

-I <dir>

--include-dir=<dir>

指定一个被包含makefile的搜索目标。可以使用多个“-I”参数来指定多个目录。

-j[<jobsnum>]

--jobs[=<jobsnum>]

指同时运行命令的个数。如果没有这个参数,make运行命令时能运行多少就运行多少。

如果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的。

注意这个参数在MS-DOS中是无用的

-k

“–keep-going”

出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了。

“-S”

“–no-keep-going”

“–stop”

取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。

-l <load>

“–load-average[=<load]”

“—max-load[=]”

指定make运行命令的负载。

-o<file>

--old-file=<file>

--assume-old=<file>

不重新生成的指定的<file>,即使这个目标的依赖文件新于它。

-p

“–print-data-base”

输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用“make -q p”命令。如果你想查看执行makefile前的预设变量和规则,你可以使用“make–p–f/dev/null”。

-r,-R

“–no-builtin-rules”

禁止make使用任何隐含规则。

“-R”

“–no-builtin-variabes”

禁止make使用任何作用于变量上的隐含规则。

-s

“–silent”

“–quiet”

在命令运行时不输出命令的输出。

-v

“–version”

输出make程序的版本、版权等关于make的信息。

-w

“–print-directory”

输出运行makefile之前和之后的信息。这个参数对于跟踪嵌套式调用make时很有用。

“–no-print-directory”

禁止“-w”选项。

–warn-undefined-variables

只要make发现有未定义的变量,那么就输出警告信息。

模式规则

自动化变量

$@

表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。

$(@D)

表示"$@“的目录部分(不以斜杠作为结尾),如果"​$@“值是"dir/foo.o”,那么”$(@D)“就是"dir”,而如果"$@“中没有包含斜杠的话,其值就是”."(当前目录)。

$(@F)

表示"$@"的文件部分,如果"​$@“值是"dir/foo.o”,那么"​$(@F)“就是"foo.o”,"​$(@F)“相当于函数"​$(notdir$@)”。

“$(*D)”

“$(*F)”

和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,"$(*D)“返回"dir”,而"$(*F)“返回"foo”

“$(%D)”

“$(%F)”

分别表示了函数包文件成员的目录部分和文件部分。

“$(<D)”

“$(<F)”

分别表示依赖文件的目录部分和文件部分。

“$(+D)”

“$(+F)”

分别表示所有依赖文件的目录部分和文件部分。

“$(?D)”

“$(?F)”

分别表示被更新的依赖文件的目录部分和文件部分

$%

仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"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”。

如果目标中的后缀是make所不能识别的,那么"$*"就是空值。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值