Make学习

1. 几个问题

  1. 为什么需要makefile?
    自动化编译。

    因为makefile 关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、
    模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,
    哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,

  2. 什么是make?
    解释makefile中指令的命令工具。

    2. 理解编译和链接

    编译:xx.cc->xx.o
    链接:把工程的所有的xx.o文件组合成可执行文件
    .lib文件:windows下的.obj文件打包后的文件
    .a文件:Linux下的.o文件打包后的文件(Archive file)

3. makefile介绍

3.1 makefile的规则

规则

  1. 如果工程没有编译过,所有的c文件都要被编译并连接
  2. 如果工程的某几个C文件被修改,那么只编译被修改的c文件,并链接目标程序。
  3. 如果工程头文件被修改,需要编译引用这几个头文件的c语言,并来链接目标程序。
1
2
3
4
5
6
7
target ... :  prerequisites ... 
command
...
...
target:目标文件(包括objectFile或者可执行文件,也可能是个label)
prerequisites:要生成target所需要的文件或者目标。
command:是make执行需要命令

所以prerequisites中如果有一个以上的文件比target新,command的命令就会被执行。

下面是一个例子
Alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.o \
insert.o search.o files.o utils.o

在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要
以一个 Tab 键作为开头。

3.2 make如何工作

默认只输入make命令

  1. make会在当前目录找"Makefile"后者"makefile"
  2. 如果找到,会找第一个target文件,并把这个文件作为最终的目标文件。
  3. 如果edit(上面的target文件)不存在或者后面.o文件修改时间>target文件,会执行后面定义的command来重新生成target文件,
  4. 如果edit依赖的.o文件不存在,则根据后面的规则生成对应的.o文件。
  5. 如果你的.c和.h存在,则会生成.o最后生成edit。

在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make 就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make 根本不理。make 只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

clean这种命令可以通过make clean来执行。

3.3 makefile中使用变量

使用变量代表文件list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit: $(objects)
cc -o $(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 //make的"隐晦规则"。表示clean是个伪目标文件
clean:
rm edit $(objects)

3.5 更精简的风格

1
2
3
4
5
6
7
8
9
10
objects: main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit: $(objects)
cc -o $(objects)
$(objects) : defs.o
kbd.o command.o files.o: command.h
display.o insert.o search.o: buffer.h
.PHONY:clean
clean:
rm edit $(objects)

3.6 保持写clean的习惯

1
2
3
4
5
6
clean:
rm edit $(objects)
或者
.PHONY : clean
-rm edit $(objects)# 减号意味着出现问题也不会中断
# clean往往放在makefile的最后

4. Makefile总述

4.1 makefile组成

Makefile包含五个元素

  1. 显示规则:明显指出要生成的文件,文件依赖和生成的命令
  2. 隐晦规则:通过自动推导得到的规则
  3. 变量定义
  4. 文件指示:包括引用其他Makefile(如同include)、根据某些情况制定Makefile的有效部分,像c语言的#if一样和定义一个多行命令
  5. 注释:#字符注释,若要使用#则用#

    命令必须以tab开始

4.2 Makefile文件名

make命令的寻找路径:./GNUmakefile–>./makefile–>./Makefile
这几个名字里最好使用Makefile
如果使用其他文件名如下操作

1
make -f Make.Linux #make.Linux为指定的makefile

4.3 引用其他的Makefile

使用include可以引用别的Makefile

1
2
3
4
5
6
include <filename> 
# 不能使用tab在include前面
# filename可以是当前shell的文件模式(包含路径和通配符)
比如
bar:e.mk f.mk
include foo.make *.mk $(bar)

如果make在当前目录没有找到include的文件,然后会按下面的情况寻找

  1. make -I或–include-dir,会去指定目录寻找
  2. 如果(/usr/include或/usr/local/bin)存在那么也会寻找
  3. 如果还是不行会报错,如果要避免则需要在include前加-

4.4 环境变量MAKEFILES

如果在环境中定义了环境变量MAKEFILES,那么会把这个变量中的值做一个类似于include的动作。这个变量中的值是其他的Makefile,用空格分割。
建议不使用这个变量,影响太大。

4.5 make的工作方式

  1. 读入所有的Makefile
  2. 读入被include的其他Makefile
  3. 初始化文件中的变量
  4. 推导隐晦规则,并分析所有规则
  5. 为所有目标文件创建依赖关系链。
  6. 根据依赖关系,决定哪些要重新生成。
  7. 执行生成命令
    1-5 为第一个阶段
    如果变量被使用,make会把其展开在使用的位置,但不会完全马上展开。仅当依赖要使用在其内部展开。
    6-7为第二阶段

5. 书写规则

规则包含依赖关系,和生成目标的方法。
Makefile中规则顺序很重要,因为Makefile只应该有一个最终目标。所以第一条规则中目标被确立为最终的目标。

5.1 举例

1
2
foo.o:foo.c foo.h # foo模块
gcc -c -g foo.c

5.2 语法

见前面的部分

5.3 在规则中使用通配符

make 支持三个通配符:* ? […]

1
2
3
4
5
6
7
8
9
10
11
12
clean:
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 会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)
关键字vpath

1
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
9
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
# 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
3
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
# -$(subst output,,$@)中$表示执行一个Makefile函数,函数名subst,后面为参数。$@表示目标的集合。“$@”依次取出目标,并执于命令。

等价于

1
2
3
4
bigoutput : 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
2
exec:
cd /home/ubuntu; pwd

6.3 命令出错

命令运行完,make会检查返回码,如果返回成功继续执行。如果某个命令错了就退出规则,并可能终止所有的规则。

1
2
3
clean:
-rm -f *.o
# 在命令前加-那么不管命令出不出错都认为成功。

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

6.4 嵌套执行make

对于大工程会把Makefile拆成多个模块的小Makefile,所有嵌套执行make会有利于我们Makefile更加简洁。
例如一个subdir,目录下有Makefile,指明编译规则。那么可以这么写总控Makefile

1
2
3
4
5
6
subsystem:
cd subdir && $(MAKE)

# 等价于
subsystem
$(MAKE) -C subdir