makefile

学习Makefile笔记,看着英文官方文档和一些资料学习,英文水平能力有限,如有看官看到,有错误或者不明确的请留言或者私信。

一、Makefile简介

make需要一个配置文件makefile来告诉make需要做什么,一般,makefile文件会指定make如何编译和链接一个程序。make重新编译编辑器时,必须重新编译每个更改的C源文件。如果头文件已更改,则必须重新编译包含头文件的每个C源文件以确保安全。每次编译都会生成一个与源文件相对应的目标文件。最后,如果重新编译了任何源文件,则必须将所有目标文件(无论是新生成的还是从以前的编译中保存的)链接在一起,以生成新的可执行编辑器。

1.1、规则介绍

简单的makefile由以下形状的“规则”组成:

target ... : prerequisites ...
	recipe
	...
	...
	######## #是注释
	#target  目标文件
	#prerequisites 条件
	#recipe 方法

       目标通常是程序生成的文件名;目标的可以是可执行文件或对象文件,也可以是要执行的操作的名称,例如“清理”。
       条件是用作创建目标的输入的文件。一个目标通常依赖于多个文件。
       方法指定make做什么。方法可以理解为一个shell命令或者其他可执行命令,一个方法可能有多个命令,要么在同一行上,要么在各自的行上。请注意:你需要在每行方法的开头加一个制表符!这种默默无闻会让粗心的人措手不及。如果你更喜欢在你的方法前面加一个字符而不是制表符,你可以设置。RECIPEPREFIX变量转换为另一个字符。
       通常,方法在一个条件的规则中,如果任何条件发生变化,它将用于创建目标文件。有些规则里面的目标并没有条件。例如,包含与目标“clean”关联的delete命令的目标没有条件。
       然后,一个规则解释了如何以及何时重制作为特定规则目标的某些文件。make对创建或更新目标的条件执行方法。一个规则还可以解释如何以及何时执行一个操作。
       一个makefile可能包含除规则之外的其他文本,但一个简单的创建文件只需要包含规则。规则可能看起来比这个模板中显示的更复杂,但所有规则或多或少都符合这个模式。

1.2、一个简单的makefile文件

       下面是一个简单的makefile文件,它描述了可执行文件edit(c语言可执行文件)依赖于8个对象文件的方式,而这8个对象文件又依赖于8个C源文件和3个头文件。
       在下面例子中,所有C文件都包含defs.h,但只有那些定义编辑命令包含command.h,,只有更改编辑器缓冲区的低级文件包含缓冲区e buffer.h。

edit : main.o kbd.o command.o display.o \
		insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o \
		insert.o search.o files.o utils.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 buffer.h
	cc -c display.c
insert.o : insert.c defs.h buffer.h
	cc -c insert.c
search.o : search.c defs.h buffer.h
	cc -c search.c
files.o : files.c defs.h buffer.h command.h
	cc -c files.c
utils.o : utils.c defs.h
	cc -c utils.c
clean :
	rm edit main.o kbd.o command.o display

上面例子是官方文档的。自己做了一个:简单的示例
       ‘\’是换行符,可以将一行分割显示成俩行或者多行。
       在命令行输入 make 编译

make

       在命令行输入make clean可以删除生成的中间文件以及可执行文件

make clean 

       在命令行输入edit运行代码。

edit

当目标是一个文件时,它的条件发生改变,就需要重新编译或者重新链接。

1.3、make怎么执行makefile

       默认情况下,make先执行第一个目标(不包括名字 以   ‘.’   开头的目标),称为默认目标。目标的目的是使make完成文件的更新。我们可以使用命令行命令或者 .DEFAULT_GOAL 特殊环境变量来更新目标。
       默认的目标是更新可执行程序的编辑;因此,我们将该规则放在第一位。上面的例子中,可以在命令行输入

make

       make会读取当前目录的makefile,并找到第一个目标,在该例中,规则将用于重新链接edit。在make处理该规则前,必须先处理该规则以来的其它规则。这些文件都是按照这个规则处理。这个规则通过C源文件来更新.o文件。源文件发生更改、条件里面的头文件发生更改、对象文件更新以及目标文件 不存在必须重新编译。
       如果规则的条件包含其他规则的目标文件,则会先处理被包含的规则。如果规则不依赖于其它目标,例如:clean,make不会处理这个规则,除非告诉make需要这样做,可以执行命令

make clean

       在重新编译对象时,make会检查条件里面的头文件和源文件是否有更新,如果makefile没有指定’.c’和’.h’文件在哪个规则做什么,make不会处理这些文件。但是make会更新自动生成的C程序,例如:Bison 和 Yacc会按照自己的规则生成。
       再重新编译edit有关的文件后,make会重新他们链接到edit。如果edit不存在,或者由任何一个文件发生更新,edit就必须重新编译,make会重新他们链接到edit

1.4、makefile的变量

       像上面例子那样书写第一个规则的条件,如果多个地方用到,需要修改时,就得一个一个修改,容易发生漏改,makefile支持定义变量,就可以解决这一问题。允许定义一个变量,多次使用。
       标准的做法是在makefile,为所有对象文件名的列表定义一个变量存放它们,可以名为:objects、OBJECTS、、objs、OBJS、obj OBJ。例如变量名为objects,可以使用 ‘$(objects)’调用它。
       上面例子的格式:

edit : main.o kbd.o command.o display.o \
		insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o \
		insert.o search.o files.o utils.o

       用变量可以这样写

objects = main.o kbd.o command.o display.o \
	insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)

       使用变量完整版如下

objects = main.o kbd.o command.o display.o \
	insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)
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 buffer.h
	cc -c display.c
insert.o : insert.c defs.h buffer.h
	cc -c insert.c
search.o : search.c defs.h buffer.h
	cc -c search.c
files.o : files.c defs.h buffer.h command.h
	cc -c files.c
utils.o : utils.c defs.h
	cc -c utils.c
clean :
	rm edit $(objects)
1.5、make执行方法

       单个编译的C源文件的方法可以省略,因为make会自动使用cc -c把’.c’文件编译成’.o’。
       当一个’.c’文件使用这种方式时,它会自动添加条件列表中。
       上面的例子可以修改为:

objects = main.o kbd.o command.o display.o \
	insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)
	main.o : defs.h
	kbd.o : defs.h command.h
	command.o : defs.h command.h
	display.o : defs.h buffer.h
	insert.o : defs.h buffer.h
	search.o : defs.h buffer.h
	files.o : defs.h buffer.h command.h
	utils.o : defs.h
.PHONY : clean
clean :
	rm edit $(objects)

       隐式规则非常方便,所以它们很重要。它们经常被使用。

1.6、Makefile的其他风格

       当makefile的对象仅由隐式规则创建时,可以使用另一种风格的makefile。在这种makefile样式中,根据目标的条件分组,而不是它们的目标对它们进行分组。例如:

objects = main.o kbd.o command.o display.o \
	insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

       defs.h是所有对象文件的条件,command.h是kbd.o、command.o、 files.o的条件buffer.h是display.o、 insert.o、 search.o files.o的条件。

1.7、清理目录的规则

       编译程序并不是编写规则的唯一目的。Makefiles通常会告诉你除了编译程序之外,如何做一些其他的事情:例如,如何删除所有的对象文件和可执行文件,“clean”指定目录。
       makefile示例:

clean:
	rm edit $(objects)

       在实践中,我们可能希望以一种更复杂的方式编写规则来处理意想不到的情况。我们可以这样做:

.PHONY : clean
clean :
	-rm edit $(objects)

       这可以防止make分不清规则clean和一个名为clean的实际文件,并导致它在rm出现错误的情况下继续运行。这样的规则不应该放在makefile的开头,因为我们不希望它在默认情况下运行!因此,在示例makefile中,我们希望用于编辑的规则(它将重新编译编辑器)保持默认目标。

二、创建makefile

       make通过读取makefile数据库的内容来重新编译系统。

2.1、makefile的组成

       makefile由5部分组成:明确的规则、隐式规则、变量定义、指令、注释。
       1)一个明确的规则规定何时以及如何重制一个或多个文件,称为该规则的目标。它列出了目标所依赖的其他文件,称为目标的条件,还可能给出一个用于创建或更新目标的方法。
       2)一个隐式规则规定了何时以及如何根据文件的名称重新创建一类文件。它描述了目标如何依赖于名称与目标相似的文件,并给出了创建或更新此类目标的方法。
       3)变量定义是为变量指定一个文本字符串值的行,该变量可以在以后被替换到文本中。
       4)指令是让make在读取makefile时做一些特殊事情的指令。这些包括:
       (1)读取其他makefile文件。
       (2)决定makefile里基于变量的值是否生效。
       (3)从包含多行的逐字字符串定义变量。
       5)makefile,一行中以 ‘#’ 开头,‘#’后面的是注释,最后一个字符时反斜杠,代表换行,下一行也是注释,不会执行。如果使用‘#’字符,并不想注释,应该在‘#’前面加反斜杠,‘#’。注释可能会出现在制作文件中的任何一行上,尽管它们在某些情况下会被特别处理。
       不能在变量引用或函数调用中使用注释:在变量引用或函数调用中,任何#的实例都将按字面意思处理(而不是作为注释的开头)。
       方法中的注释被传递给shell,就像其他方法内容一样。shell决定如何解释它:这是否为注释取决于shell。
       在define指令中,在变量定义期间不会忽略注释,而是在变量的值中保持完整。当变量展开时,它们要么被视为make注释,要么被视为方法内容,这取决于变量被求值的上下文。

2.1.1拆分长行

       makefile使用“基于行”的语法,其中换行符是特殊的,并标记语句的结束。GNU make对语句行长度没有限制,它的长度取决于计算机的内存大小。
       如果行太长,不换行或滚动显示就很难阅读。因此,为了提高makefile的可读性,可以在语句中间添加换行符:通过使用反斜杠()字符转义内部换行符来实现。在需要区分的地方,我们将“物理行”称为以换行符结尾的单行(无论是否转义),而“逻辑行”则是一个完整的语句,包括从第一个非转义换行符开始的所有转义换行符。
       处理反斜杠/换行组合的方式取决于语句是方法行还是非方法行。
       在方法行之外,反斜杠/换行被转换成一个空格字符。这样做之后,反斜杠/换行符周围的所有空白就被压缩成一个空格:这包括反斜杠之前的所有空白,反斜杠/换行符之后行头的所有空白,以及任何连续的反斜杠/换行符组合。
       如果定义了. POSOX特殊目标,则稍微修改反斜杠/换行处理以符合POSIX.2:首先,不删除反斜杠之前的空格,并且不压缩连续的反斜杠/换行。
换行不添加空白字符
       如果你需要分隔一行,但不想添加任何空格,你可以利用一个小技巧:用三个字符美元符号/反斜杠/换行符替换你的反斜杠/换行对:

var := one$\
	word

       在make之后,删除反斜杠/换行符,并将下面的行压缩为一个空格,这相当于:

var := one$ word

       然后make将执行变量展开。变量引用’ $ '指向一个名称为“”(空格)的不存在的变量,因此展开为空字符串,给出一个等价的最终赋值

var := oneword
2.2、makefile取名规则

       默认情况下,当make查找makefile文件时,它会尝试以下名称:GNUmakefile, makefile 和Makefile
       通常会使用makefile和Makefile。建议使用Makefile,Makefile一般在文件第一个或者在重要的文件旁边,例如README。默认的文件不建议设置成GNUmakefile。如果使用GNUmakefile,GNU make可以很好的识别它,其它版本的make可能会识别不了,其它版本的make可能只会识别makefile和Makefile。
       如果make找不到默认的makefile文件,它可能什么都不会做。这是,必须使用执行make的参数来找到makefile文件。
       如果不适用标准文件,需要使用make的参数:“-f 文件名”或者“-file 文件”选择makefile文件。上面俩个参数会使make读取选择的文件。上面俩个参数可以同时指定几个makefile文件,将会依次执行。使用了“-f 文件名”或者“-file 文件”,make就不会去寻找标准文件GNUmakefile, makefile 和Makefile。

2.3、引入其他的makefile

       inlcude指令会使make暂停读取当前makefile文件,读取include的文件。示例:

include filenames...

文件名可以包含shell模式文件名。如果文件名为空,则不包含任何内容,也不输出任何错误。
       在改行的开头可以是空格,不能是‘tab’或者.RECIPEPREFIX的值,否则会被认为是方法的行。在include和filenames需要有空格,多余的空格和开头末尾的空格会被忽略。可以用#来注释表明用意。如果文件名包含任何变量或函数引用,则将展开它们。
       例如,如果您有三个.mk文件,a.mk, b.mk和c.mk,并且$(bar)展开为bish bash,那么下面的表达式:

include foo *.mk $(bar)

等价于

include foo a.mk b.mk c.mk bish bash

       当make处理include指令时,它暂停对包含makefile的读取,并依次从每个列出的文件中读取。当这些完成后,make继继续读取出现指令的makefile。使用include指令的一种情况是,当多个程序(由不同目录中的独立makefile处理)需要使用一组通用的变量定义或模式规则。
       另一种情况是,当您想从源文件自动生成条件时;条件可以放在主makefile包含的文件中。这种做法通常比将先决条件附加到主makefile的末尾更简洁,这是其他make版本的传统做法。
       如果文件名没有使用绝对路径,会优先在当前目录找,如果找不到,会寻找其他目录。首先,任何用’ -I ‘或’——include-dir '选项指定的目录都会被搜索,如果还是找不到,就会寻找下列目录:prefix/include (normally /usr/local/include) /usr/gnu/include,/usr/local/include, /usr/include。
       如果所包含的makefile不能在这些目录中找到,则会生成一条警告消息,但这不是一个立即致命的错误;继续处理包含include的makefile。一旦读取完makefiles, make将尝试重新制作任何过时或不存在的文件。只有在它试图找到重新创建makefile的方法并失败后,那么make会将丢失的makefile诊断为致命错误。如果你想让make忽略一个不存在或者不能重做的makefile,并且没有错误消息,使用-include指令代替include,示例

-include filenames...

       这类似于以各种方式include,除非有文件名(或文件名的条件)不存在或不能重做,则不会出现错误(甚至没有警告)。
       make为了各个版本兼容,-include的别名是sinclude。

2.4、环境变量MAKEFILES

       如果定义了环境变量MAKEFILES,则make将其值视为附加MAKEFILES的文件名称列表(以空格分隔),这些MAKEFILES定义的文件列表将在其他makefile文件之前被读取。此外,默认文件不会从这些makefiles中的一个(或MAKEFILES中的makefile文件)中获取,如果没有找到MAKEFILES中列出的文件,并不会出现错误。
       MAKEFILES的主要用途是在对make的递归调用之间进行通信。通常不会在第一次调用make之前设置环境变量,因为最好不要从外部干扰makefile。但是在执行make时没有明确的makefile, MAKEFILES中的makefile可以做一些有用的事情来帮助内置的隐式规则更好地执行,比如定义搜索路径。
       有些用户倾向于在登录时在环境中自动设置MAKEFILES,并编写makefile程序来期望这样做。这是一个糟糕的做法,因为如果makefile由其他用户使用,可能无法正常工作。最好是在makefile中使用include指令明确要引用的makefile。

2.5、重新创建makefile

       makefile有时可以从其他文件重新创建,如RCS或SCCS文件。如果makefile是从其他文件重新创建的,执行make的时候,make读取的应该是最新的文件。
       make在读取所有makefile后,make会将每个makefile视为一个目标,并尝试更新它。如果当前makefile或者其他地方的makefile有一个显示规则或者隐式规则说明makefile是怎么更新的,那么makefile必然会更新。检查所有makefile之后,如果有makefile发生改变,make会清空之前读取的makefile,重新读取makefile(make重新读取完makefile后,仍然会检查和更新makefile,一般情况下,这时的makefile是最新的,实际并不会更新)。每次重启都会更新环境变量MAKE_RESTARTS。
       如果已经知道一个或多个makefile不能被重新创建,并且希望让make对它们执行隐式规则搜索,考虑效率,可以使用普通方法阻止隐式规则搜索。例如,可以编写一个明确的规则,以makefile为目标,并编写一个空方法。
       如果makefiles指定了一个双冒号规则来重新创建一个带有方法但没有条件的makefile文件,该文件将永远被重新创建。在这种情况下,一个有双冒号规则但没有条件的makefile会在每次运行make时重新生成,然后在make开始后再次读取makefile。这将导致进入死循环,make不断重新创建makefile,不做执行其他事情。为了避免这种情况,Make将不会尝试重新创建那些被指定为双冒号规则的目标但没有条件的makefile。
       如果没有指定make选项“-f”和“-file”,make将会读取默认makefile文件。与使用’ -f ‘或’-file ‘选项明确请求的makefile不同,make不能确定默认makefile文件是否存在。然而,如果缺省makefile不存在,但可以通过运行make规则创建makefile,以便使用。
       因此,如果默认makefile文件不存在,make将尝试按照搜索时的顺序排列,创建makefile,直到没有地方尝试,如果创建不成功,不算是错误。生成makefile不是必要的。
       make的‘-t’ or ‘–touch’选项, touch 目标而不是重新创建它们。所以’ -t ‘选项对更新makefile没有影响;即使指定了’ -t ',它们也会真正更新makefile的目标。同样,“-q” (‘–question’,, --dry-run,–recon,不运行makefile的方法,如果makefile没有问题退出)和“-n”’ (–just-print’,不会实际运行任何方法;打印过程),不会阻止makefile更新,因为过期的makefile会导致其他目标的错误输出。使用 ‘make -f mfile -n foo’更新mfile,读取更新后的mfile,然后在不运行它的情况下打印方法来更新foo和它的条件。为foo打印的方法将是mfile更新内容中指定的方法。
       有时可能需要阻止makefile的更新,您可以通过在命令行中将makefile指定为目标,或者将它们指定为makefile来实现这一点。当makefile的名字被明确指定为目标时,“-t”等选项确实适用于它们。
       例如:‘make -f mfile -n mfile foo’,读取makefile 文件mfile,在不实际运行的情况下打印更新所需的方法,然后在不运行的情况下打印更新foo所需的方法,foo的方法将由mfile的现有内容指定。

2.6、重写其他makefile

       有时,有一个与另一个makefile基本相同的makefile是很有用的。你可以经常使用’ include '指令将其中一个包含到另一个中,并添加更多的目标或变量定义。但是,两个makefile为同一个目标提供不同的配方是无效的。但还有另一种方法。

       在包含的makefile中(希望包含另一个makefile的makefile),您可以使用任意匹配模式规则来说明,要从包含的makefile中的信息重新生成任何无法生成的目标,make应该在另一个makefile中查找
       例如,如果您有一个名为makefile的生成文件,它说明如何生成目标,’ foo '(和其他目标),你可以写一个makefile文件“GNUmakefile”包含

foo:
	frobnicate > foo
%: force
	@$(MAKE) -f Makefile $@
force: ;

       如果执行“‘make foo”,make会找到GNUmakefile,并且解读它,如何执行foo,它需要运行这个方法“frobnicate > foo”。如果执行‘make bar’,make将无法在GNUmakefile中找到bar执行它,他将执行规则模式中的方法: ‘make-f Makefile bar’ ,如果Makefile提供了更新的规则,Make将应用这条规则。同样,对于GNUmakefile没有说明如何创建的任何其他目标。

       它的工作方式是,模式规则的模式只有’ % ',所以它匹配任何目标。规则指定了一个前提条件:force,以确保即使目标文件已经存在,方法也会运行。我们为force目标提供了一个空配方,以防止make搜索用于构建它的隐式规则,否则它将应用相同的匹配任何规则来强制自己并创建一个条件循环。

3.7、make阅读makefile

       GNU make的工作分为两个不同的阶段。在第一阶段,它读取所有的makefile,应用的makefile等,并内部化所有变量及其值、隐式和显式规则,并构建所有目标及其先决条件的依赖关系图。第二个阶段,make使用这个内部化的数据来确定需要更新哪些目标,并运行更新这些目标所需的方法。

       理解这个两阶段的方法很重要,因为它直接影响变量和功能扩展的发生;在编写makefile时,这常常是一些混乱的根源。下面是可以在makefile中找到的不同构造的摘要,以及构造的每个部分展开所处的阶段。

       我们说,如果在第一阶段发生,则立即展开:make将在解析makefile时展开构造的那一部分。如果不是立即扩张,我们就说扩张被推迟了。延迟构造部分的展开被延迟到使用该展开时:要么是在直接上下文中引用它,要么是在第二阶段需要它。

       您可能还不熟悉其中的一些构念。当您熟悉它们时,可以在后面的章节中参考本节。

       变量赋值
变量定义解析如下:

immediate = deferred
immediate ?= deferred
immediate := immediate
immediate ::= immediate
immediate += deferred or immediate
immediate != immediate
define immediate
	deferred
endef
define immediate =
	deferred
endef
define immediate ?=
	deferred
endef
define immediate :=
	immediate
endef
define immediate ::=
	immediate
endef
define immediate +=
	deferred or immediate
endef
define immediate !=
	immediate
endef

       对于附加操作符’ += ‘,如果变量之前被设置为简单变量(’:= ‘或’::= ‘),则认为右边是即时的,否则是延迟的。对于shell的赋值操作符’ != '时,将立即计算右边的值并将其传递给shell。结果存储在左侧命名的变量中,该变量成为一个简单变量(因此将在每次引用时重新计算)。
条件指令
       条件指令将立即被解析。这意味着,例如,自动变量不能在条件指令中使用,因为自动变量直到该规则的方法被调用时才会被设置。如果你需要在条件指令中使用自动变量,你必须将条件移动到方法中,并使用shell条件语法。
规则定义
规则总是以同样的方式展开,无论其形式如何:

immediate : immediate ; deferred
	deferred

       也就是说,目标和条件部分将立即展开,而用于构建目标的方法总是延迟。对于显式规则、模式规则、后缀规则、静态模式规则和简单的条件定义来说是这样的。

3.8、makefile如何解析

       GNU逐行解析生成文件。解析过程使用以下步骤:

       1. 读取完整的逻辑行,包括反斜杠转义的行;

       2. 删除注释;

       3. 如果该行以recipe前缀字符开头,并且处于规则上下文中,则将该行添加到当前recipe并读取下一行;

       4. 展开出现在即时展开上下文中的行元素;

       5. 扫描该行,寻找分隔符,如’:‘或’ = ',以确定该行是宏赋值还是规则

       :6. 内部化结果操作并读取下一行。

       这样做的一个重要结果是,如果宏只有一行长,那么它可以扩展为整个规则。这将有效:

myrule = target : ; echo built
$(myrule)

       然而,这不会起作用,因为make在扩展行之后不会重新分隔行:

define myrule
target:
	echo built
endef
$(myrule)

       上面的makefile结果是定义了一个目标’ target ‘,前提条件是’ echo ‘和’ built ',就好像makefile包含了target: echo built,而不是一个有方法的规则。展开完成后一行中仍然存在的换行符将被忽略为普通空格。

       为了正确地展开多行宏,必须使用eval函数:这将导致make解析器对展开的宏的结果运行

3.9二次扩展

       以前我们了解到GNU的make分为两个不同的阶段:读入阶段和目标更新阶段。GNU make还能够对makefile中定义的一些或所有目标的先决条件(仅)进行第二次扩展。

       为了实现第二次扩展,特殊目标 .SECONDEXPANSION必须在使用此特性的第一个条件列表之前定义。

       如果定义了这个特殊目标,那么在上面提到的两个阶段之间,就在读入阶段的末尾,在这个特殊目标之后定义的所有目标的条件将被第二次扩展。在大多数情况下,这种二次扩展不会产生任何影响,因为所有变量和函数引用都将在makefile的初始解析期间进行扩展。为了利用解析器的二级扩展阶段,有必要转义makefile中的变量或函数引用。在这种情况下,第一个展开只是取消转义引用,而没有展开它,而展开则留给第二个展开阶段。例如,考虑这个makefile:

.SECONDEXPANSION:
ONEVAR = onefile
TWOVAR = twofile
myfile: $(ONEVAR) $$(TWOVAR)

       在第一个扩展阶段之后,myfile目标的条件列表将是onefile和$(TWOVAR);对ONEVAR的第一个(未转义的)变量引用被展开,而第二个(转义的)变量引用只是未转义的,而不被识别为变量引用。

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

       

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值