提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
在Linux中编译程序需要使用make命令,而make则依赖于Makefile文件。在实际的使用中,Makefile关注于项目的构建过程,而GCC则关注于源代码的编译。两者在软件开发中各有其重要作用,通常一起使用以完成项目的编译和构建任务。
一、Makefile的基本知识
Makefile是一个用于自动化编译和构建程序的工具,它包含一个或多个规则,每个规则说明如何基于一个或多个文件生成一个或多个输出文件。这些规则定义了哪些文件是源文件,哪些文件是目标文件,以及如何使用编译器或链接器从源文件生成目标文件。Makefile的基本结构包括目标、依赖和命令三部分。目标通常是要生成的文件名,依赖是用来生成目标的输入文件名,命令是生成目标时需要运行的命令。使用Makefile的好处是自动化了构建过程,只需要简单地执行make命令,Makefile就会按照定义的规则自动完成编译和链接等操作,从而生成最终的可执行文件。此外,Makefile还可以方便地管理多个源文件之间的依赖关系,以及处理复杂的构建任务。在编写Makefile时,需要遵循一定的语法和规则,以确保其正确性和可读性。同时,还需要根据具体的项目需求来定制Makefile,以满足不同的构建需求。
1. Makefile的引入
我们的.c源文件转化为可执行文件的过程大致如下图所示:
这里首先需要提到无敌的“gcc -o”命令,继续以hello.c文件为例:
gcc -o hello hello.c
这将产生一个可以在“当前环境”下执行的hello文件。此时,gcc -o命令独自完成了包括预处理,编译、汇编和连接的过程。但是由于该命令每次都会将所有文件重新编排一遍,故而在大型项目时直接全编译不太合理,此时就需要我们逐步编译。
2. Makefile的基本语法
2.1 基本规则
目标:[依赖1] [依赖2]
[tab]命令
//例子:
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
若不存在目标或者依赖的更新时间晚于目标时,则执行该文档。
2.2 通配符:%.o
- $@ 表示规则中的目标
- $^ 表示规则中的所有依赖, 组成一个列表, 以空格隔开, 如果这个列表中有重复的项则消除重复项
- $< 表示规则中的第一个依赖
- $? 第一变化的依赖
目标:[依赖1] [依赖2]
[tab]命令
//例子:
test:a.o b.o
gcc -o test a.o b.o
%.o:%.c
gcc -c -o $@ $<
2.3 PHONY
在Makefile中,PHONY是一个特殊的标记,用于指明一个目标是伪目标(phony target)。伪目标并不是实际要生成的文件,而是执行某些特定的命令或操作。使用PHONY可以避免当Makefile中的目标名称与实际文件名冲突时,导致命令不被执行的问题。具体来说,当Makefile中的目标与实际文件重名时,如果没有将目标定义为PHONY,make工具会误认为目标文件已经存在且是最新的,从而不会执行相应的命令。而通过将目标定义为PHONY,可以确保无论是否存在同名文件,make都会执行相应的命令。此外,使用PHONY还可以提高make的执行效率,因为将目标定义为PHONY后,make不会试图寻找该目标的隐含规则,从而减少了不必要的搜索和匹配操作。例如:
# 声明clean为伪文件
.PHONY:clean
clean:
# shell命令前的 - 表示强制这个指令执行, 如果执行失败也不会终止
-rm $(obj) $(target)
echo "hello, 我是测试字符串"
2.4 即使变量和延时变量
A = XXX;//定义时赋值
B :=XXX; //使用时再赋值
C ?=XXX; //第一次定义时才起效(若已定义过则直接忽略)
D +=XXX; //附加,取决与前面定义为哪种类型
makefile的使用:make [目标/空]:目标:执行该目标; 空:执行文件中第一个目标
二、 Makefile的函数
3.1 $(foreach var,list,text)
具体而言,$(foreach var, list, text)函数会将列表list中的每个元素依次赋值给变量var,并根据每个元素生成一段文本text。然后,将所有生成的文本段连接在一起,并返回结果。
list := a c b
text := $(foreach f,$(list),.$(f).o)
all:
@echo text = $(text)
//输出结果为: a.o c.o b.o
3.2 ( f i l t e r p a t t e r n , t e x t ) 和 (filter pattern,text)和 (filterpattern,text)和(filter-out pattern,text)
二者分别是指在text中挑选出符合/不符合pattern格式的值。
list := a c b d/
A := $(filter %/,text)
B := $(filter-out %/,text)
all:
@echo A = $(A)
@echo B = $(B)
//输出结果为:A= d/
// B= a c b
3.3 $(wildcard pattern)
用于匹配指定模式的而文件列表:
1、具体来说,它会返回一个包含所有匹配文件名的列表(包括文件路径),这些文件名之间用空格分开。
2、模式可以包含通配符,如*(表示匹配任意长度的任意字符)和?(表示匹配任意单个字符)等,用于匹配文件名中的字符。
值得注意的是,pattern只能是该文件列表下的。
list := a c b d/
text := $(foreach f,$(list),$(f).o)
A := $(wildcard *.o)
all:
@echo A = $(A)
//输出结果为: a.o c.o b.o
3.4 $(patsubst pattern,replacement,text)
用于替换文本中匹配某个模式的部分 ,会在文本text中查找与给定模式pattern匹配的部分,(其中pattern可以使用通配符%来匹配任意长度的任意字符),并将其替换为replacement,它返回替换后的新文本。
list := a.c c.c
A := $(patsubst %.c,/%.o,$(list))
all:
@echo A = $(A)
//输出结果为: a.o c.o
三、Makefile的编写
1.编写的核心
- Makefile中要确定被编译的文件、目录,Makefile始终包含于Makefile_build中。
- 顶层目录和各个子目录下均需要存在Make_file文件。
2.通用模板解析
在顶层目录下执行make,导致顶层Makefile的第一个目标all被处理,并进入到Makefile_build文件中继续执行命令。
//Makefile(top)
obj-y += xxx.o //读取当前目录下.c文件
obj-y += /yyy //读取子目录(sub)
all : start_recursive_build $(TARGET)
@echo $(TARGET) has been built!
start_recursive_build:
make -C ./ -f $(TOPDIR)/Makefile.build //使用Makefile.build处理顶层目录
在build中具体编译子目录和当前目录下的.c文件并链接生成build-in.o文件
//Makefile_build(top)
PHONY := __build
__build:
//清零变量
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=
//包含底层模块中的Makefile(top),里面有各种obj-y,确定文件目录
include Makefile
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
//预先处理子目录a/得到子目录下的build-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# 得到子目录下的.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)
__build : $(subdir-y) built-in.o
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
dep_file = .$@.d
%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
.PHONY : $(PHONY)
执行完这条指令后会返回到Makefile(top)中,继续编译当前目录(顶层目录)中的c文件,再将生成的o文件和子目录产生的build-in.o链接生成当前目录下的build-in.o(top)
//Makefile(top)
tart_recursive_build:
make -C ./ -f $(TOPDIR)/Makefile.build
$(TARGET) : built-in.o
$(CC) -o $(TARGET) built-in.o $(LDFLAGS)
最后便可以链接到所有目录下的.o文件件到一个app中。