说明:
一个简易的Makefile入门,待我后续深入学习驱动和内核之后,会逐渐完善这个Makefile教程。
入门
Makefile 内容通常由以下三部分组成
<目标名称>:<前置依赖>
\t (Tab)<需要执行的命令>
定义变量
变量名 := hello.o \
main.o
自动推导
main.o: hello.h
hello.o: hello.h
声明伪目标:(防止目录下有同名文件导致命令失效)
# 定义变量 objects
objects := hello.o \
main.o
# 放在第一个的是默认目标
# 目标是编译出main文件 依赖hello.o 和 main.o 文件
# 编译的命令是gcc hello.o main.o -o main
main: ${objects}
gcc ${objects} -o main
# 目标是main.o 依赖main.c和hello.h
# 编译的命令是gcc -c main.c
main.o: main.c hello.h
gcc -c main.c
# 目标是hello.o 依赖hello.c和hello.h
# 编译的命令是gcc -c hello.c
hello.o: hello.c hello.h
gcc -c hello.c
# 伪目标(防止目录下有同名文件导致命令失效)
.PHONY : clean
clean: # 加一个 - 可以忽略错误,make clean不会报错
-rm ${objects} main
####################### 自动推导 ##############
# Makefile 内容通常由以下三部分组成
# <目标名称>:<前置依赖>
# \t <需要执行的命令>
# 定义变量 objects
objects := hello.o \
main.o
# 放在第一个的是默认目标
# 目标是编译出main文件 依赖hello.o 和 main.o 文件
# 编译的命令是gcc hello.o main.o -o main
main: ${objects}
gcc ${objects} -o main
# 目标是main.o 依赖main.c和hello.h
# 编译的命令是gcc -c main.c
main.o: hello.h
# 目标是hello.o 依赖hello.c和hello.h
# 编译的命令是gcc -c hello.c
hello.o: hello.h
# 伪目标(防止目录下有同名文件导致命令失效)
.PHONY : clean
clean:# 加一个 - 可以忽略错误,make clean不会报错
-rm ${objects} main
两个Makefile函数
$(foreach var, list, text)
解析:对list中的每一个元素,取出来赋值给var,然后把var改为text所描述的形式。
例子:
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // 最终 dep_files := .a.o.d .b.o.d
$(wildcard pattern)
pattern所列出的文件是否存在,把存在的文件都列出来。
例子:
src_files := $( wildcard *.c) // 最终 src_files 中列出了当前目录下的所有.c文件
标准模板
CC:=gcc
fopen_test: fopen_test.c
-$(CC) -o $@ $^
-./$@
-rm ./$@
说明:
- 有时编译器不只是gcc,我们将编译器定义为变量CC,当切换编译器时只需要更改该变量的定义,而无须更改整个Makefile
- $@相当于当前target目标文件的名称,此处为fopen_test
- $^相当于当前target所有依赖文件列表,此处为fopen_test.c
- ./$@的作用是执行目标文件
Makefile基础
核心语法
目标 (target): 依赖 (prerequisites)
<Tab键> 命令 (command)
- 目标:通常是要生成的文件名(如可执行文件或 .o 文件)
- 依赖:生成目标所需要的文件
- 命令:实际执行的动作(必须以Tab键开头,不能是空格)
示例:
# 最终生成名为 app 的程序
app: main.o tools.o
gcc main.o tools.o -o a
# 生成 main.o
main.o: main.c tools.h
gcc -c main.c
# 生成 tools.o
tools.o: tools.c tools.h
gcc -c tools.c
# 清理编译生成的文件
clean:
rm *.o app
# 命令解析
rm: 这是linux系统中的删除命令,它用于删除文件或目录
*.0: 所有以.o结尾的文件
test: 指定要删除的具体文件名
-f: 表示"force"(强制)
- 忽略不存在的文件:如果目录下没有.o文件或test文件,rm命令平时会报错(提示"File not found"),加上-f后,如果没有这些文件,它会保持陈默,不会报错中止
- 无需确认:它会跳过任何删除前的确认提示
执行原理
核心逻辑:依赖树
当执行make命令时,他会读取当前目录下的Makefile文件,并试图构建指定的“终极目标”
为了完成这个目标,make会在内存中构建一颗依赖树
- 目标 (Target): 你想要生成的文件(如可执行文件
app)。 - 依赖 (Dependencies): 生成该目标需要的文件(如
main.o,utils.o)。 - 命令 (Commands): 具体的构建动作(如
gcc -o app main.o ...)。
执行流程:自顶向下的推导
make的执行遵循后序遍历(从底层向上推导)的逻辑:
- 查找终极目标:默认执行第一个 Target
- 递归检查:检查该目标的依赖文件是否存在?
- 如果依赖文件也是其他规则的“目标”,则先跳转去处理那个目标
- 如果依赖文件不存在,且没有规则生成它,则报错。
- 最终落地:一直追踪到最底层的源文件
判断算法:时间戳对比
这是 make 智能的地方。它通过对比文件修改时间(Timestamp)来决定是否执行命令:
规则: 如果 目标文件 的时间戳 早于 任何一个 依赖文件 的时间戳,或者 目标文件 根本不存在,则认为该目标已“过时”,执行其对应的命令。
自动推导
make是一个功能强大的构建工具,他有自动推导的能力,make在进行编译的时候会使用一个默认的编译规则,按照默认规则完成对.c文件的编译,生成对应的.o文件。它使用命令 cc -c 来编译.c源文件。在Makefile中只要给出需要构建的目标文件名(一个.o文件),make会自动为这个.o文件寻找合适的依赖文件(对应的.c文件),并且使用默认的命令来构建这个目标文件。
自动推导的核心逻辑:隐含规则
make内部内置了一套隐含规则
逻辑: 如果它需要一个 foo.o 文件,但你没写规则,它会自动寻找 foo.c。如果找到了,它会自动调用类似 $(CC) $(CFLAGS) -c foo.c -o foo.o 的命令。
例子:
app: main.o utils.o
gcc -o app main.o utils.o
哪怕你没写如何生成 main.o 和 utils.o,make 也会自动执行以下隐藏动作:
- 发现需要
main.o-> 寻找main.c-> 执行cc -c main.c -o main.o。 - 发现需要
utils.o-> 寻找utils.c-> 执行cc -c utils.c -o utils.o。
变量
makefile中的变量分为三种:自定义变量,预定义变量和自动变量
自定义变量
用户可以定义自己的变量
变量名区分大小写,可以是任意字符串,不能含有”:”, “#”, “=”
用法:
# 变量名 = 变量值
# 变量名 := 变量值
# 变量名 ::= 变量值
CC = gcc
# 取出变量的值
# $(变量的名字)
$(CC)
# 示例:
files = main.cpp hello.cpp
main.o $(files)
预定义变量
在Makefile中有一些已经定义的变量,用户可以直接使用这些变量,不用进行定义。在进行编译的时候,某些条件下 Makefile 会使用这些预定义变量的值进行编译。这些预定义变量的名字一般都是大写的,经常采用的预定义变量如下:
自动变量
自动变量用来代表这些规则中的目标文件和依赖文件,并且它们只能在规则的命令中使用。
$@:①本条规则的目标名;②如果目标是归档文件的成员,则为归档文件名;③在多目标的模式规则中, 为导致本条规则方法执行的那个目标名;
$<:本条规则的第一个依赖名称
$?:依赖中修改时间晚于目标文件修改时间的所有文件名,以空格隔开
$^:所有依赖文件名,文件名不会重复,不包含order-only依赖
$+:类似上一个, 表示所有依赖文件名,包括重复的文件名,不包含order-only依赖
$|:所有order-only依赖文件名
$*:(简单理解)目标文件名的主干部分(即不包括后缀名)
$%:如果目标不是归档文件,则为空;如果目标是归档文件成员,则为对应的成员文件名
Makefile文件内容组成
一个Makefile文件通常由五种类型的内容组成:显式规则、隐式规则、变量定义、指令和注释
- 显式规则(explicit rules):显式指明何时以及如何生成或更新目标文件,显式规则包括目标、依赖和更新方法三个部分
- 隐式规则(implicit rules):根据文件自动推导如何从依赖生成或更新目标文件。
- 变量定义(variable definitions):定议变量并指定值,值都是字符串,类似C语言中的宏定义(#define),在使用时将值展开到引用位置
- 指令(directives):在make读取Makefile的过程中做一些特别的操作,包括:
- 读取(包含)另一个makefile文件(类似C语言中的#include)
- 确定是否使用或略过makefile文件中的一部分内容(类似C语言中的#if)
- 定义多行变量
- 注释(comments):一行当中 # 后面的内容都是注释,不会被make执行。make当中只有单行注释。如果需要用到#而不是注释,用#。
Makefile读取过程
make分为两个阶段来执行Makefile,第一阶段(读取阶段):
- 读取Makefile文件的所有内容
- 根据Makefile的内容在程序内建立起变量
- 在程序内构建起显式规则、隐式规则
- 建立目标和依赖之间的依赖图
第二阶段(目标更新阶段):
- 用第一阶段构建起来的数据确定哪个目标需要更新然后执行对应的更新方法
变量和函数的展开如果发生在第一阶段,就称作立即展开,否则称为延迟展开。立即展开的变量或函数在第一个阶段,也就是Makefile被读取解析的时候就进行展开。延迟展开的变量或函数将会到用到的时候才会进行展开,有以下两种情况:
- 在一个立即展开的表达式中用到
- 在第二个阶段中用到
显示规则中,目标和依赖部分都是立即展开,在更新方法中延迟展开
变量赋值
递归展开赋值
第一种方式就是直接使用 =,这种方式如果赋值的时候右边是其他变量引用或者函数调用之类的,将不会做处理,直接保留原样,在使用到该变量的时候再来进行处理得到变量值(Makefile执行的第二个阶段再进行变量展开得到变量值)
简单赋值(立即展开)
简单赋值使用 := 或 ::= ,这种方式如果等号右边是其他变量或者引用的话,将会在赋值的时候就进行处理得到变量值。(Makefile执行第一阶段进行变量展开)
简单赋值(立即展开)
简单赋值使用 := 或 ::= ,这种方式如果等号右边是其他变量或者引用的话,将会在赋值的时候就进行处理得到变量值。(Makefile执行第一阶段进行变量展开)
条件赋值
条件赋值使用 ?= ,如果变量已经定义过了(即已经有值了),那么就保持原来的值,如果变量还没赋值过,就把右边的值赋给变量。
追加
使用 += 在变量已有的基础上追加内容
Shell运行赋值
使用 != ,运行一个Shell指令后将返回值赋给一个变量
定义多行变量
define <varable_name> # 默认为 =
# 变量内容
endef
define <varable_name> :=
# 变量内容
endef
define <varable_name> +=
# 变量内容
endef
取消变量
清除一个变量
undefine <变量名> 如 undefine files, undefine objs
变量替换引用
语法:$(var:a=b),意思是将变量var的值当中每一项结尾的a替换为b
变量覆盖
所有在Makefile中的变量,都可以在执行make时能过指定参数的方式进行覆盖。
绑定目标的变量
Makefile中的变量一般是全局变量。也就是说定义之后在Makefile的任意位置都可以使用。但也可以将变量指定在某个目标的范围内,这样这个变量就只能在这个目标对应的规则里面保用
多目标与多规则
显示规则中一条规则可以有多个目标,多个目标可以是相互独立的目标,也可以是组合目标,用写法来区分
独立多目标
相互独立的多个目标与依赖之间直接用 : ,常用这种方式的有以下两种情况:
- 只需要写目标和依赖,不需要写方法的时候:block.o input.o scene.o : common.h
- 生成目标的方法写法一样的,只是依赖于目标不一样时:block.o: block.cpp common.h block.h color.h
g++ -c block.cpp
command.o: command.cpp command.h scene.h
g++ -c command.cpp
input.o: input.cpp common.h utility.inl
g++ -c input.cpp
main.o: main.cpp scene.h input.h test.h
g++ -c main.cpp
scene.o: scene.cpp common.h scene.h utility.inl
g++ -c scene.cpp
test.o: test.cpp test.h
g++ -c test.cpp所有.o文件的生成都是用的同一方法g++ -c <文件名>如果不考虑依赖源文件进行更新时,可以进行简写如下:block.o command.o input.o main.o scene.o test.o : common.h block.h command.h …
g++ -c $(@:%.o=%.cpp)这样的简写可以减少内容的书写量,但是不利于将每个目标与依赖分别对应。
组合多目标
多目标与依赖之前用&:,这样的多个目标称为组合目标。与独立多目标的区别在于,独立多目标每个目标的更新需要单独调用一次更新方法。而组合多目标调用一次方法将更新所有目标
block.o input.o scene.o &: block.cpp input.cpp scene.cpp common.h
g++ -c block.cpp
g++ -c input.cpp
g++ -c scene.cpp
所有目标的更新方法都写到其中,每次更新只会调用一次。
同一目标多条规则
同一目标可以对应多条规则。同一目标的所有规则中的依赖会被合并。但如果同一目标对应的多条规则都写了更新方法,则会使用最新的一条更新方法,并且会输出警告信息。
同一目标多规则通常用来给多个目标添加依赖而不用改动已写好的部分。
input.o: input.cpp utility.inl
g++ -c input.cpp
main.o: main.cpp scene.h input.h test.h
g++ -c main.cpp
scene.o: scene.cpp scene.h utility.inl
g++ -c scene.cpp
input.o main.o scene.o : common.h
同时给三个目标添加了一个依赖common.h,但是不用修改上面已写好的部分。
多Makefile文件
包含其他makefile文件
使用include指令可以读入其他makefile文件的内容,效果就如同在include的位置用对应的文件内容替换一样。
include mkf1 mkf2 # 可以引入多个文件,用空格隔开
include *.mk # 可以用通配符,表示引入所有以.mk结尾的文件
如果找不到对应文件,则会报错,如果要忽略错误,可以在include前加-
-include mkf1 mkf2
应用实例:自动生成依赖
objs = block.o command.o input.o main.o scene.o test.o
sudoku: $(objs)
g++ $(objs) -o sudoku
include $(objs:%.o=%.d)
%.d: %.cpp
@-rm $@
$(CXX) -MM $< > $@.temp
@sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.temp > $@
@-rm $@.temp
%.o : %.cpp
g++ -c $<
@echo $^
嵌套make
如果将一个大项目分为许多小项目,则可以使用嵌套(递归)使用make。具体做法为,写一个总的Makefile,然后在每个子项目中都写一个Makefile,在总Makefile中进行调用。
例如,可以把sudoku项目中除main.cpp,test.cpp外的其他cpp存为一个子项目,编译为一个库文件,main.cpp test.cpp为另一个子项目,编译为.o然后链接库文件成可执行文件:
库文件Makefile
vpath %.h ../include
CXXFLAGS += -I../include -fexec-charset=GBK -finput-charset=UTF-8
cpps := $(wildcard *.cpp)
objs := $(cpps:%.cpp=%.o)
libsudoku.a: $(objs)
ar rcs $@ $^
$(objs): %.o : %.cpp %.h
main.cpp test.cpp的Makefile
CXXFLAGS += -I../include -fexec-charset=GBK -finput-charset=UTF-8
vpath %.h ../include
vpath %.a ../lib
../sudoku: main.o test.o -lsudoku
$(CXX) -o $@ $^
总的Makefile
.PHONY: all clean
all: subsrc
subsrc: sublib
$(MAKE) -C src
sublib:
$(MAKE) -C lib
clean:
-rm *.exe src/*.o lib/*.o lib/*.a
其中
$(MAKE) -C subdir
这一指令会自动进入subdir文件夹然后执行make。
可以通过export指令向子项目的make传递变量。
export var # 传递var
export # 传递所有变量
unexport # 取消传递
示例分析:
第 1 个 Makefile,简单粗暴,效率低:
test : main.c sub.c sub.h
gcc -o test main.c sub.c
第 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 个 Makefile,效率高,精炼,不支持检测头文件:
# 最终目标
# 要生成test,必须先有main.o和sub.o
# 将两个.o目标文件链接在一起,输出名为test的可执行文件
test : main.o sub.o
gcc -o test main.o sub.o
# 模式规则
# %被称为模式匹配符,作用类似于常用的通识符*
# 所有的.o文件都依赖于同名的.c文件
# $@ : 自动变量,代表目标文件
# $< : 自动变量,代表第一个依赖文件
# -c : 告诉编译器只编译,不链接
%.o : %.c
gcc -c -o $@ $<
clean:
rm \*.o test -f
第 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 个 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))
# foreach把main.o变成.main.o.d,这些.d文件是编译器生成的,记录了.c文件到底引用了那些.h文件
# wildcard用来检查这些.d文件在磁盘上是否真实存在
# 把依赖文件包含进来
ifneq ($(dep_files),)
include $(dep_files)
endif
# 这是Makefile的核心增强功能,如果找到了.d文件,就用include把它们的内容包含进来
# .d文件的内容通常长这样:main.o: main.c config.h
%.o : %.c
gcc -Wp,-MD,.$@.d -c -o $@ $<
# -Wp,-MD,.$@.d: 这是传给预处理器的参数。它的作用是:在编译 .c 的同时,自动生成一个辅助文件(如 .main.o.d),里面详细列出 # 了该文件依赖的所有头文件。
clean:
rm \*.o test -f
distclean:
rm $(dep_files) \*.o test -f
# 两种清理规则对比
# clean: 只删除中间件(.o)和程序(test)
# disclean: 彻底清理,除了clean的内容,还会把自动生成的依赖文件(.d)也删掉
通用Makefile的解析
make命令的使用
执行make命令时,它会去当前目录下寻找名为”Makefile”的文件,并根据它的指示去执行操作,生成第一个目标。
-f :选择指定文件,不再使用名为”Makefile”的文件
make -f Makefile.build
-C:选择指定目录,切换到其他目录里去
make -C a/ -f Makefile.build
还可以指定目标,不再默认生成第一个目标
make -C a/ -f Makefile.build other_target
即时变量、延时变量
在GUN make中对变量的赋值有两种方式:延时变量、立即变量。
延时变量:在定义时不立即展开,而是在该变量被使用时才去寻找它的值,它会递归地查找最后一次被赋予的值
立即变量:在定义地哪一行立即展开,它之参考当前行之前已经定义好的值,一旦赋值,它的值就被固定下来,不受后面变量变化的影响
A = xxx // 延时变量
B ?= xxx // 延时变量,只有第一次定义时赋值才成功;如果曾定义过,此赋值无效
C := xxx // 立即变量
D += yyy // 如果D在前面是延时变量,那么现在它还是延时变量,如果D在前面是立即变量,那么现在它还是立即变量
变量的导出
在编译程序时,如果想让某个变量的值在所有目录中都可见,要把它export出来。例如”CC = $(CROSS_COMPILE)gcc”,这个CC变量表示编译器,在整个过程中都是一样的,定义它之后,要使用”export CC”把它导出来。
Makefile中可以使用shell命令
TOPDIR := $(shell pwd)
这是个立即变量,TOPDIR等于shell命令pwd的结果。
在Makefile中怎么放置第1个目标
执行make命令时如果不指定目标,那么它默认是去生成第 1 个目标。
所以“第 1 个目标”,位置很重要。有时候不太方便把第 1 个目标完整地放
在文件前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖与命
令。
First_target: // 这句话放在前面
.... // 其他代码,比如 include 其他文件得到后面的 xxx 变量
First_target : $(xxx) $(yyy) // 在文件的后面再来完善
command
伪目标
防止目录下有同名文件导致命令失效
.PHONY : clean
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
常用函数
1. $(foreach var,list,text)
简单地说,就是 for each var in list, change it to text。对 list 中的每一个
元素,取出来赋给 var,然后把 var 改为 text 所描述的形式。
例子:
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // 最终 dep_files := .a.o.d .b.o.d
2. $(wildcard pattern)
pattern所列出的文件是否存在,把存在的文件都列出来
例子:
src_files := $( wildcard *.c) // 最终 src_files 中列出了当前目录下的所有.c 文件
3. $(filter pattern…,text)
把text中符合pattern格式的内容,filter(过滤)出来、留下来。
例子:
obj-y := a.o b.o c/ d/
DIR := $(filter %/, $(obj-y)) //结果为:c/ d/
4. $(filter-out pattern…,text)
把 text 中符合 pattern 格式的内容,filter-out(过滤)出来、扔掉。
例子:
obj-y := a.o b.o c/ d/
DIR := $(filter-out %/, $(obj-y)) //结果为:a.o b.o
5. $(patsubst pattern,replacement,text)
寻找’text’中符合格式’pattern’的字,用’replacement’替换它们。’pattern’和’replacement’中可以使用通识符
例子:
subdir-y := c/ d/
subdir-y := $(patsubst %/, %, $(subdir-y)) // 结果为:c d
通用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
Makefile常用函数
函数调用的格式如下:
$(function arguments)
这里 function 是函数名,arguments 是该函数的参数。参数和函数名之间是用空格或 Tab 隔开,如果有多个参数,它们之间用逗号隔开。这些空格和逗号不是参数值的一部分。内核的 Makefile 中用到大量的函数,现在介绍一些常用的。
字符串替换和分析函数
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 bra.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
文件名函数
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…)
抽取name…中每一个文件名的后缀
比如:$(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
其他函数
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’,扩展结果为‘$(wildcard a/*)’;
- 第二个赋给变量 dir 的值是`b’,扩展结果为‘$(wildcard b/*)’;
- 第三个赋给变量 dir 的值是`c’,扩展结果为‘$(wildcard c/*)’;
- 如此继续扩展。
这个例子和下面的例有共同的结果:
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’。
演示
《Makefile 介绍》这小节可以在阅读内核、bootloader、应用程序的Makefile 文件时,作为手册来查询。下面以 options 程序的 Makefile 作为例子进行演示,Makefile 的内容如下:
File: Makefile
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 中$@、$^、$<称为自动变量。$@表示规则的目标文件名;$^表示所有依赖的名字,名字之间用空格隔开;$<表示第一个依赖的文件名。‘%’是通配符,它和一个字符串中任意个数的字符相匹配。
options 目录下所有的文件为 main.c,Makefile,sub.c 和 sub.h,下面一行行地分析:
- 第 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 文件不用再次编译,节省了编译的时间。










