Makefile学习总结
目录
- Makefile学习总结
- 1. Makefile介绍
- 2. Makefile规则
- 3. Makefile文件里的赋值方法
- 4. Makefile常用函数
- 4.1 字符串替换和分析函数
- 4.2 文件名函数
- 4.3 其他函数
- 5. Makefile使用示例
- 6、多级目录通用Makefile Demo
- 6.1 一般通用Makefile的设计思想
- 6.2 Demo分析
参考教程:
韦东山老师教程
《跟我一起写Makefile》文档
Makefile官方文档
GCC参考文章
1. Makefile介绍
Makefile是一种用于自动化构建过程的脚本文件,广泛应用于软件开发中。它定义了如何从源代码构建目标文件(如可执行文件或库文件)的一系列规则和依赖关系。Makefile通常与make工具一起使用,make是一个命令行工具,它可以解析Makefile中的指令并执行相应的构建任务。
Makefile概念:
- 目标(Target):Makefile 中的一个目标通常是一个文件,它是由一系列依赖文件通过一个或多个命令生成的。最终的目标文件通常是可执行文件或库文件。
- 依赖(Dependencies):目标文件的生成需要依赖其他文件(如源代码文件或其他目标文件),这些文件被称为依赖文件。
- 命令(Commands):用于生成目标文件的一系列 shell 命令。
目标(target)…: 依赖(prerequiries)…
<tab>命令(command)
如果“依赖文件”比“目标文件”更加新,那么执行“命令”来重新生成“目标文件”。
命令被执行的2个条件:依赖文件比目标文件新,或是目标文件还没生成。
Makefile组成:
- 目标:最终希望生成的文件。
- 依赖文件:生成目标所需的文件。
- 命令:用于生成目标的一系列 shell 命令。
- 变量:用于简化 Makefile 编写和维护的变量。
- 模式规则(Pattern Rules):自动处理常见文件扩展名转换的规则。
- 隐含规则(Implicit Rules):Make 自带的一些预定义规则,用于简化 Makefile 编写。
2. Makefile规则
每个命令行前面必须是一个Tab字符,即命令行第一个字符是Tab。这是容易出错的地方。
通常,如果一个依赖发生了变化,就需要规则调用命令以更新或创建目标。但是并非所有的目标都有依赖,例如,目标“clean”的作用是清除文件,它没有依赖。
规则一般是用于解释怎样和何时重建目标。make首先调用命令处理依赖,进而才能创建或更新目标。当然,一个规则也可以是用于解释怎样和何时执行一个动作,即打印提示信息。
一个Makefile文件可以包含规则以外的其他文本,但一个简单的Makefile文件仅仅需要包含规则。虽然真正的规则比这里展示的例子复杂,但格式是完全一样的。
例如Makefile:
hello : hello.c
gcc -o hello hello.c
执行make
命令时,仅当hello.c文件比hello文件新,才会执行命令gcc –o hello hello.c
生成可执行文件hello;如果还没有hello文件,这个命令也会执行。
3. Makefile文件里的赋值方法
变量的定义语法形式如下:
immediate = deferred
immediate ?= deferred
immediate := immediate
immediate += deferred or immediate
define immediate
deferred
endef
在GNU make中对变量的赋值有两种方式:延时变量、立即变量。区别在于它们的定义方式和扩展时的方式不同,前者在这个变量使用时才扩展开,意即当真正使用时这个变量的值才确定;后者在定义时它的值就已经确定了。使用=
,?=
定义或使用define指令定义的变量是延时变量;使用:=
定义的变量是立即变量。需要注意的一点是,?=
仅仅在变量还没有定义的情况下有效,即?=
被用来定义第一次出现的延时变量。
对于附加操作符+=
,右边变量如果在前面使用:=
定义为立即变量则它也是立即变量,否则均为延时变量。
- 简单赋值(
=
):右侧的值在赋值时立即展开。 - 条件赋值(
?=
):只有在变量未定义时才赋值。 - 双冒号赋值(
:=
):右侧的值在使用变量时展开。 - 追加赋值(
+=
):右侧的值追加到已有值的末尾。
一个综合性的Makefile示例,展示了这四种赋值方法的使用:
CC = gcc
CFLAGS = -Wall
CFLAGS += -g
CFLAGS ::= $(patsubst %.c,%.o,$(wildcard *.c))
CFLAGS ?= -O2
SOURCES = main.c foo.c bar.c
LDFLAGS ?= -lm
all: program
program: $(SOURCES:.c=.o)
$(CC) $(CFLAGS) $(LDFLAGS) -o program $(SOURCES:.c=.o)
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f program *.o
输出:
CC
的值为gcc
CFLAGS
的值为-Wall -g
(+=
追加赋值后,:=
的赋值被覆盖)SOURCES
的值为main.c foo.c bar.c
LDFLAGS
的值为-lm
4. Makefile常用函数
函数调用的格式如下:
$(function arguments)
这里function
是函数名,arguments
是该函数的参数。参数和函数名之间是用空格或Tab隔开,如果有多个参数,它们之间用逗号隔开。这些空格和逗号不是参数值的一部分。内核的Makefile中用到大量的函数,现在介绍一些常用的。
4.1 字符串替换和分析函数
1、$(subst from,to,text)
在文本text
中使用to
替换每一处from
。
比如:
$(subst ee,EE,feet on the street)
结果为:
fEEt on the strEEt
2、$(patsubst pattern,replacement,text)
寻找text
中符合格式pattern
的字,用replacement
替换它们。pattern
和replacement
中可以使用通配符。
比如:
$(patsubst %.c,%.o,x.c.c bar.c)
结果为:
x.c.o bar.o
3、$(strip string)
去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。
比如:
$(strip a b c )
结果为:
a b c
4、$(findstring find,in)
在字符串in
中搜寻find
,如果找到,则返回值是find
,否则返回值为空。
比如:
$(findstring a,a b c)
$(findstring a,b c)
结果为:
a
和``(空字符串)
5、$(filter pattern…,text)
返回在text
中由空格隔开且匹配格式pattern...
的字,去除不符合格式pattern...
的字。
比如:
$(filter %.c %.s,foo.c bar.c baz.s ugh.h)
结果为:
foo.c bar.c baz.s
6、 $(filter-out pattern…,text)
返回在text
中由空格隔开且不匹配格式pattern...
的字,去除符合格式pattern...
的字。它是函数filter的反函数。
比如:
$(filter %.c %.s,foo.c bar.c baz.s ugh.h)
结果为:
ugh.h
7、$(sort list)
将list
中的字按字母顺序排序,并去掉重复的字。输出由单个空格隔开的字的列表。
比如:
$(sort foo bar lose)
结果为:
bar foo lose
4.2 文件名函数
1、$(dir names…)
抽取names...
中每一个文件名的路径部分,文件名的路径部分包括从文件名的首字符到最后一个斜杠(含斜杠)之前的一切字符。
比如:
$(dir src/foo.c hacks)
结果为:
src/ ./
2、$(notdir names…)
抽取names...
中每一个文件名中除路径部分外一切字符(真正的文件名)。
比如:
$(notdir src/foo.c hacks)
结果为:
foo.c hacks
3、$(suffix names…)
抽取names...
中每一个文件名的后缀。
比如:
$(suffix src/foo.c src-1.0/bar.c hacks)
结果为:
.c .c
4、$(basename names…)
抽取names...
中每一个文件名中除后缀外一切字符。
比如:
$(basename src/foo.c src-1.0/bar hacks)
结果为:
src/foo src-1.0/bar hacks
5、$(addsuffix suffix,names…)
参数names...
是一系列的文件名,文件名之间用空格隔开;suffix是一个后缀名。将suffix(后缀)的值附加在每一个独立文件名的后面,完成后将文件名串联起来,它们之间用单个空格隔开。
比如:
$(addsuffix .c,foo bar)
结果为:
foo.c bar.c
6、$(addprefix prefix,names…)
参数names
是一系列的文件名,文件名之间用空格隔开;prefix是一个前缀名。将preffix(前缀)的值附加在每一个独立文件名的前面,完成后将文件名串联起来,它们之间用单个空格隔开。
比如:
$(addprefix src/,foo bar)
结果为:
src/foo src/bar
7、$(wildcard pattern)
参数‘pattern’是一个文件名格式,包含有通配符(通配符和shell中的用法一样)。函数wildcard的结果是一列和格式匹配的且真实存在的文件的名称,文件名之间用一个空格隔开。
比如若当前目录下有文件 1.c、2.c、1.h、2.h,则:
c_src := $(wildcard *.c)
结果为:
1.c 2.c
4.3 其他函数
1、$(foreach var,list,text)
前两个参数,var
和list
将首先扩展,注意最后一个参数text
此时不扩展;接着,list
扩展所得的每个字,都赋给var
变量;然后text
引用该变量进行扩展,因此text
每次扩展都不相同。
函数的结果是由空格隔开的text
在list
中多次扩展后,得到的新list
,就是说:text
多次扩展的字串联起来,字与字之间由空格隔开,如此就产生了函数foreach的返回值。
下面是一个简单的例子,将变量‘files’的值设置为‘dirs’中的所有目录下的所有文件的列表:
dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
这里text
是$(wildcard $(dir)/*)
,它的扩展过程如下:
- 第一个赋给变量dir的值是
a
,扩展结果为$(wildcarda/*)
; - 第二个赋给变量dir的值是
b
,扩展结果为$(wildcardb/*)
; - 第三个赋给变量dir的值是
c
,扩展结果为$(wildcardc/*)
; - 如此继续扩展。
这个例子和下面的例有共同的结果:
files := $(wildcard a/* b/* c/* d/*)
2、$(if condition,then-part[,else-part])
首先把第一个参数condition
的前导空格、结尾空格去掉,然后扩展。如果扩展为非空字符串,则条件condition
为真
;如果扩展为空字符串,则条件condition
为假
。
如果条件condition
为真
,那么计算第二个参数then-part
的值,并将该值作为整个函数if的值。
如果条件condition
为假
,并且第三个参数存在,则计算第三个参数else-part
的值,并将该值作为整个函数if的值;如果第三个参数不存在,函数if将什么也不计算,返回空值。
**注意:**仅能计算then-part
和else-part
二者之一,不能同时计算。这样有可能产生副作用(例如函数shell的调用)。
3、$(origin variable)
变量variable
是一个查询变量的名称,不是对该变量的引用。所以,不能采用$
和圆括号的格式书写该变量,当然,如果需要使用非常量的文件名,可以在文件名中使用变量引用。
函数origin的结果是一个字符串,该字符串变量是这样定义的:
'undefined' :如果变量‘variable’从没有定义;
'default' :变量‘variable’是缺省定义;
'environment' :变量‘variable’作为环境变量定义,选项‘-e’没有打开;
'environment override' :变量‘variable’作为环境变量定义,选项‘-e’已打开;
'file' :变量‘variable’在 Makefile 中定义;
'command line' :变量‘variable’在命令行中定义;
'override' :变量‘variable’在 Makefile 中用 override 指令定义;
'automatic' :变量‘variable’是自动变量
4、$(shell command arguments)
函数shell是make与外部环境的通讯工具。函数shell的执行结果和在控制台上执行command arguments
的结果相似。不过如果command arguments
的结果含有换行符(和回车符),则在函数shell的返回结果中将把它们处理为单个空格,若返回结果最后是换行符(和回车符)则被去掉。
比如当前目录下有文件1.c、2.c、1.h、2.h,则:
c_src := $(shell ls *.c)
结果为:
1.c 2.c
5. Makefile使用示例
示例背景:假设当前目录下有main.c、sub.c、sub.h三个文件,main.c中调用了sub.h中声明的sub.c中的函数,main.c引用了sub.h头文件
在当前目录下编写Makefile
1、第1个Makefile,简单粗暴,效率低:
test : main.c sub.c sub.h
gcc -o test main.c sub.c
2、第2个Makefile,效率高,相似规则太多太啰嗦,不支持检测头文件:
test : main.o sub.o
gcc -o test main.o sub.o
main.o : main.c
gcc -c -o main.o main.c
sub.o : sub.c
gcc -c -o sub.o sub.c
clean:
rm *.o test -f
3、第3个 Makefile,效率高,精炼,不支持检测头文件:
test : main.o sub.o
gcc -o test main.o sub.o
%.o : %.c
gcc -c -o $@ $<
clean:
rm *.o test -f
4、第4个Makefile,效率高,精炼,支持检测头文件(但是需要手工添加头文件规则):
test : main.o sub.o
gcc -o test main.o sub.o
%.o : %.c
gcc -c -o $@ $<
sub.o : sub.h
clean:
rm *.o test -f
5、第5个Makefile,效率高,精炼,支持自动检测头文件:
objs := main.o sub.o
test : $(objs)
gcc -o test $^
#
需要判断是否存在依赖文件
# .main.o.d .sub.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))
# 把依赖文件包含进来
ifneq ($(dep_files),)
include $(dep_files)
endif
%.o : %.c
gcc -Wp,-MD,.$@.d -c -o $@ $<
clean:
rm *.o test -f
distclean:
rm $(dep_files) *.o test -f
6、最终版
01 src := $(shell ls *.c)
02 objs := $(patsubst %.c,%.o,$(src))
03
04 test: $(objs)
05 gcc -o $@ $^
06
07 %.o:%.c
08 gcc -c -o $@ $<
09
10 clean:
11 rm -f test *.o
上述Makefile中$@
、$^
、$<
称为自动变量。$@
表示规则的目标文件名;$^
表示所有依赖的名字,名字之间用空格隔开;$<
表示第一个依赖的文件名。%
是通配符,它和一个字符串中任意个数的字符相匹配。
一行行地分析:
-
第1行src变量的值为
main.c sub.c
。 -
第2行objs变量的值为
main.o sub.o
,是src变量经过patsubst函数处理后得到的。 -
第4行实际上就是:
-
test : main.o sub.o
-
目标test的依赖有二:main.o和sub.o。开始时这两个文件还没有生成,在执行生成test的命令之前先将main.o、sub.o作为目标查找到合适的规则,以生成main.o、sub.o。
-
-
第7、8行就是用来生成main.o、sub.o的规则:
-
对于main.o这个规则就是:
-
main.o:main.c gcc -c -o main.o main.c
-
对于sub.o这个规则就是:
-
sub.o:sub.c gcc -c -o sub.o sub.c
-
这样,test的依赖main.o和sub.o就生成了。
-
-
第5行的命令在生成main.o、sub.o后得以执行。
-
在options目录下第一次执行make命令可以看到如下信息:
-
gcc -c -o main.o main.c gcc -c -o sub.o sub.c gcc -o test main.o sub.o
-
然后修改sub.c文件,再次执行make命令,可以看到如下信息:
-
gcc -c -o sub.o sub.c gcc -o test main.o sub.o
-
可见,只编译了更新过的sub.c文件,对main.c文件不用再次编译,节省了编译的时间。
-
6、多级目录通用Makefile Demo
参考源文件:《多级目录Makefile示例》
目录环境说明:
- 当前目录下有main.c、sub.c、Makefile(根目录)、Makefile.build、目录a、目录include
- 目录a下有sub2.c、sub3.c、Makefile(子目录)
- 目录include下有sub.h、sub2.h、sub3.h
6.1 一般通用Makefile的设计思想
- 在Makefile文件中确定要编译的文件、目录,比如:
obj-y += main.o
obj-y += a/
“Makefile”文件总是被“Makefile.build”包含的。
-
在Makefile.build中设置编译规则,有3条编译规则:
- 怎么编译子目录?进入子目录编译:
$(subdir-y): make -C $@ -f $(TOPDIR)/Makefile.build
- 怎么编译当前目录中的文件?
%.o : %.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
- 当前目录下的.o和子目录下的built-in.o要打包起来:
built-in.o : $(cur_objs) $(subdir_objs) $(LD) -r -o $@ $^
-
顶层Makefile中把顶层目录的built-in.o链接成APP:
$(TARGET) : built-in.o
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
6.2 Demo分析
1、子目录Makefile
# 定义DEBUG宏,在当前子目录下所有文件中都能够使用该宏
EXTRA_CFLAGS := -D DEBUG
# 定义DEBUG_SUB3宏,指定仅sub3.c才能使用该宏
CFLAGS_sub3.o := -D DEBUG_SUB3
obj-y += sub2.o
obj-y += sub3.o
#EXTRA_CFLAGS :=
#CFLAGS_file.o :=
#
#obj-y += file.o
#obj-y += subdir/
#
# "obj-y += file.o" 表示把当前目录下的file.c编进程序里,
# "obj-y += subdir/" 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。
# "EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
# "CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置
# 注意:
# 1. "subdir/"中的斜杠"/"不可省略
# 2. 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用
# 3. CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者组成xxx.c的编译选项
2、顶层目录Makefile
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
export STRIP OBJCOPY OBJDUMP
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include
LDFLAGS :=
export CFLAGS LDFLAGS
TOPDIR := $(shell pwd)
export TOPDIR
TARGET := test
obj-y += main.o
obj-y += sub.o
obj-y += a/
all : start_recursive_build $(TARGET)
@echo $(TARGET) has been built!
start_recursive_build:
make -C ./ -f $(TOPDIR)/Makefile.build
$(TARGET) : start_recursive_build
$(CC) -o $(TARGET) built-in.o $(LDFLAGS)
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)
# 它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,
# 主要是定义工具链前缀CROSS_COMPILE,
# 定义编译参数CFLAGS,
# 定义链接参数LDFLAGS,
# 这些参数就是文件中用export导出的各变量。
3、顶层目录Makefile.build
# 这里定义了一个伪目标__build,并且将其添加到了PHONY变量中。PHONY变量用来标记那些不是文件名的目标,而是用来触发一系列动作的伪目标。
# 伪目标:为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
# 例如:.PHONY : clean
# 只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:
# .PHONY: clean
# clean:
# rm *.o temp
PHONY := __build
__build:
# 初始化了一些变量,其中obj-y和subdir-y分别用于收集对象文件和子目录。
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=
# 包含Makefile,里面有:
# obj-y += main.o
# obj-y += sub.o
# obj-y += a/
include Makefile
# 这部分代码从obj-y中提取出所有以/结尾的条目(即子目录),并去掉最后的/字符,将结果存储到subdir-y变量中。
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
# 对于每个子目录,生成一个built-in.o文件的列表。
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# 这部分代码提取除了子目录之外的对象文件,并生成对应的依赖文件。dep_files变量存储了所有存在的依赖文件。
cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
# 如果存在依赖文件,则将它们包含进来,以便Make能够根据这些文件自动处理依赖关系。
ifneq ($(dep_files),)
include $(dep_files)
endif
# 将子目录声明为伪目标,目的是避免错误,如果不将子目录名称声明为伪目标,当用户尝试执行像make a这样的命令时,Make会尝试去找一个名为a的文件。如果没有找到这样的文件,Make可能会报告错误,即使实际上a是一个子目录。
PHONY += $(subdir-y)
# 定义了__build目标,它依赖于所有子目录的目标以及built-in.o文件。
__build : $(subdir-y) built-in.o
# 对于每个子目录,调用make命令进入该目录并使用相同的Makefile.build文件构建。
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
# built-in.o文件由当前目录的对象文件和子目录中的built-in.o文件链接而成。
built-in.o : $(subdir-y) $(cur_objs)
$(LD) -r -o $@ $(cur_objs) $(subdir_objs)
# 每个.c文件编译成.o文件时,也会生成一个依赖文件.d,其中包含了该.o文件的依赖项。
dep_file = .$@.d
# 描述了如何将一个.c源文件编译成一个.o目标文件。
# $(CC):gcc 顶层目录Makefile中定义
# $(CFLAGS):-Wall -O2 -g -I $(shell pwd)/include 顶层目录Makefile中定义
# $(EXTRA_CFLAGS):-D DEBUG 子目录Makefile中定义,其中D是define的意思
# $(CFLAGS_$@):这是一个条件编译标志,允许为特定的目标文件指定特定的编译选项。这里的$@是目标文件名(例如foo.o),因此CFLAGS_foo.o可以定义特定于foo.o的编译选项。
# -Wp,-MD,$(dep_file):这是一个预处理器标志,它告诉编译器生成依赖文件。-MD选项要求编译器生成依赖信息,并将其写入到标准错误输出。-Wp选项允许向预处理器传递选项,这里将-MD选项连同依赖文件的名称一起传递给预处理器。
# -c:告诉编译器仅编译并汇编源文件,但不进行链接。
# -o $@:指定输出文件的名称。在这里,‘$@代表目标文件(例如foo.o`),因此编译后的目标文件将保存在这个文件中。
# $<:依赖文件(即源文件),在这里是指.c文件(例如foo.c)。
%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
# 更新.PHONY目标,确保__build等伪目标被正确识别。
.PHONY : $(PHONY)
4、main.c
#include <stdio.h>
extern void sub_fun(void);
extern void sub2_fun(void);
void sub3_fun(void);
int main(int argc, char* argv[])
{
printf("Main fun!\n");
sub_fun();
sub2_fun();
sub3_fun();
return 0;
}
5、sub.c
#include <stdio.h>
#include "sub.h"
void sub_fun(void)
{
printf("Sub fun, A = %d!\n", A);
}
6、sub.h
#define A 1
void sub_fun(void);
7、sub2.c
#include <stdio.h>
#include <sub2.h>
void sub2_fun(void)
{
printf("Sub2 fun, B = %d!\n", B);
#ifdef DEBUG //此宏定义由子目录Makefile中“EXTRA_CFLAGS := -D DEBUG”定义并传递到sub2.c
printf("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
}
8、sub2.h
#define B 2
void sub2_fun(void);
9、sub3.c
#include <stdio.h>
#include <sub3.h>
void sub3_fun(void)
{
printf("Sub3 fun, C = %d!\n", C);
#ifdef DEBUG //此宏定义由子目录Makefile中“EXTRA_CFLAGS := -D DEBUG”定义并传递到sub3.c
printf("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
#ifdef DEBUG_SUB3 //次宏定义由子目录Makefile中“CFLAGS_sub3.o := -D DEBUG_SUB3”指定定义到sub3.c中
printf("It is only debug info for sub3.\n");
#endif
}
10、sub3.h
#define C 3
void sub3_fun(void);
make步骤分析:
- 对每层目录都使用Makefile.build规则编译
- 将子目录(a)下的所有.c文件(sub2.c、sub3.c)编译生成对应.o(sub2.o、sub3.o)文件,并将所有的.o(sub2.o、sub3.o)文件链接成built-in.o
- 回到顶层目录,将顶层目录中的所有.c(main.c、sub.c)文件编译生成对应.o(main.o、sub.o)文件,将顶层目录中所有的.o(main.o、sub.o)文件和所有子目录的built-in.o文件链接生成最终的built-in.o文件
- 最后使用最终的built-in.o文件生成可执行程序
针对有多个子目录或者多级子目录进行Makefile编译时,设计思想不改变,在此基础上进行扩展即可。