Make学习
Make学习
1. 几个问题
- 为什么需要makefile?
自动化编译。因为makefile 关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、
模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,
哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作, - 什么是make?
解释makefile中指令的命令工具。2. 理解编译和链接
编译:xx.cc->xx.o
链接:把工程的所有的xx.o文件组合成可执行文件
.lib文件:windows下的.obj文件打包后的文件
.a文件:Linux下的.o文件打包后的文件(Archive file)
3. makefile介绍
3.1 makefile的规则
规则
- 如果工程没有编译过,所有的c文件都要被编译并连接
- 如果工程的某几个C文件被修改,那么只编译被修改的c文件,并链接目标程序。
- 如果工程头文件被修改,需要编译引用这几个头文件的c语言,并来链接目标程序。
1 | target ... : prerequisites ... |
所以prerequisites中如果有一个以上的文件比target新,command的命令就会被执行。
下面是一个例子
1 | edit : main.o kbd.o command.o display.o \ |
在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要
以一个 Tab 键作为开头。
3.2 make如何工作
默认只输入make命令
- make会在当前目录找
"Makefile"
后者"makefile"
- 如果找到,会找第一个target文件,并把这个文件作为最终的目标文件。
- 如果edit(上面的target文件)不存在或者后面.o文件修改时间>target文件,会执行后面定义的command来重新生成target文件,
- 如果edit依赖的.o文件不存在,则根据后面的规则生成对应的.o文件。
- 如果你的.c和.h存在,则会生成.o最后生成edit。
在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make 就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make 根本不理。make 只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
clean这种命令可以通过make clean
来执行。
3.3 makefile中使用变量
使用变量代表文件list1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24objects = 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 comman.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.o \
insert.o search.o files.o utils.o
3.4 make自动推导
make有自动推导的能力,只要看到.o能推导需要的.c和命令
所以可以修改makefile文件只保留.h文件
1 | objects = main.o kbd.o command.o display.o \ |
3.5 更精简的风格
1 | objects: main.o kbd.o command.o display.o \ |
3.6 保持写clean的习惯
1 | clean: |
4. Makefile总述
4.1 makefile组成
Makefile包含五个元素
- 显示规则:明显指出要生成的文件,文件依赖和生成的命令
- 隐晦规则:通过自动推导得到的规则
- 变量定义
- 文件指示:包括引用其他Makefile(如同include)、根据某些情况制定Makefile的有效部分,像c语言的#if一样和定义一个多行命令。
- 注释:#字符注释,若要使用#则用#
命令必须以tab开始
4.2 Makefile文件名
make命令的寻找路径:./GNUmakefile
–>./makefile
–>./Makefile
这几个名字里最好使用Makefile
如果使用其他文件名如下操作1
make -f Make.Linux #make.Linux为指定的makefile
4.3 引用其他的Makefile
使用include可以引用别的Makefile1
2
3
4
5
6include <filename>
# 不能使用tab在include前面
# filename可以是当前shell的文件模式(包含路径和通配符)
比如
bar:e.mk f.mk
include foo.make *.mk $(bar)
如果make在当前目录没有找到include的文件,然后会按下面的情况寻找
- make -I或–include-dir,会去指定目录寻找
- 如果(/usr/include或/usr/local/bin)存在那么也会寻找
- 如果还是不行会报错,如果要避免则需要在include前加-
4.4 环境变量MAKEFILES
如果在环境中定义了环境变量MAKEFILES,那么会把这个变量中的值做一个类似于include的动作。这个变量中的值是其他的Makefile,用空格分割。
建议不使用这个变量,影响太大。
4.5 make的工作方式
- 读入所有的Makefile
- 读入被include的其他Makefile
- 初始化文件中的变量
- 推导隐晦规则,并分析所有规则
- 为所有目标文件创建依赖关系链。
- 根据依赖关系,决定哪些要重新生成。
- 执行生成命令
1-5 为第一个阶段
如果变量被使用,make会把其展开在使用的位置,但不会完全马上展开。仅当依赖要使用在其内部展开。
6-7为第二阶段
5. 书写规则
规则包含依赖关系,和生成目标的方法。
Makefile中规则顺序很重要,因为Makefile只应该有一个最终目标。所以第一条规则中目标被确立为最终的目标。
5.1 举例
1 | foo.o:foo.c foo.h # foo模块 |
5.2 语法
见前面的部分
5.3 在规则中使用通配符
make 支持三个通配符:* ? […]1
2
3
4
5
6
7
8
9
10
11
12clean:
rm -f *.o
# 打印机打印所有.c
print: *.c
lpr -p $?
touch print
objects = *.o
# 变量中的*.o不展开
objects := $(wildcard *.o)
# 这里的是展开的*.o
5.4 文件搜寻
如果有大量源文件需要分类并存放在不同的目录中,所以在给文件加上路径让make自动寻找。
特殊变量VPATH
如果没有指明这个变量,make 只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。1
VPATH = src:../headers
上面的的定义指定两个目录,“src”和“../headers”,make 会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)
关键字vpath1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# 三种用法
vpath <pattern> <directories>
# 符合pattern的文件指定搜索目录
vpath <pattern>
# 消除符合模式pattern的文件的搜索目录
vpath
# 消除所有已被设置好了的文件搜索目录
vpath %.h ../headers
# 要求make在../headers目录下搜索所有以.h结尾的文件。
vpath %.c foo
vpath % blish
vpath %.c bar
# 表示.c结尾的文件,先在foo目录,然后是blish,最后是bar
vpath %.c foo:bar
vpath % blish
# 表示.c结尾的文件,先在foo目录,然后是bar目录,最后是blish
5.5 伪目标
伪目标是标签不是文件。所以不能和文件重名。
为了避免重名,所以使用1
2
3.PHONY : clean
clean:
rm *.o temp
只要有这个声明只能通过make clean
执行。
伪目标一般没有依赖的文件,但是也可以指定依赖的文件。通过一键生成多个目标文件。1
2
3
4
5
6
7
8
9all : 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
# make all 生成所有目标文件
伪目标也可以成为依赖1
2
3
4
5
6
7.PHONY : cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
5.6 多目标
多个target可以依赖于一个文件,并且生成的命令大体相似。于是能把其合并。1
2
3bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
# -$(subst output,,$@)中$表示执行一个Makefile函数,函数名subst,后面为参数。$@表示目标的集合。“$@”依次取出目标,并执于命令。
等价于1
2
3
4bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
5.7 静态模式
静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵
活 。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30# 语法
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
# targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合
# target-parrtern 是指明了 targets 的模式,也就是的目标集模式。
# prereq-parrterns 是目标的依赖模式,它对 target-parrtern 形成的模式再进行一次依赖目标的定义。
# 举例
objects = foo.o bar.o
all : $(objects)
$(objects) : %.o:%.c
$(CC) -c $(CFLAGS) $< -o $@
# $< = 所有的依赖目标集(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-complie $<
# filter函数可以过滤files里面不采用的文件,再进行模式推导。
5.8 自动生成依赖性
cc编译器的-M选项:自动寻找源文件包含的头文件并生成依赖关系
如果使用GNU的编译器,需要使用-MM参数,否则会引入大量标准库的头文件。
例如1
2
3
4 cc -M main.c
main.o: main.c defs.h
gcc -MM main.c
main.o: main.c defs.h
GNU建议把编译器自动生成的依赖存放在一个文件中,[.d]文件。1
2
3
4
5
6
7
8
9
10
11# 所有的.d依赖于.c
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
# 接下来我们把这个Makefile引入常用的Makefile
sources = foo.c bar.c
include $(sources:.c=.d)
# .c=.d 是做一个替换,把变量中所有的.c字串替换成.d。
6. 书写命令
命令必须以Tab开头,以;隔开,shell解析make命令
6.1 显示命令
当我们在命令前添加@,命令不会显示出来。
如果make执行时,加入-n
或--just-print
,那么只打印命令但不执行。而-s
或--slient
是全面禁止命令显示。
6.2 命令执行
例如1
2exec:
cd /home/ubuntu; pwd
6.3 命令出错
命令运行完,make会检查返回码,如果返回成功继续执行。如果某个命令错了就退出规则,并可能终止所有的规则。1
2
3clean:
-rm -f *.o
# 在命令前加-那么不管命令出不出错都认为成功。
或者给make加-i或–ignore-errors参数。
make 的参数的是“-k”或是“–keep-going”,这个参数的意思是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。
6.4 嵌套执行make
对于大工程会把Makefile拆成多个模块的小Makefile,所有嵌套执行make会有利于我们Makefile更加简洁。
例如一个subdir,目录下有Makefile,指明编译规则。那么可以这么写总控Makefile1
2
3
4
5
6subsystem:
cd subdir && $(MAKE)
# 等价于
subsystem
$(MAKE) -C subdir