0
点赞
收藏
分享

微信扫一扫

makefile学习

酷子腿长一米八 2022-05-03 阅读 144
c++

windows中间代码文件.obj
makefile 使用变量

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

\换行符

只要 make 看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果 make
找到一个 whatever.o,那么 whatever.c,就会是 whatever.o 的依赖文件。并且 cc -c
whatever.c 也会被推导出来,于是,我们的 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) 
 
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

这种可以解决头文件共用,但是增加了文件的耦合,文件引用头文件有变化,经常要修改;

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 
 
.PHONY : clean 
clean : 
rm edit $(objects)

前面说过,.PHONY 意思表示 clean 是一个“伪目标”,。而在 rm 命令前面加了一个小
减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean 的规
则不要放在文件的开头,不然,这就会变成 make 的默认目标,相信谁也不愿意这样。不成
文的规矩是——“clean 从来都是放在文件的最后”。

伪目标的概念
目标是什么

makefile中目标一般对应着一个文件
make比较目标文件和依赖之间的新旧关系,如果依赖新则执行命令
make以文件处理作为第一优先级

伪目标是什么

所谓伪目标就是他不代表一个真正的文件名,在执行make时可以只当这个目标来执行所在规则定义的命令,有时我们将一个伪目标成为标签

为什么引入伪目标

防止存在和目标名一样的文件存在时,当执行make发现文件是最新的导致目标下的命令无法执行

如何定义伪目标

.PHONY: 目标名1 目标名2 ...
当使用定义过后的目标名,无论当前文件夹下是否存在同名的文件都会执行该目标下的所有命令

前面rm和mkdir这两条命令哪怕执行出错了,也不影响正常的仿真。所以,可以在这两条命令前面加上横杠“-”,即使这两条执行出错,makefile的执行也不会中断,不影响后续正常的仿真。

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

make 会在当前目录下找为“GNUmakefile”、“makefile”、“Makefile”文件;
“GNUmakefile”,这个文件是 GNU 的 make 识别的;

可 以 使 用 别 的 文 件 名 来 书 写 Makefile , 比 如 : “Make.Linux” ,
“Make.Solaris”,“Make.AIX”等,如果要指定特定的 Makefile,你可以使用 make 的
“-f”和“–file”参数,如:make -f Make.Linux 或 make --file Make.AIX。

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

include foo.make *.mk $(bar) 
等价于: 
include foo.make a.mk b.mk c.mk e.mk f.mk 

make 命令开始时,会把找寻 include 所指出的其它 Makefile,并把其内容安置在当前
的位。就好像 C/C++的#include 指令一样。如果文件都没有指定绝对路径或是相对路径的话,
make 会在当前目录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个
目录下找:

1、如果 make 执行时,有“-I”或“–include-dir”参数,那么 make 就会在这个参数
所指定的目录下去寻找。
2、如果目录/include(一般是:/usr/local/bin 或/usr/include)存在的话,
make 也会去找。如果有文件没有找到的话,make 会生成一条警告信息,但不会马上出现致
命错误。它会继续载入其它的文件,一旦完成 makefile 的读取,make 会再重试这些没有找
到,或是不能读取的文件,如果还是不行,make 才会出现一条致命信息。如果你想让 make
不理那些无法读取的文件,而继续执行,你可以在 include 前加一个减号“-”。

make 工作时的执行步骤

1、读入所有的 Makefile。 
2、读入被 include 的其它 Makefile。 
3、初始化文件中的变量。 
4、推导隐晦规则,并分析所有规则。 
5、为所有的目标文件创建依赖关系链。 
6、根据依赖关系,决定哪些目标要重新生成。 
7、执行生成命令。 

规则包含两个部分,一个是依赖关系,一个是生成目标的方法。
在 Makefile 中,规则的顺序是很重要的,因为,Makefile 中只应该有一个最终目标,
其它的目标都是被这个目标所连带出来的,所以一定要让 make 知道你的最终目标是什么。
一般来说,定义在 Makefile 中的目标可能会有很多,但是第一条规则中的目标将被确立为
最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make
所完成的也就是这个目标。

targets : prerequisites 
command 
... 
或是这样: 
targets : prerequisites ; command 
command 
...

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

make 支持
三各通配符:“*”,“?”和“[…]”。这是和 Unix 的 B-Shell 是相同的。 波浪号(“~”)
字符在文件名中也有比较特殊的用途。如果是“~/test”,这就表示当前用户的$HOME 目录
下的 test 目录。而“~hchen/test”则表示用户 hchen 的宿主目录下的 test 目录。

文件搜寻

在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存
放在不同的目录中。所以,当 make 需要去找寻文件的依赖关系时,你可以在文件前加上路
径,但最好的方法是把一个路径告诉 make,让 make 在自动去找。

Makefile 文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,
make 只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make
就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。

VPATH = src:…/headers

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

另一个设置文件搜索路径的方法是使用 make 的“vpath”关键字(注意,它是全小写
的),这不是变量,这是一个 make 的关键字,这和上面提到的那个 VPATH 变量很类似,但是
它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使
用方法有三种:

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

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

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

vapth 使用方法中的需要包含“%”字符。“%”的意思是匹配零或若干字符,
例如,“%.h”表示所有以“.h”结尾的文件。指定了要搜索的文件集,而
则指定了的文件集的搜索的目录。例如:

vpath %.h …/headers

该语句表示,要求 make 在“…/headers”目录下搜索所有以“.h”结尾的文件。(如果
某文件在当前目录没有找到的话)

我们可以连续地使用 vpath 语句,以指定不同搜索策略。如果连续的 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 的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖
于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的
生成规则的执行命令是同一个,这可能会可我们带来麻烦,不过好在我们的可以使用一个自
动化变量“$@”(关于自动化变量,将在后面讲述),这个变量表示着目前规则中所有的目
标的集合,这样说可能很抽象,还是看一个例子吧。

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 

其中,- ( s u b s t o u t p u t , , (subst output,, (substoutput,,@)中的“ ” 表 示 执 行 一 个 M a k e f i l e 的 函 数 , 函 数 名 为 s u b s t , 后 面 的 为 参 数 。 关 于 函 数 , 将 在 后 面 讲 述 。 这 里 的 这 个 函 数 是 截 取 字 符 串 的 意 思 , “ ”表示执行一个 Makefile 的函数,函数名为 subst, 后面的为参数。关于函数,将在后面讲述。这里的这个函数是截取字符串的意思,“ Makefilesubst@”表
示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。

静态模式

静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵
活 。
我们还是先来看一下语法:

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

targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern 是指明了 targets 的模式,也就是的目标集模式。
prereq-parrterns 是目标的依赖模式,它对 target-parrtern 形成的模式再进行一次依赖
目标的定义。

这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们的定义成“%.o”,意思是我们的集合中都是以“.o”结尾的,而
如果我们的定义成“%.c”,意思是对所形成的目标
集进行二次定义,其计算方法是,取模式中的“%”(也就是去掉了[.o]
这个结尾),并为其加上[.c]这个结尾,形成的新集合。

所以,我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文
件名中有“%”那么你可以使用反斜杠“\”进行转义,来标明真实的“%”字符。
看一个例子:
objects = foo.o bar.o

all: $(objects)

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

上面的例子中,指明了我们的目标从 o b j e c t 中 获 取 , “ 尾 的 目 标 , 也 就 是 “ f o o . o b a r . o ” , 也 就 是 变 量 object 中获取,“%.o”表明要所有以“.o”结 尾的目标,也就是“foo.o bar.o”,也就是变量 objectfoo.obar.oobject 集合的模式,而依赖模式“%.c”
则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的
依赖目标就是“foo.c bar.c”。而命令中的“ < ” 和 “ <”和“ <@”则是自动化变量,“ < ” 表 示 所 有 的 依 赖 目 标 集 ( 也 就 是 “ f o o . c b a r . c ” ) , “ <”表示 所有的依赖目标集(也就是“foo.c bar.c”),“ <foo.cbar.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

试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”
就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,
那会一个很强大的功能。再看一个例子:

files = foo.elc bar.o lose.o

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

( f i l t e r (filter %.o, (filter(files))表示调用 Makefile 的 filter 函数,过滤“$filter”集,只要其
中模式为“%.o”的内容。其的它内容,我就不用多说了吧。这个例字展示了 Makefile 中更
大的弹性。

自动生成依赖性

在 Makefile 中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的 main.c
中有一句“#include “defs.h””,那么我们的依赖关系应该是:

main.o : main.c defs.h

但是,如果是一个比较大型的工程,你必需清楚哪些 C 文件包含了哪些头文件,并且,
你在加入或删除头文件时,也需要小心地修改 Makefile,这是一个很没有维护性的工作。
为了避免这种繁重而又容易出错的事情,我们可以使用 C/C++编译的一个功能。大多数的
C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依
赖关系。例如,如果我们执行下面的命令:
cc -M main.c

其输出是:
main.o : main.c defs.h

于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关
系,而由编译器自动生成了。需要提醒一句的是,如果你使用 GNU 的 C/C++编译器,你得用
“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。

gcc -M main.c 的输出是:

main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \ 
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \ 
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \ 
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \ 
/usr/include/bits/sched.h /usr/include/libio.h \ 
/usr/include/_G_config.h /usr/include/wchar.h \ 
/usr/include/bits/wchar.h /usr/include/gconv.h \ 
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \ 
/usr/include/bits/stdio_lim.h 

gcc -MM main.c 的输出则是:

main.o: main.c defs.h 

那么,编译器的这个功能如何与我们的 Makefile 联系在一起呢。因为这样一来,我们
的 Makefile 也要根据这些源文件重新生成,让 Makefile 自已依赖于源文件?这个功能并不
现实,不过我们可以有其它手段来迂回地实现这一功能。GNU 组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个“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]文件依赖于[.c]文件,“rm -f @ ” 的 意 思 是 删 除 所 有 的 目 标 , 也 就 是 [ . d ] 文 件 , 第 二 行 的 意 思 是 , 为 每 个 依 赖 文 件 “ @”的意思是删除所有 的目标,也就是[.d]文件,第二行的意思是,为每个依赖文件“ @[.d]<”,也就是[.c]文件生成
依赖文件,“ @ ” 表 示 模 式 “ “ n a m e ” , “ @”表示模式“%.d”文件,如果有一个 C 文件是 name.c,那么“%”就是 “name”,“ @name$$$”意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第
三行使用 sed 命令做了一个替换,关于 sed 命令的用法请参看相关的使用文档。第四行就是
删除临时文件。

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

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

于是,我们的[.d]文件也会自动更新了,并会自动生成了,当然,你还可以在这个[.d]
文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个[.d]文件都包含一个
完赖的规则。一旦我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的
主 Makefile 中。我们可以使用 Makefile 的“include”命令,来引入别的 Makefile 文件(前
面讲过),例如:

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

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

嵌套执行 make

在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,
我们可以在每个目录中都书写一个该目录的 Makefile,这有利于让我们的 Makefile 变得更
加地简洁,而不至于把所有的东西全部写在一个 Makefile 中,这样会很难维护我们的
Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。

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

subsystem:
cd subdir && $(MAKE)

其等价于:

subsystem:
$(MAKE) -C subdir

定义$(MAKE)宏变量的意思是,也许我们的 make 需要一些参数,所以定义成一个变量比
较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行 make 命令。
我们把这个 Makefile 叫做“总控 Makefile”,总控 Makefile 的变量可以传递到下级
的 Makefile 中(如果你显示的声明),但是不会覆盖下层的 Makefile 中所定义的变量,除
非指定了“-e”参数。

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

export <variable …>

如果你不想让某些变量传递到下级 Makefile 中,那么你可以这样声明:

unexport <variable …>

如:

示例一:

export variable = value

其等价于:

variable = value
export variable

需要注意的是,有两个变量,一个是 SHELL,一个是 MAKEFLAGS,这两个变量不管你是
否 export,其总是要传递到下层 Makefile 中,特别是 MAKEFILES 变量,其中包含了 make
的参数信息,如果我们执行“总控 Makefile”时有 make 参数或是在上层 Makefile 中定义
了这个变量,那么 MAKEFILES 变量将会是这些参数,并会传递到下层 Makefile 中,这是一
个系统级的环境变量。

但是 make 命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和
“-W”(有关 Makefile 参数的细节将在后面说明),如果你不想往下层传递参数,那么,你
可以这样来:

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

如果你定义了环境变量 MAKEFLAGS,那么你得确信其中的选项是大家都会用到的,如果
其中有“-t”,“-n”,和“-q”参数,那么将会有让你意想不到的结果,或许会让你异常地
恐慌。

还有一个在“嵌套执行”中比较有用的参数,“-w”或是“–print-directory”会在
make 的过程中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级 make 目录
是“/home/hchen/gnu/make”,如果我们使用“make -w”来执行,那么当进入该目录时,
我们会看到:

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

而在完成下层 make 后离开目录时,我们会看到:
make: Leaving directory `/home/hchen/gnu/make’

当你使用“-C”参数来指定 make 下层 Makefile 时,“-w”会被自动打开的。如果参数中有
“-s”(“–slient”)或是“–no-print-directory”,那么,“-w”总是失效的。

定义命令包

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

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

这里,“run-yacc”是这个命令包的名字,其不要和 Makefile 中的变量重名。在
“define”和“endef”中的两行就是命令序列。这个命令包中的第一个命令是运行 Yacc
程序,因为 Yacc 程序总是生成“y.tab.c”的文件,所以第二行的命令就是把这个文件改改
名字。还是把这个命令包放到一个示例中来看看吧。

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

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

举报

相关推荐

0 条评论