Makefile相关知识
1、传统的gcc -o 指令的缺点 9.5
假设现在有两个c文件,a.c和b.c,我在a.c的main函数中调用b.c中的函数,那么我生成可执行程序的编译指令是:
gcc -o test a.c b.c
这条指令会分别对a.c和b.c执行预处理、编译和汇编,最终生成a.o以及b.o,最后在链接成为test。这样做的问题在于,如果我修改了a.c,需要重新执行gcc指令,这条指令会再将a.c和b.c进行上述处理,可我b.c根本没有修改,不需要再处理,这就造成了重复。
我们一般将预处理、编译和汇编三者合起来称为编译,也就是说程序的形成过程分为两个步骤,编译和链接。解决重复的方法是将编译和链接分开处理,比如:
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -o test a.o b.o
如果我修改了a.c,我只需要重新将a.c编译成a.o,再将a.o和,没变的b.o链接即可。
那么问题来了,如果文件很多,怎么才能知道那个文件被修改了呢?处理方法是看文件时间,如果a.o的时间比a.c更早,那么说明a.c被修改了,需要重新编译。所谓的makefile就是在做这样的工作
2、makefile规则介绍 9.5
Makefile里以一个“规则”作为基本单位,规则的写法如下:
目标文件名:依赖文件名
tab键 命令
当依赖文件比目标文件“新”时或者依赖文件不存在时,执行下一行对应的命令。将上面的程序生成过程写成Makefile如下,一共三个规则:
test:a.o b.o
gcc -o test a.o b.o
a.o:a.c
gcc -c -o a.o a.c
b.o:b.c
gcc -c -o b.o b.c
将上述语句写入一个文件中,放在与a.c和b.c相同目录下,并且文件命名为Makefile,不带后缀。
当我们第一次执行make命令的时候,机器认识到需要生成的是test文件,其依赖于a.o b.o文件,发现这两个文件没有,则先去生成a.o,往下看发现a.o依赖于a.c,且现在我有a.c但是没有a.o,则认为a.c比a.o更加“新”,则执行下面的命令:gcc -c -o a.o a.c 生成a.o,b.o的生成同理,最后a.o和b.o都有了,而test没有,则认为a.o和b.o都比test“新”,所以执行下面的命令:gcc -o test a.o b.o,最终生成test。
假设我修改了a.c,再次执行make。机器认识到需要生成的是test文件,其依赖于a.o b.o文件,首先去生成a.o,发现a.c比已经有的a.o更新,则用gcc -c -o a.o a.c重新生成a.o;再去生成b.o,发现已经有b.o而且b.c没有比b.o更新,故不执行命令。最后再进行链接。
3、makefile语法介绍 9.6
3.1、通配符以及一些常用符号
背景:按照上面第2点的写法,如果有一万个c文件,则需要写一万个类似下面的语句,太麻烦了。
a.o:a.c
gcc -c -o a.o a.c
解决:使用通配符
将上面的Makefile改写
test:a.o b.o
gcc -o test a.o b.o
a.o:a.c
gcc -c -o a.o a.c
b.o:b.c
gcc -c -o b.o b.c
改写后:
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
注释:
%.o:%.c:%是通配符,机器在找寻生成a.o和b.o的规则时,通过%.o 找到对应规则。
$@:表示目标文件
$<:表示第一个依赖文件
$^:表示所有的依赖文件
3.2、假想目标
背景:假设我现在想在Makefile里面加一个功能,清除所有的.o文件以及test文件,写法如下:
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm *.o test
键入make clean即可完成清除操作,也就是说,make后面你可以带上目标名,则执行目标名对应的规则,如果不带目标名,即单纯的make就是默认从第一个目标开始,如果没目标,就从第一条语句开始。
问题:如果在当前目录下已经存在一个名为“clean”的文件,由于Makefile中clean对应规则后面没有依赖,也就是说永远都是依赖比目标clean更“旧”,这种情况下再执行make clean,会没有反应。
解决:将clean定义为假想目标
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm *.o test
.PHONY: clean
3.3、变量
Makefile里面有两种变量,即时变量和延时变量。
即时变量:A:=xxx #A的值即刻确定,在定义时即确定
延时变量:B=xxx #B的值在使用到时才确定
注意:立即变量不能进行
obj-y += main.o
obj-y += display/
obj-y += encoding/
这样的连续追加的操作,因为立即变量必须定义时就确定,这种追加必须用延时变量。
变量的使用:$(变量名,如A或者B)
例子:
A:=$(C)
B=$(C)
all:
@echo A=$(A)
@echo B=$(B)
C=abc
运行make后
输出A等于空,B等于abc,因为在A定义的时候,C还没定义,而B是延时变量,注意C在B的后面仍然能生效的原因是因为make操作的时候是先将整个Makefile进行分析。
总结一下常用的变量定义:
:= #即时变量
= #延时变量
?= #延时变量,如果是第一次定义才有效,如果在前面该变量已定义则忽略这句话
+= #附加,跟C语言类似,它是即时还是延时,取决于前面的定义
4、makefile自带的一些函数 9.7
4.1、$(foreach var,list,text)
作用:对于list里面的每一个变量,执行text中的操作
例子,将A中的变量全部加上.o后缀
A= a b c
B= $(foreach f,$(A), $(f).o)
all:
@echo B= $(B)
root@zwg-virtual-machine:/home/zwg/weidongshan# make
B= a.o b.o c.o
4.2、$(filter pattern…,text)和(filter-out pattern…,text)
作用:在text中取出符合pattern格式的值或在text中取出不符合pattern格式的值
例子,从变量C里筛选出含有/和不含/的。
C=a b c d/
D=$(filter %/,$(C))
E=$(filter-out %/,$(C))
all:
@echo D= $(D)
@echo E= $(E)
root@zwg-virtual-machine:/home/zwg/weidongshan# make
D= d/
E= a b c
4.3、$(wildcard pattern)
作用:在pattern中定义了文件名的格式,wildcard函数取出当前目录下与格式相符合的文件(如果有的话)
例子,假设当前目录下有a.c b.c c.c 三个文件
files=$(wildcard *.c)#将当前目录下的.c文件的名字赋值给files
files2=a.c b.c c.c d.c e.c
flies3=$(wildcard $(files2))#检查变量files2中哪些文件是当前目录下真实存在的
all:
@echo files = $(files)
@echo files3= $(files3)
4.4、$(patsubst pattern,replacement,text)
作用:将text中的符合pattern格式的值全部替换为replacement格式
例子,将files2里面的.c全部变成.d
files2 = a.c b.c c.c e.c d.c abc
dep_files=$(patsubst %.c,%.d,$(files2))
all:
@echo dep_files = $(dep_files)
root@zwg-virtual-machine:/home/zwg/weidongshan# make
dep_files = a.d b.d c.d e.d d.d abc
5、关于Makefile中的头文件依赖 9.8
当前目录下的一些文件:
root@zwg-virtual-machine:/home/zwg/weidongshan# ls
a.c b.c c.c c.h Makefile
目前最新的Makefile如下:
test:a.o b.o c.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm *.o test
.PHONY: clean
背景:假设我新加一个文件c.h,里面做一些操作,比如定义一个宏,再在c.c里使用这个宏。那么我的c.o就同时依赖于c.c和c.h两个文件,我需要将c.h加入到Makefile的依赖项中,做法如下:
test:a.o b.o c.o
gcc -o test $^
c.o:c.c c.h #这条规则没有命令,只是告诉机器c.o的依赖项
%.o:%.c
gcc -c -o $@ $<
clean:
rm *.o test
.PHONY: clean
这样做很麻烦,因为我这里是已知c.c里面要用到c.h,可实际上如何确定某个.c文件里包含了哪些.h文件是很麻烦的。
解决:参考博客https://blog.csdn.net/qq1452008/article/details/50855810
指令1:使用gcc -M c.c 查看c.c的所有依赖项目(这里应该是系统自动能够查到某个c文件所对应的依赖文件,不需要人为处理,这也是为什么我们写c.c的头文件必须用同名的c.h的原因,方便系统查找):
root@zwg-virtual-machine:/home/zwg/weidongshan# gcc -M c.c
c.o: c.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
/usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/bits/long-double.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
/usr/include/x86_64-linux-gnu/bits/typesizes.h \
/usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
/usr/include/x86_64-linux-gnu/bits/types/FILE.h \
/usr/include/x86_64-linux-gnu/bits/libio.h \
/usr/include/x86_64-linux-gnu/bits/_G_config.h \
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
/usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h c.h
指令2:使用 gcc -M -MF c.d c.c,将c.c文件所有的依赖项存放到文件c.d中。
注意: 生成的从c.d或者是下面生成的c.o.d(本质上都是产生c.o所需要的依赖文件,只是命名不同),这些依赖文件不仅仅包含了.h文件,也有.c文件,注意看依赖文件的格式,也就是说makefile中只需要包含依赖文件就可以了,不再需要写
%.o:%.c
gcc -c -o $@ $<
root@zwg-virtual-machine:/home/zwg/weidongshan# gcc -M -MF c.d c.c
root@zwg-virtual-machine:/home/zwg/weidongshan# ls
001 a.c b.c c.c c.d c.h Makefile
root@zwg-virtual-machine:/home/zwg/weidongshan# cat c.d
c.o: c.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
/usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/bits/long-double.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
/usr/include/x86_64-linux-gnu/bits/typesizes.h \
/usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
/usr/include/x86_64-linux-gnu/bits/types/FILE.h \
/usr/include/x86_64-linux-gnu/bits/libio.h \
/usr/include/x86_64-linux-gnu/bits/_G_config.h \
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
/usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h c.h
指令3:使用指令 gcc -c -o c.o c.c -MD -MF c.d,在编译的同时,生成c.d
root@zwg-virtual-machine:/home/zwg/weidongshan# gcc -c -o c.o c.c -MD -MF c.d
root@zwg-virtual-machine:/home/zwg/weidongshan# ls
001 a.c b.c c.c c.d c.h c.o Makefile
修改Makefile:
步骤1: 在第一次make时生成依赖文件
test:a.o b.o c.o
gcc -o test $^
# c.o:c.c c.h
# %.o:%.c
# gcc -c -o $@ $<
%.o : %.c
gcc -c -o $@ $< -MD -MF .$@.d
clean:
rm *.o test
.PHONY: clean
修改后make一下,就会出现三个新的文件: .a.o.d .b.o.d .c.o.d,这三个文件里存放的是对应.c文件中的依赖项目
这样就不用在写类似于 c.o:c.c c.h 的语句。注意上面步骤1写的makefile只是生成了依赖文件,并没有将这些依赖文件加入到makefile的文本中,还需要步骤2.
步骤2: 条件编译,在第一次make时将产生的依赖文件加入makefile文本中
最终的成品如下,加入了是否包含依赖文件的一个条件判断,具体看9.8视频
objs=a.o b.o c.o
dep_files:=$(patsubst %,.%.d,$(objs))
dep_files:=$(wildcard $(dep_files))
test:$(objs)
gcc -o test $^
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o : %.c
gcc -c -o $@ $< -MD -MF .$@.d
clean:
rm *.o test
distclean:
rm $(dep_files)
.PHONY:clean
总结 :在第一次make时,makefile主要执行 gcc -c -o $@ < − M D − M F . < -MD -MF . <−MD−MF.@.d ,makefile第一次执行想生成比如说a.o时,虽然我们只指明了a.o依赖a.c,没有说a.o依赖a.h,但是还是可以正确编译,系统会自动找到a.h,也就是说第一次make实际上不需要对依赖文件进行处理。但是这个a.h不能改变,自动寻找a.c对应依赖a.h猜测是系统的功能,而自动判断依赖文件是否比目标文件更“新”猜测是makefile本身的功能,这个功能的实现需要将具体依赖文件的文本名字写到makefile文本中,所以如果第一次make以后,改变了a.h,第二次make将识别不到a.h的改变。按照上面的makefile,第二次make时,就会将第一次make生成的依赖文件包含进makefile文本中,这样就没问题了。
6、Makefile中的CFLAGS 9.8
6.1 编译器系统目录
我们的程序一般包含两种头文件,一种是自己写的(这种一般和对应的.c文件在同一个目录,或者在一个统一的自己创建的include目录下),另一种是标准库的头文件,比如#include <stdio.h>,那么在make时,编译器如何知道该在那里找库的头文件呢?
如果时Linux上的gcc编译器,默认是在/usr/include
如果是用交叉编译工具arm-linux-gcc,则是在其存放的目录下面去找,那么怎样找到这个目录呢?命令行键入命令:
arm-linux-gcc ^C
echo $PATH
就会打印出文件路径,再退回打印的路径的上一个目录,就是了。
上面讲的是库头文件,库文件也是一样的。
可以人为改变编译器的系统目录(就是存放库头文件和库文件的目录)。方法是设置CFLAGS 的 -I 和 -F选项。
6.2 设置CFLAGS,进行编译优化,改变编译器系统目录
objs=a.o b.o c.o
dep_files:=$(patsubst %,.%.d,$(objs))
dep_files:=$(wildcard $(dep_files))
CFLAGS= -Werror -I
#说明CFLAGS的内容,其中-Werror是在编译时将所有的warn都当做error来处理,推荐这样做,可以避免很多错误
#其中-I是指定编译器搜索的默认头文件目录,比如-I.表示默认头文件目录在当前目录
#一般我们习惯将所有的.h头文件放到一个include文件夹里,就可以用 -Iinclude 来指定这个文件夹
test:$(objs)
gcc -o test $^
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o : %.c
gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d #将CFLAGS加入到编译过程中
clean:
rm *.o test
distclean:
rm $(dep_files)
.PHONY:clean
7、修改Makefile以支持一个大型工程
之前讲解的都是对一堆.c文件的处理,而且这些.c文件都在都一个文件目录下,在一个有层次地工程项目中,各种源文件一般都是分布在不同层次的目录中,比如uboot和Linux,现在修改makefile以支持这种工程,主要参考韦东山第三期视频:4_数码相框_编写通用的Makefile:46分钟往后的内容,之前的不用看,思想主要是仿照着Linux的makefile写。
7.1 工程结构及总体认知
这里虚拟一个工程项目,假设所有文件都在目录total下,total目录下有一个顶层Makefile文件,一个Makefile.build文件,一个main.c文件,dir_a,dir_b,dir_c三个子目录用于存放.c源文件,还有一个include子目录,里面存放所有.c文件对应的.h文件。三个子目录里分布有两个个.c文件和一个Makefile文件,.三个子目录下的c文件的名字为dir_x_y.c,其中x可以是a、b、c,y可以是1、2,具体如下:
顶层目录total
Makefile
Makefile.build
mian.c
include
各种自己写的.h文件
dir_a
dir_a_1.c
dir_a_2.c
Makefile
dir_a_a
dir_a_a_1.c
Makefile
dir_b
dir_b_1.c
dir_b_2.c
Makefile
dir_c
dir_c_1.c
dir_c_2.c
Makefile
现在说一下整体make过程的总体流程,我们在顶层total目录下键入make命令,系统自动执行total/Makefile文件,total/Makefile文件中会指明当前total目录下的目标程序有main.o,以及需要进入dir_abc三个子目录去执行子目录的Makefile。
#顶层total目录下Makefile部分内容
obj-y += main.o
obj-y += dir_a/
obj-y += dir_b/
obj-y += dir_c/
系统不会先处理total下的目标文件,而是先执行子目录的Makefile(因为顶层的main依赖子目录),比如会先去dir_a目录下执行dir_a的子Makefile,这里dir_a目录下还有子子目录dir_a_a,则继续先执行子子目录下的Makefile。每个层级下的每个Makefile执行完后都会生成一个对应的built-in.o文件,用以表示该目录下生成的目标文件,该built-in.o文件又会作为一部分去组成上一级目录的built-in.o,最终total目录下的Makefile将dir_a、dir_b、dir_c三个子目录下的built-in.o(其中dir_a的built-in.o包含了dir_a_a的built-in.o)和main.o一起再次组合成total目录下的built-in.o,最后这个total下的built-in.o再和其他需要链接的文件一起组成最终可执行目标文件,命名为final。
每个目录下built-in.o文件的建立依赖于各个目录中的Makefile去调用顶层的total/Makefile.build文件。
7.2 顶层Makefile
下面贴出Makefile的原文,逐行分析。
CROSS_COMPILE = arm-linux- #延时变量CROSS_COMPILE,用于指定交叉编译器
AS = $(CROSS_COMPILE)as #为一些常见的编译命令起别名,以下几行都是
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
export AS LD CC CPP AR NM #这些别名只是在当前顶层Makefile中定义了,需要export导出,使得子目录下的子Makefile也能用
export STRIP OBJCOPY OBJDUMP
CFLAGS := -Wall -Werror -O2 -g #打印出所有警告信息,这一条不重要,可有可无
CFLAGS += -I $(shell pwd)/include # $(shell pwd)表示执行pwd这个shell命令,该命令得到当前目录,比如当前目录为/total,-I表示改变
#编译器寻找头文件的默认目录,这句话总体表示将寻找头文件的默认目录改为/total/include,我们在开发的时候将写的.h文件都放到这个目录。疑问:
#那像stdio.h这种库头文件在哪里去找呢?
LDFLAGS := -lm -lfreetype -lts -lpthread -ljpeg #链接参数,指明要链接的库,这里lm表示lib math,数学库,从左到右分别是:数学库、#freetype库(用于显示矢量字体)、触摸屏库(支持触摸屏操作)、pthread线程库、jpeg图片库(解析jpeg格式图片)
export CFLAGS LDFLAGS
TOPDIR := $(shell pwd) #指出顶层目录就是当前目录
export TOPDIR
TARGET := final #最终目标可执行文件的名字,随便取
#obj-y是最终想得到的所有目标文件的集合,main.o放在最前面
obj-y += main.o
#下面三条语句表示进入到dir三个子目录,分别执行其中的子Makefile,如果子目录下面还有子子目录,就在子目录中同样操作,去调用子子目录的#Makefile,而且需要注意的是,如果子目录发现确实有子子目录,是优先去执行子子目录的Makefile,执行完后再返回执行子目录的Makefile
obj-y += dir_a/
obj-y += dir_b/
obj-y += dir_c/
#all是和clean等一样的伪目标,make会将第一个出现的目标作为默认目标,就是只执行make不加目标名的时候,第一个目标名通常是all。
#也就是说执行make就等于在执行make all。
all :
#make -C表示递归调用子目录中的Makefile,-C选项后跟目录,表示到该目录下执行该目录的Makefile,然后从该目录递归往下调用
#make -C ./ 表示从当前目录开始,也就是从total目录开始
#-f指定子目录中的文件(文件名可以随意,不一定用Makefile作为文件名)作为Makefile,比如total/Makefile.build
#也就是说,实际上每个目录,包括顶层目录,最终作为真正意义上的Makefile去执行的其实是total/Makefile.build,每个目录都会调用它
#作为该目录的Makefile,total/Makefile.build中会先将obj-y等变量赋空值,然后会包含当前目录下的Makefile,这样就能识别当前目录
#的obj-y是多少,以及需要进入哪些子目录。一旦执行make,会首先执行顶层的Makefile,也即是现在所处的这个文件,然后会首先想生成
#all这个目标,进而去执行make -C ./ -f $(TOPDIR)/Makefile.build这条语句,转而去在那个目录下递归执行total/Makefile.build,
#total/Makefile.build中会包含各个目录下原本的Makefile文档中内容。可以把所有built—in.o的生成比作递归函数的调用
#total/Makefile.build就是具体执行的递归函数,函数返回值是built—in.o
make -C ./ -f $(TOPDIR)/Makefile.build
#在顶层目录中将最终生成的built-in.o文件和其他库文件一起链接成可执行文件final
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
7.3 Makefile.build文件讲解
PHONY := __build
#(1)首先想生成第一个目标__build,下面有__build的依赖
__build:
#先定义obj-y和subdir-y 两个变量,赋值为空,obj-y存当前目录下的Makefile文件中定义的obj-y
#subdir-y存放当前目录下的Makefile文件中定义的需要进一步调用的下层目录
obj-y :=
subdir-y :=
#包含当目录下的Makefile文本,假设当前文本包含obj-y := a.o b.o c/ d/
include Makefile
#这句话将当前目录下Makefile定义的obj-y(假设为obj-y := a.o b.o c/ d/)中带斜杠的选出来去掉斜杠赋给subdir-y
#下面两句执行完后,最终subdir-y的值是 c和d
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
# 将刚才得到的c和d,变成c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# 将obj-y := a.o b.o c/ d/带斜杆的提出,此时cur_objs就等于a.o和b.o
cur_objs := $(filter-out %/, $(obj-y))
# 依赖文件的处理,之前讲过
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
ifneq ($(dep_files),)
include $(dep_files)
endif
PHONY += $(subdir-y)
# (2)当前目录下的__build 依赖于子目录的调用结果以及本目录生成的built-in.o
__build : $(subdir-y) built-in.o
# (3)如果有子目录,递归往下调用
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
#本目录生成的built-in.o依赖于当前目录的.o文件,和子目录生成的built-in.o文件
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
dep_file = .$@.d
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
.PHONY : $(PHONY)
7.4 下层Makefile
下面各层的Makefile都很简单,就是包含自身的.c文件对应的.o文件,如果有下下层的话,就包含下下层的Makefile
dir_a目录下的Makefile
obj-y += dir_a_1.o
obj-y += dir_a_2.o
obj-y += dir_a_a/
dir_b目录下的Makefile
obj-y += dir_b_1.o
obj-y += dir_b_2.o
dir_c目录下的Makefile
obj-y += dir_c_1.o
obj-y += dir_c_2.o
dir_a_a目录下的Makefile
obj-y += dir_a_a_1.o