1、编写一个简单Makefile模板

时间:2022-10-03 09:22:16

一、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