makefile 语法
代码语言:javascript复制目标:依赖
(tab)命令
如:add.o:add.c
(一个tab缩进)gcc –Wall –g –c add.c –o add.o
目标:要生成的目标文件
依赖:目标文件由哪些文件生成
命令:通过执行该命令由依赖文件生成目标
makefile 工作原理
1、若想生成目标,检查规则中的依赖条件是否存在,如不存在,则寻找是否有规则用来生成该依赖文件 2、检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任一个被更新,则目标必须更新
- 分析各个目标和依赖之间的关系
- 根据依赖关系自底向上执行命令
- 根据修改时间比目标新,确定更新
- 如果目标不依赖任何条件,则执行对应命令,以示更新
一个最简单的 makefile
代码语言:javascript复制main:main.c
gcc main.c -o main
该 makefile 生成目标为 main 的文件,依赖 main.c,所需命令是 gcc main.c -o main
,注意前面的 (tab)。
联合编译 makefile
上面的例子只是一个最简单的 makefile 的使用方法,但实际项目里面不可能只有一个文件,实际可能是多个 .c .h 组成,像这样的项目,我们该如何通过 makefile 管理呢?我们来看下面这个项目的目录。
代码语言:javascript复制mycode@vmware:~/Desktop/code/makefile$ tree
.
├── add.c
├── main.c
├── makefile
├── mul.c
└── sub.c
目录中有 main.c 是项目的 mian 函数入口代码,里面用到了三个函数,分别是 add()、sub()、mul(),他们都再不同的 .c
文件中,如果我们手动编译这个项目需要用到如下命令:
gcc main.c add.c sub.c mul.c -o app
执行上面命令后会执行联合编译,直接生成可执行文件 app
。而有些时候你会发现,这样编译带来的问题是如果我只改动了其中一个文件的代码,执行这个编译语法的时候,会重新编译所有的代码,小工程也就算了,如果是个大项目那肯定会很慢,所以这不是上上之选。正常的做法应该是先使用 -c
参数生成每个文件的 .o
文件,然后联合编译所有的 .o
文件,当某个 .c
文件修改后,只重新编译这个 .c
到 .o
,然后再执行联合编译,这样就减少了多余代码编译的过程,方法如下:
gcc -c main.c add.c sub.c mul.c
这样就生成了 4 个 .o
文件。
mycode@vmware:~/Desktop/code/makefile$ tree
.
├── add.c
├── add.o
├── main.c
├── main.o
├── makefile
├── mul.c
├── mul.o
├── sub.c
└── sub.o
然后对 4 个 .o
文件执行联合编译,最终生成可执行的 app
文件。
gcc main.o add.o sub.o mul.o -o app
以上是我们通过手动敲命令的方式执行编译,这种联合编译,我们如何通过 makefile 来处理呢?先来分析一下,我们把手动执行编译的过程逆向思考一下,想生成目标为可执行的 app
文件,需要依赖 4 个 .o
文件的支持,main.o add.o sub.o mul.o
。,所需执行的命令是 gcc main.o add.o sub.o mul.o -o app
,那么下面的 makefile 语法应运而生了。
app:main.o add.o sub.o mul.o
gcc main.o add.o sub.o mul.o -o app
但这里你会发现,如果我们项目目录是空的,只有 .c
和 .h
文件时,根本没有 .o
文件,makefile 去哪找这些 .o 文件呢?所以我们还需要制作一些其他的目标文件,那就是这些 .o
文件。语法如下:
main.o:main.c
gcc -c main.c
add.o:add.c
gcc -c add.c
sub.o:sub.c
gcc -c sub.c
mul.o:mul.c
gcc -c mul.c
组合后的 makefile 如下:
代码语言:javascript复制app:main.o add.o sub.o mul.o
gcc main.o add.o sub.o mul.o -o app
main.o:main.c
gcc -c main.c
add.o:add.c
gcc -c add.c
sub.o:sub.c
gcc -c sub.c
mul.o:mul.c
gcc -c mul.c
以上就是一个算是能实现功能的 makefile 了,但是还不算完美,后面我们再引入其他 makefile 的特性,先在这个项目目录下执行一次 make
命令,看看编译的效果如何。
mycode@vmware:~/Desktop/code/makefile$ make
gcc -c main.c
gcc -c add.c
gcc -c sub.c
gcc -c mul.c
gcc main.o add.o sub.o mul.o -o app
执行 make 命令后我们可以看到,首先对每个 .c
文件进行编译,生成 .o
文件,然后对几个 .o
文件联合编译生成可执行的 app
文件。并且如果你修改了其中一个 .c
文件的情况下,重新执行 make
命令,make
只会重新编译你修改过的源文件,并不会重新编译所有。
mycode@vmware:~/Desktop/code/makefile$ vi main.c
mycode@vmware:~/Desktop/code/makefile$ make
gcc -c main.c
gcc main.o add.o sub.o mul.o -o app
makefile 变量
接下来我们引入 makefile 变量机制,来修改一下上面的 makefile 文件。
代码语言:javascript复制obj = main.o add.o sub.o mul.o
app:$(obj)
gcc $(obj) -o app
main.o:main.c
gcc -c main.c
add.o:add.c
gcc -c add.c
sub.o:sub.c
gcc -c sub.c
mul.o:mul.c
gcc -c mul.c
clean:
rm -rf $(obj) app
执行后,同样可以达到效果,我们引入了一个 obj 的变量,注意变量在使用的时候要加 $(),中间变量名字。并且增加了一个 clean
目标,他不依赖任何东西,执行一条清除所有 .o
文件和 app
文件的命令。用来帮助我们清理项目目录。使用方法就是在 make
命令后加 clean
参数即可,以下是执行后的效果:
mycode@vmware:~/Desktop/code/makefile$ make clean
rm -rf main.o add.o sub.o mul.o app
mycode@vmware:~/Desktop/code/makefile$ ls
add.c main.c makefile mul.c sub.c
makefile 自动变量
makefile 中有一些预定义的变量,你可以理解它像是 C 语言中的一些关键字,分别有不同的意义,我们列举几个常用的自动变量(其他还有很多),通过上面的 makefile 文件做修改来演示他们的用法。
代码语言:javascript复制$@:在命令中使用,表示规则中的目标
$<:在命令中使用,表示规则中的第一个条件
$^:在命令中使用,表示规则中的所有条件,组成一个列表,以空格隔开,如果这个列表中有重复的项则消除重复项。
他们的使用方法如下:
代码语言:javascript复制obj = main.o add.o sub.o mul.o
app:$(obj)
gcc $^ -o $@
main.o:main.c
gcc -c $< -o $@
add.o:add.c
gcc -c $< -o $@
sub.o:sub.c
gcc -c $< -o $@
mul.o:mul.c
gcc -c $< -o $@
clean:
rm -rf $(obj) app
我们分别把这三个自动变量放到了不同的位置,用来表示他们的作用。可以一目了然。执行 make
命令后,可以达到同样的效果。
makefile 模式规则
再分析一下上面的 makefile 代码,对于每个要生成的 .o
文件,我们都要给他写一条规则,如果有很多怎么办?难道要一条一条的写吗?当然不会,makefile 提供了一种模式规则,使用 %
符号来匹配任意字符串达到通配的作用,先来看改造后的代码,然后我们来分析其执行流程。
obj = main.o add.o sub.o mul.o
app:$(obj)
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
clean:
rm -rf $(obj) app
上面的 makefile 经过改造后,只剩下这么一点内容了,但别看内容少,一样可以达到我们的需求。其执行流程是要生成的最终目标为 app
,app
需要 4 个 .o
文件的支持,这 4 个文件我们用了一个变量 obj
来表示。当去找 main.o
文件时,发现没有,则去下面找是否有匹配生成这个 .o
文件的规则,因为 %.o
可以匹配 main.o
,所以找到了下面这条规则:
%.o:%.c
gcc -c $< -o $@
规则中的 %.c
的 % 值,取决于目标 %.o
,而此时 %.o
中 %
的值是上面生成 app
所需的 main.o
,所以解释以后的代码相当于下面这样:
main.o:main.c
gcc -c $< -o $@
根据这条规则,生成出了 main.o
文件,其他 3 个 .o
文件也依次类推。这样就实现自动化的去匹配生成规则。
mekfile 函数
如果你认为上面的 makefile 已经很完美了,那你就大错特错了,做一个假设,如果你在项目中新增了一个 .c
的文件后,你还是需要修改 makefile 增加一个所依赖的 .o
文件。想自动化实现这个步骤,如果你自己写脚本,你是不是应该考虑,有多少个 .c
文件就生成多少个 .o
文件,而且每个 .o
文件的名字都与 .c
一样,所以我们可以获取一份 .c
文件的列表,根据这份列表把所有后缀改为 .o
再形成一个新的列表,这份列表就作为生成最终 app
的依赖。想实现这样的功能我们就需要用到 makefile 中的函数!如下所示:
# 获取所有 .c 文件的列表赋值给 src 变量
src = $(wildcard *.c)
# 根据 src 变量获取 .o 文件列表存放到 obj 变量中
obj = $(patsubst %.c, %.o, $(src))
app:$(obj)
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@
clean:
rm -rf $(obj) app
这次无论你增加多少个 .c
文件,makefile 都会自动遍历到并生成 .o
文件了。我们也可以添加一些自定义的变量,让以后 makefile 维护起来更方便:
# 获取所有 .c 文件的列表赋值给 src 变量
src = $(wildcard *.c)
# 根据 src 变量获取 .o 文件列表存放到 obj 变量中
obj = $(patsubst %.c, %.o, $(src))
# 方便后面更换编译器
CC = gcc
# 一些通用的编译参数
CFLAGS = -Wall -g
app:$(obj)
$(CC) $^ -o $@
%.o:%.c
$(CC) -c $< -o $@ $(CFLAGS)
clean:
rm -rf $(obj) app
我们在生成 .o
文件时增加了 -Wall
和 -g
参数,这样我们就可以看到编译时是不是有警告信息了,因为我这个项目没有引入头文件告诉编译器去哪里找实现函数,所以就会出现一些警告信息,当然我们目的并不是要处理这些警告,而是来描述 makefile 的使用,看这篇文章的人,你可以自己根据需求修改。
makefile 中的 all
因为 makefile 的执行流程是找到第一个目标作为最终生成的目标,如果顺序错乱了,makefile 就可能报错,all
方法就是解决这个问题而存在的,并且,all
方法可以让一个 makefile 生成多个目标。示例如下:
src = $(wildcard *.c)
obj = $(patsubst %.c, %.o, $(src))
CC = gcc
CFLAGS = -Wall -g
all:app main
%.o:%.c
$(CC) -c $< -o $@ $(CFLAGS)
app:$(obj)
$(CC) $^ -o $@
main:$(obj)
$(CC) $^ -o $@
clean:
rm -rf $(obj) app
makefile clean 方法优化
make clean
命令是用来清除目录下临时文件的,执行 clean 这个目标时,不需要任何依赖项,也就意味着,如果目录下有一个文件名为 clean
的话,执行 make clean
命令时会判断这个目标所依赖的内容是否有变化,如有变化则重新生成,无变化则跳过,而恰恰我们这个 clean
没写依赖规则!这将导致 clean
无论如何都不会被执行。解决这个问题的办法就是将 clean
方法声明为一个_伪目标_,做就就是让 clean
无论如何都更新,同样我们生成的 all
目标也可能会出现这种情况,所以我们将它们两个都声明为伪目标
,方法如下:
src = $(wildcard *.c)
obj = $(patsubst %.c, %.o, $(src))
CC = gcc
CFLAGS = -Wall -g
all:app main
%.o:%.c
$(CC) -c $< -o $@ $(CFLAGS)
app:$(obj)
$(CC) $^ -o $@
main:$(obj)
$(CC) $^ -o $@
clean:
-rm -rf $(obj) main app
.PHONY:clean all
这样即使目录下有名为 clean
或 all
的文件时,也不影响 make clean
的执行。同时注意看代码的人可能也发现了,rm -rf $(obj) main app
命令前增加了一个 -
符号,这个符号的目的就是如果这条命令执行失败了继续执行,不影响后续命令的执行。 至此 makefile 的功能说明到此为止一,下面就是收集的一些常用做测试用的 makefile 代码。
常用 makefile
将目录下所有 .c
文件都分别生成为可执行文件,比如有 sort.c、readfile.c、writefile.c,他们都分别有自己的 main 函数,我们希望生成时生成 3 个可执行文件,分别叫 sort、readfile、writefile,那使用以下 makefile 即可实现。
src = $(wildcard *.c)
dsc = $(patsubst %.c, %, $(src))
CC = gcc
CFLAGS = -Wall -g
all:$(dsc)
%:%.c
$(CC) $^ -o $@
clean:
-rm -rf $(dsc)
.PHONY:clean all
一个有目录规则的 makefile 代码,我们的目录如下所示:
代码语言:javascript复制mycode@vmware:~/Desktop/code/project$ tree
.
├── bin
├── inc
│ └── head.h
├── makefile
├── obj
└── src
├── add.c
├── mian.c
├── mul.c
└── sub.c
4 directories, 6 files
bin 是生成最终目标文件的目录 inc 是存放头文件的目录 obj 是生成的 .o 文件目录 src 是源文件目录 makefile 如下:
代码语言:javascript复制src = $(wildcard ./src/*.c)
obj = $(patsubst ./src/%.c, ./obj/%.o, $(src))
inc = ./inc/
CC = gcc
CFLAGS = -Wall -g
CPPFLAGS = -I
all:./bin/app
./bin/app:$(obj)
$(CC) $^ -o $@ $(CFLAGS)
./obj/%.o:./src/%.c
$(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) $(inc)
clean:
-rm -rf $(obj) ./bin/app
.PHONY:clean all
执行后的结果
代码语言:javascript复制mycode@vmware:~/Desktop/code/project$ make
gcc -c src/add.c -o obj/add.o -Wall -g -I ./inc/
gcc -c src/mian.c -o obj/mian.o -Wall -g -I ./inc/
gcc -c src/mul.c -o obj/mul.o -Wall -g -I ./inc/
gcc -c src/sub.c -o obj/sub.o -Wall -g -I ./inc/
gcc obj/add.o obj/mian.o obj/mul.o obj/sub.o -o bin/app -Wall -g
mycode@vmware:~/Desktop/code/project$ tree
.
├── bin
│ └── app
├── inc
│ └── head.h
├── makefile
├── obj
│ ├── add.o
│ ├── mian.o
│ ├── mul.o
│ └── sub.o
└── src
├── add.c
├── mian.c
├── mul.c
└── sub.c
4 directories, 11 files