基本介绍
- makefile编写的关键在于解决源文件的“文件依赖性”
- 编译链接过程:源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数,变量是否被声明。如果函数未声明,编译器会给出一个警告,但可以生成object file。而在链接程序时,链接器会在所有的object file中寻找函数的实现,如果找不到,那到就会报链接错误码linker error.
- makefile最核心的内容:prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
- clean不是一个文件,只是一个动作名字,类似地,我么可以在一个makefile中定义不用地编译或是和编译无关地命令,比如程序地打包,程序的备份。
- 使用变量:
objs = a.o b.o
c.o d.o
program: $(objs)
main: $(objs)
cc -o program $(objs) - make自动推导
make看到a.o就会自动寻找a.c - “隐晦规则”和“伪目标文件”
.PHONY
$(objects) : defs.h
可以用共同的依赖头文件,但是会使得关系变得混乱 - 清理目标文件的规则
.PHONY : clean
clean :
-rm edit $(objects)
而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。不成文的规矩是——“clean从来都是放在文件的最后”。 - makefile主要包含了五个东西:显示规则,隐晦规则,变量定义,文件指示,和注释
- 引用其他的makefile
- 通过include
如include foo.make *,mk $(bar) - 如果有-I或–include-dir参数,那么make就会在参数指定的目录下去寻找
- 如果目录是/include(一般是/usr/local/bin或/usr/include存在的话,make也会去找
- -include <filename> 会忽略找不到的文件而不会因找不到文件而出现致命信息
- 环境变量:如果环境变量定义了MAKEFILES,那么,make会把这个变量的值当作一个类似于include的动作,但建议不要使用
make的工作方式
GNU的make工作执行步骤如下:
- 读入所有的Makefile
- 读入被include的其它Makefile
- 初始化文件中的变量
- 推导隐晦规则,并分析所有规则
- 为所有的目标文件创建依赖关系链
- 根据依赖关系,决定哪些目标要重新生成
- 执行生成命令
书写规则
依赖关系,生成目标的方法
-
在规则中使用通配符
支持三种*,?,[…], ~的特殊用途- “/test",表示当前用户$HOME目录下的test目录。而"hchen/test"则表示用户hchen的宿主目录下的test目录.
-
makefile中的变量其实就是c++/c中的宏
objects = *.o并不会展开
使用关键字才会展开
objects := $(wildcard *.o)
文件搜寻
- 指定搜寻变量
VPATH = src:…/headers
上面的的定义指定两个目录,“src”和“…/headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。当前目录永远是最高优先搜寻的地方 - 另一个设置文件搜索路径的方法是使用make的“vpath”关键字
1、vpath
为符合模式的文件指定搜索目录。
2、vpath
清除符合模式的文件的搜索目录。
3、vpath
清除所有已被设置好了的文件搜索目录。
vapth使用方法中的需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件
vpath %.h …/headers
vpath %.c foo:bar
vpath % blish
而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。
伪目标
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的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
多目标
bigoutput littleoutput : text.g
generate text.g -
(
s
u
b
s
t
o
u
t
p
u
t
,
,
(subst output,,
(substoutput,,@) >
@
上
述
规
则
等
价
于
:
b
i
g
o
u
t
p
u
t
:
t
e
x
t
.
g
g
e
n
e
r
a
t
e
t
e
x
t
.
g
−
b
i
g
>
b
i
g
o
u
t
p
u
t
l
i
t
t
l
e
o
u
t
p
u
t
:
t
e
x
t
.
g
g
e
n
e
r
a
t
e
t
e
x
t
.
g
−
l
i
t
t
l
e
>
l
i
t
t
l
e
o
u
t
p
u
t
其
中
,
−
@ 上述规则等价于: bigoutput : text.g generate text.g -big > bigoutput littleoutput : text.g generate text.g -little > littleoutput 其中,-
@上述规则等价于:bigoutput:text.ggeneratetext.g−big>bigoutputlittleoutput:text.ggeneratetext.g−little>littleoutput其中,−(subst output,
@
)
中
的
“
@)中的“
@)中的“”表示执行一个Makefile的函数,函数名为subst,后面的为参数
静态模式
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
另一个例子:
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 $<
自动生成依赖性
我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。
例如,如果我们执行下面的命令:
cc -M main.c
其输出是:
main.o : main.c defs.h
需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。
%.d: %.c
@set -e; rm -f $@;
$(CC) -M $(CPPFLAGS) $< >
@
.
@.
@.$KaTeX parse error: Undefined control sequence: \ at position 3: ; \̲ ̲sed 's,\($*\)\.…KaTeX parse error: Can't use function '$' in math mode at position 4: > $̲@; \ rm -f $@.$$
这
第六章 书写命令
显示目录
@echo 正在编译XXX模块…
如果make执行时,带入make参数“-n”或“–just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。
而make参数“-s”或“–slient”则是全面禁止命令的显示。
命令执行
需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。
exec:
cd /home/hchen; pwd
为了做到忽略命令的出错,我们可以在Makefile的命令行前加一个减号“-”(在Tab键之后),标记为不管命令出不出错都认为是成功的。
clean:
-rm -f *.o
还有一个全局的办法是,给make加上“-i”或是“–ignore-errors”参数,那么,Makefile中所有命令都会忽略错误。而如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。
还有一个要提一下的make的参数的是“-k”或是“–keep-going”,这个参数的意思是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。
例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:
subsystem:
cd subdir && $(MAKE)
等价于
subsystem:
$(MAKE) -C subdir
我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了“-e”参数。
如果你要传递变量到下级Makefile中,那么你可以使用这样的声明:
export <variable …>
如果你不想让某些变量传递到下级Makefile中,那么你可以这样声明:
unexport <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”参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。
“/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”结束