Make 学习笔记(1)
参考:
GNU make 学习总结(1)
基础
make是帮助程序员使编译器明白如何编译工程的一种工具; 核心是规则.
规则一般由三部分组成:
- 目标(target)
- 必要条件(prerequisite)
- 命令(command)
具体的书写规则一般为:
target1 target2 : prereq1 prereq2
command1
command2
目标和条件之间由冒号隔开, 命令在目标和条件的下一行, 并以Tab开头.每条规则中可以有多个目标,多个条件和多个命令.
make的原理简单剖析:
当规则中规定的目标文件不存在或者必要条件中某个文件的时间戳比目标文件的时间戳要新,就执行下面的命令,生成新的目标文件.
make会将第一条规则中的目标作为最终目标.
测试的例子:
|-- InputSpeed
|-- main.c
|-- timeutil.c
|-- timeutil.h
|-- wdshow.c
|-- wdshow.h
如上, 目录文件如此.
原始的Makefile文件如下:
main : main.o timeutil.o wdshow.o
gcc timeutil.o wdshow.o main.o -o main
main.o : main.c wdshow.c
gcc -c main.c
wdshow.o : wdshow.c wdshow.h timeutil.h
gcc -c wdshow.c
timeutil.o : timeutil.c timeutil.h
gcc -c timeutil.c
Make会将第一条规则中的目标作为最终目标,也就是我们的可执行文件main,在生成main的时候,需要三个对象文件作为必要条件文件,如果对象文件不存在或不是最新,Make会继续去找是否有规则以该文件为目标文件,找到的话就执行对应的命令,否则就会报错。这个过程大致如下,是一个递归的算法
make(rule) {
for target in rule
for each prerequisite file in rule
if file exist and is up-to-date
return "ok";
else if there is a rule(called rule-file) for file
make(rule-file);
else
return "error"
for each command in rule
run command
}
执行make命令,输出如下。可以看到,第一条命令是最后执行的,这不难理解,因为Make查找的过程是一个递归过程,最先入栈的程序将在最后被执行。
规则
基础知识
变量及自动变量
普通变量的定义是$
后面跟字母或者是括号字母,如$x, $(x)
. 但是在make系统中定义了部分自动变量:
$@ 目标文件名
$% 档案文件成员,是指以a.o(b.o)这种形式作为目标时,括号中的内容
$< 第一个必要条件文件名
$? 时间戳在目标文件之后的所有必要条件文件名,空格隔开
$^ 所有必要条件的文件名,空格隔开,这份列表删除了重复的文件名
$+ 和$^一样,只是未删除重复的文件名
$* 目标的主文件名(即不包括后缀)
以上变量都有两个变体, 加D表示文件的目录部分, 加F表示文件的文件名部分, 注意要加括号, 如$(@D), $(@F)
等.
可以简化makefile如下:
main : main.o timeutil.o wdshow.o
gcc $^ -o $@
main.o : main.c wdshow.c
gcc -c $<
wdshow.o : wdshow.c wdshow.h timeutil.h
gcc -c $<
timeutil.o : timeutil.c timeutil.h
gcc -c $<
这里需要解释下, 每一条规则是并行的, 但这里的自动变量是针对一条规则中说的, 在规则间没有这种自动变量.
假想目标
不指向任何实际文件的目标, 所以总是被更新, 对应的命令总会被执行, 对应的必要条件以及依赖总保持更新.
注意 : 如果clean是一个假想目标,那么当工程中也有一个命名为clean的文件时, 由于make并不知道clean到底是个实际文件还是假想目标(事实上make会优先确定clean是一个实际文件)同时clean也没有必要条件和依赖的更新, make clean
会永远返回文件是最新的. 要确定向编译器表明某一目标是假想目标需要使用关键字.PHONY
, 例如.PHONY : clean all
需要将clean
指定成.PHONY
的一个必要条件,这样clean就总会被更新了,这里.PHONY
是一个特殊目标,它告诉Make它的必要条件都是假想目标。于是我们可以在原来的Makefile中加入几行, 变为:
main : main.o timeutil.o wdshow.o
gcc $^ -o $@
main.o : main.c wdshow.c
gcc -c $<
wdshow.o : wdshow.c wdshow.h timeutil.h
gcc -c $<
timeutil.o : timeutil.c timeutil.h
gcc -c $<
.PHONY: clean all
all: main
clean:
rm main *.o
几个比较常用的假想目标:
clean 清除编译得到的二进制文件
all 所有需要生成的可执行文件
install 经过make all步骤后,在系统中安装生成的二进制程序
distclean 比clean更彻底的删除,包括由configure生成的Makefile文件
TAGS 提供可供编辑的标记表
info 从Textinfo源码创建GNU info源码
check 执行相关测试
类似.PHONY
的特殊目标还有以下几个:
.SUFFIXES 指定Makefile已知后缀列表,用于后缀规则
.INTERMEDIATE 必要条件视为中间文件,make过程如果生成了指定的中间文件,完成后会被删除
.SECONDARY 同样指定中间文件,但make完成后不会被删除
.PRECIOUS make运行中断时不删除指定的目标文件
.DELETE_ON_ERROR make运行中断时删除指定的目标文件
VPATH 和 vpath
改善工程文件目录, 如将之前工程的形式改为:
|-- InputSpeed
-- include
|-- wdshow.h
|-- timeutil.h
-- src
|-- timeutil.c
|-- wdshow.c
|-- main.c
Makefile
如果不改变Makefile不能正确找到.c
和.h
文件, 可以使用 VPATH = src include
方式来找到文件, 但是VPATH只返回找到的第一个文件, 并且可能存在重名文件. 更好的方式是使用vpath pattern directory
的形式来指定在哪个文件夹下搜索哪个文件, 并且对于头文件应该给GCC加上参数.具体的makefile如下:
vpath %.c src
vpath %.h include
CFLAGS = -I include
main : main.o timeutil.o wdshow.o
gcc $^ -o $@
main.o : main.c wdshow.c
gcc $(CFLAGS) -c $< -o $@
wdshow.o : wdshow.c wdshow.h timeutil.h
gcc $(CFLAGS) -c $< -o $@
timeutil.o : timeutil.c timeutil.h
gcc $(CFLAGS) -c $< -o $@
.PHONY: clean all
all: main
clean:
rm main *.o
编译规则的分类:
== 具体规则 ==
即目标, 条件, 命令都明确给出的规则.
== 模式规则 ==
依据是x.o
和x.c
之间具有对应关系, 需要编译出x.o
就会对应去寻找x.c
.
注意:
在一个规则中,如果主文件名中包含了%就表示这是一个模式规则。所谓模式规则,是指对符合这个模式的目标都采用这个规则,注意%和通配符的不同,在Makefile中是可以使用通配符的,*.c表示的是所有以c结尾的文件的集合,而%.c表示所有以c结尾的文件都匹配这条规则,一定要注意区分。
所有的内置规则都是模式规则,使用make -p可以看到这些内置规则,用其中生成%.o的这一条作为例子:
CC = gcc
CFLAGS = -I include
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
OUTOUT_OPTION = -o $@
%.o: %.c
# commands to execute (built-in):
$(COMPILE.c) $(OUTPUT_OPTION) $<
== 静态模式规则 ==
与模式规则基本一样,只是规定了模式的范围,格式如下,模式目标文件必须在指定的变量中
$(OBJECTS): %.o: %c
== 隐含规则 ==
就是make自带的内置规则, 其实就是模式规则. 比如默认的x.o
由x.c
生成.当没有设定规则时, make就会使用内置规则. 简化上面的规则如:
vpath %.c src
vpath %.h include
CFLAGS = -I include
main: main.o timeutil.o wdshow.o
main.o: wdshow.h
wdshow.o: wdshow.h timeutil.h
timeutil.o: timeutil.h
== 自动生成依赖 ==
gcc有一个神奇的功能-MM
, 可以得到头文件的依赖项, 如在我的电脑上以刚才的工程结构, 输入:
gcc -I include -MM src/main.c src/timeutil.c src/wdshow.c
可以得到:
main.o: src/main.c include/wdshow.h
timeutil.o: src/timeutil.c include/timeutil.h
wdshow.o: src/wdshow.c include/wdshow.h include/timeutil.h