在Linux下使用makefile,可以实现自动化编译。基makfile定义了一系列的规则来指定,哪些文件需
要先编译,哪些要后编译。
如下图,我的文件下有两个.c文件,一个.h文件。
内容如下。
正常在命令行下,可以使用下面的命令:
gcc main.c list.c -o mian.out
生成可执行文件。
如果要生成.o文件或其它文件,会敲很多的命令。来一步步生成,makefile可以让我们把所有生成文件的命令都放在一个文件下。到时候只需要输入命令make就可以完成。
基本使用和规则
在与文件同级目录下创建一个makefile文件或Makefile文件。格式如下。
target ...: prerequisites ...
command
...
...
target是目标文件(obj),也可以是一个执行文件,还可以是一个标签(label)。
target这一个或多个目标文件依赖于prerequisites中的文件,其生成规则定义在command中。
prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
现在我的文件下有三个文件,main.c、list.c、head.h,要生成一个可执行文件main和两个二进制文件list.o、main.o。
main: main.o list.o
gcc main.o list.o -o main
main.o: main.c
gcc -c main.c -o main.o
list.o: list.c
gcc -c list.c -o list.o
冒号前面的叫目标文件,冒号后面的是依赖文件,整句就是一个依赖关系。
把最终要生成的文件放在开头,这里是main,main的生成依赖main.o和list.o。main.o也是目标文件依赖main.c。
定义好一个依赖关系后,后面就是如何根据依赖文件生成目标文件的命令(这里是三个gcc),这个命令一定要以一个Tab键开头。(用vim的时候不能设置~/.vimrc中的set expandtab 这样会使人的tab制表变成空格,要用set noexpandtab要不然make的时候会得到***分隔符缺失。停止。)
输入make
可以看到了生成两个.o文件,一个可执行文件main。运行main和main.out效果一样。
make是如何工作的?
当我们输入完make命令后:
- make 先在当前目录下找makefile或Makefile文件
- 如果找到,它就会找了Makefile文件中的第一个目标文件(target),上例就是main文件,并把这个文件作为最终的文件。
- 如果main文件不存在,或者说你的main的依赖更改过(体现为日期更新),那它就会去执行后面的命令来生成main
- 然后发现main.o和list.o不存在,那就去找它们的依赖关系。直到你的文件存在,例子中就是.c文件是存在的。
由.c 文件生成了.o文件,由.o文件生成可执行文件。make后面的三句话体现了这一点。是不是有点像递归?
clean
好了,现在你生成完了,你要是想把这三个文件删除岂不是很麻烦,makefile也可以帮你。你可以在最后加上一行。
main: main.o list.o
gcc main.o list.o -o main
main.o: main.c
gcc -c main.c -o main.o
list.o: list.c
gcc -c list.c -o list.o
clean:
rm -i main.o list.o main
这里有一个不同点,就是clean也是目标,但是我们不需要生成clean这个文件,我们管这种目标叫伪目标。它一般没有依赖文件。
执行clean,要把clean在make后写出来:
make clean
为了将伪目标与要生成为文件的目标作一个区分,可以用一个声明来声明,它是一个伪目标。要不然搞不好会给你生成一个伪目标的文件。或者你本身目录下就有与这个伪目标同名的文件。
main: main.o list.o
gcc main.o list.o -o main
main.o: main.c
gcc -c main.c -o main.o
list.o: list.c
gcc -c list.c -o list.o
.PHONY: clean
clean:
rm -i main.o list.o main
.PHONY: 伪目标名,就表示这个目标是一个伪目标。
自动推导
可见,我们的.o文件生成,只依赖.c文件,好像所有的文件都是这样,所以make提供了自动推导这种功能。找.o文件时会通过.c文件自动生成,也就是不需要你再去写相关的命令了。
main: main.o list.o
gcc main.o list.o -o main
.PHONY: clean
clean:
rm -i main.o list.o main
可以看出make给我自动生成了两个所需要的.o文件。
变量
自定义变量
可以将文件名给一些变量,这样就不用每次用都写文件名了。
- 变量要在声明时给初值,可以是多个值
- 使用时在变量前加上$,这时最好将变量用()包起来
默认变量
在Makefile中
CC = gcc
自动化变量
$@:表示目标文件集合
$<:表示依赖文件中的第一个
$^:表示依赖文件集合
makefile里的变量与C语言中的宏一样,都是直接替换。下面的$(target)就是直接替换成了main。
objects = main.o list.o
target = main
$(target): $(objects)
$(CC) $^ -o $@
.PHONY: clean
clean:
rm -i main.o list.o main
根据替换原则:$^=main.o list.o $@=main
$(target): $(objects) = main: main.o list.o
$(CC) $^ -o $@ = gcc main.o list.o -o main
函数
当然,人类的极致就是追求懒。如果有很多.o文件不得敲死我,怎么办,有没有什么办法。自动给我找到所有的.o文件?
真有:
这里有两个函数:
- wildcard 通配符函数
- patsubst 模式字符串替换函数
obj = $(wildcard *.c)
objects = $(patsubst %.c, %.o, $(obj))
target = main
$(target): $(objects)
$(CC) $^ -o $@
.PHONY: clean
clean:
rm -i main.o list.o main
- 函数在使用时,要用$(函数)包起来,函数名后面是空格,参数之间用逗号隔开。
- wildcard *c 函数的意思是找到当前目录下所有.c文件,这个*是一个通配符,可以理解为任意字符。
可能有人想既然*表示通配符为什么不能这么写呢?
obj = *.c
这样obj就是所有的以.c为后缀的文件了吗?
不对,因为makefile里的变量就相当于c语言中的宏,所以$(obj) = *.c
所以才需要函数。
- patsubst %.c, %o, $(obj)意思是把obj里所有的.c替换成.o。%与*有点像,意思是匹配零或若干个字符。但是%只找makefile文件中符合条件的,*找系统目录中符合条件的。
所有这两句在例子中等于
obj = $(wildcard *.c) = main.c list.c
objects = $(patsubst %.c, %.o, $(obj)) = main.o list.o
Makefile里少了.o的生成规则总是觉得少了什么。但又不想一个个敲可以用静态模式。
静态模式
<targets> : <target-pattern> : <prereq-patterns...>
<commands>
...
- targets定义了一系列的目标文件,可以有通配符,是目标的一个集合
- target-pattern指明了targets的模式,也就是目标的一个集合的模式、
- prereq-patterns是目标的依赖模式,它对target-pattern再进行一次依赖目标的定义
比如:
$(objects) : %.o : %.c
$(CC) -c $< -o $@
意思是目标集合在objects中找,找全部是以.o结尾的文件构成了目标集合。依赖就是对.o这个目标集合的二次定义,就是将.o换成.c的一个集合构成了依赖集合。
上面的规则展开等价于下面的规则。
main.o: main.c
gcc -c main.c -o main.o
list.o: list.c
gcc -c list.c -o list.o
目标模式和依赖模式都应该有%。
obj = $(wildcard *.c)
objects = $(patsubst %.c, %.o, $(obj))
target = main
$(target): $(objects)
$(CC) $^ -o $@
$(objects): %.o: %.c
$(CC) -c $< -o $@
.PHONY: clean
clean:
rm -i main.o list.o main