一、Makefile简介
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。linux内核的编译同样也遵循这些规则,具体说明可见kernel/Documentation/kbuild/makefiles.txt
二、简单编写一个Makefile模板
当编译少量的源文件时,可以用以下两种方法编译
1、直接用一条命令编译
gcc -o test 1.c 2.c
编译过程说明:
执行gcc -o test 1.c 2.c时,
对于1.c:预编译、编译、汇编、链接
对于1.c:预编译、编译、汇编、链接
优点:命令简单,易操作
缺点:如果文件很多,即使修改了一个文件,但是所有的文件都要重新"预处理、编译、汇编",效率很低
2、通过Makefile建立编译规则
一个的Makefile文件包含一系列的规则(想了解更多规则可参考《GNU make手册》),其格式如下:
目标 : 依赖1 依赖2 ... 依赖n
命令
注意:命令的前边必须是一个Tab
命令执行的条件:
(1)"依赖"文件比"目标"的时间新
(2)没有"目标"这个文件
三、代码实践
功能说明:当任何一个文件发生改变时,其所有依赖文件和自己都会被重新编译,而其他与此没有关联的文件则不会被编译
源文件如下:
<1>文件1.c
#include <stdio.h> #include "1.h" int main(char argc, char *argv[]) { printf("This is 1.c\n"); func(); printf("AA = %d\n", AA); return 0; }
<2>文件2.c
#include <stdio.h> void func(void) { printf("This is 2.c\n"); }
<3>文件1.h
#define AA 2
1、第1个Makefile
1 target:1.c 2.c 1.h 2 gcc -o target 1.c 2.c
解析:
第一次编译(没有目标生成)或依赖发生变化(依赖比目标新)就执行下面的命令这样的缺点就是,只要其中的一个依赖发生变化,所有的文件都要重新被编译一遍,这样效率极低
2、第2个Makefile
target:1.o 2.o gcc -o target 1.o 2.o #-c表示只编译不链接(只进行预编译、编译、汇编) 1.o : 1.c gcc -c -o 1.o 1.c 2.o : 2.c gcc -c -o 2.o 2.c
解析:
解析:当执行make命令时,想要生成第一个目标target,而target依赖于1.o、2.o,但是当前目录下并没有1.o、2.o,它会用1.o往下查找到1.o依赖于1.c,同时判断(1.c是否比1.o新或1.o是否存在)是否执行gcc -c -o .1.o 1.c,我们这里满足第二个条件所以此命令会被执行,分析1.o也是如此
3、第3个Makefile(效果同第2个Makefile)
target:1.o 2.o gcc -o target 1.o 2.o %.o : %.c gcc -c -o $@ $<
解析:
%.o : %.c,
gcc -c -o $@ $<
等同于
1.o : 1.c
gcc -c -o 1.o 1.c
2.o : 2.c
gcc -c -o 2.o 2.c
%为通配符,%.o,表示所有的.o文件%.c,表示所有的.c文件,
$@表示目标(对应%.o),$<表示第一个依赖(对应%.c)
测试:
(1)第一次编译,则所有的文件都会被编译
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# ls
1.c 1.h 2.c Makefile
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
gcc -c -o 1.o 1.c
gcc -c -o 2.o 2.c
gcc -o target 1.o 2.o
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# ls
1.c 1.h 1.o 2.c 2.o Makefile target
(2)只修改1.c,则所有依赖1.c的文件都会重新被编译,而2.c不会重新被编译
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# vi 1.c
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
gcc -c -o 1.o 1.c
gcc -o target 1.o 2.o
(3)只修改2.c,则所有依赖2.c的文件都会重新被编译,而1.c不会重新被编译
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# vi 2.c
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
gcc -c -o 2.o 2.c
gcc -o target 1.o 2.o
(4)但是修改1.h就不行了,不能检测到1.h的更新,第4个Makefile中继续修改
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# vi 1.h
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
make: `target' is up to date.
4、第4个Makefile
target:1.o 2.o gcc -o target 1.o 2.o #添加此行,当修改1.h时,所有依赖1.h的文件都会重新编译 1.o : 1.c 1.h %.o : %.c gcc -c -o $@ $<
解析:
(1)1.o依赖于1.c,同样也依赖于1.h,故添加1.o : 1.c 1.h
(2)make时就会检测1.c所包含的1.h是否发生改变,若发生改变,则重新编译
(3)但存在一个问题,当有上万个.c文件时,要查看它所包含的.h文件是否发生改变,这样操作就不行了,那么是否有什么自动的规则,来生成依赖文件,第5个Makefile中继续修改
测试:
root@ubuntu:/mnt/hgfs/winshare/mak/4_makefile# make
gcc -c -o 1.o 1.c
gcc -o target 1.o 2.o
5、第5个Makefile
利用-Wp,-MD,1.d自动生成依赖存到1.d中,1.o的所有依赖如下
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# gcc -c -o 1.o 1.c -Wp,-MD,1.d
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# ls
1.c 1.d 1.h 1.o 2.c Makefile
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# vi 1.d
1.o: 1.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/features.h /usr/include/i386-linux-gnu/sys/cdefs.h \
/usr/include/i386-linux-gnu/bits/wordsize.h \
/usr/include/i386-linux-gnu/gnu/stubs.h \
/usr/include/i386-linux-gnu/gnu/stubs-32.h \
/usr/lib/gcc/i686-linux-gnu/4.8/include/stddef.h \
/usr/include/i386-linux-gnu/bits/types.h \
/usr/include/i386-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i686-linux-gnu/4.8/include/stdarg.h \
/usr/include/i386-linux-gnu/bits/stdio_lim.h \
/usr/include/i386-linux-gnu/bits/sys_errlist.h 1.h
target:1.o 2.o gcc -o target 1.o 2.o %.o : %.c gcc -c -Wp,-MD,$@.d -o $@ $< clean: rm *.o target distclean: rm *.o target *.d
(1)-Wp,-MD,$@.d表示编译时自动生成1.o.d、2.o.d,所有关于1.o、2.o的依赖
都保存在1.o.d、2.o.d
(2)clean,distclean表示伪目标,make clean 时,则执行rm *.o target,
make distclean也如此
6、第6个Makefile
objs := 1.o 2.o target:$(objs) gcc -o target $^ $(warning $(objs)) # .1.o.d .2.o.d dep_files := $(foreach f,$(objs),.$(f).d) dep_files := $(wildcard $(dep_files)) $(warning $(dep_files)) ifneq ($(dep_files),) include $(dep_files) endif %.o : %.c gcc -c -Wp,-MD,.$@.d -o $@ $< clean: rm *.o target distclean: rm -f *.o target .*.d
解析:
(1)定义一个立即变量objs来表示target的依赖
(2)$(foreach f,$(objs),.$(f).d)的分析:
foreach 含义是每一个,是一个函数,类似于for循环
f 代表$(objs)中的一个成员变量1.o或2.o,
.$(f).d 把$(objs)中的每个成员都加上前缀.和后缀.d,如.1.o.d和.2.o.d
(3)$(wildcard $(dep_files))表示取出$(dep_files)中的成员变量
(4)ifneq ($(dep_files),)判断$(dep_files)是否非空,非空条件成立
(5)至此,所有.h类型的依赖被包含进去
测试:
单独修改1.h编译成功,修改AA的值为3
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# vi 1.h
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# make
.1.o.d .2.o.d
gcc -c -Wp,-MD,.1.o.d -o 1.o 1.c
gcc -o target 1.o 2.o
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# la
1.c 1.h 1.o .1.o.d 2.c 2.o .2.o.d Makefile target
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# ./target
This is 1.c
This is 2.c
AA = 3