在Linux
中使用make
命令来编译程序,特别是大程序;而make
命令所执行的动作依赖于Makefile
文件。
Makefile
文件规则如下:
目标(target)…: 依赖(prerequiries)…
<tab>命令(command)
**目标(target)**通常是要生成的文件的名称,可以是可执行文件或OBJ文件,也可以是一个执行的动作名称,诸如`clean’。
依赖是用来产生目标的材料(比如源文件),一个目标经常有几个依赖。
命令是生成目标时执行的动作,一个规则可以含有几个命令,每个命令占一行。
最简单的Makefile文件如下:
hello: hello.c
gcc -o hello hello.c
clean:
rm -f hello
将上述4行存为Makefile
文件(注意必须以Tab键缩进第2、4行,不能以空格键缩进),直接执行make
命令即可编译程序,执行make clean
即可清除编译出来的结果。
make
命令根据文件更新的时间戳来决定哪些文件需要重新编译,这使得可以避免编译已经编译过的、没有变化的程序,可以大大提高编译效率。
假如有a.c
和b.c
两个程序。
//a.c
#include
int main(){
func_b();
return 0;
}
//b.c
#include
void func_b(){
printf("This is B\n");
}
对应的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
clean:
rm *.o test
假如一个目标文件所依赖的依赖文件很多,那样岂不是我们要写很多规则,这显然是不合乎常理的。我们可以使用通配符,来解决这些问题。常用通配符如下:
上面的Makefile
修改如下:
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm *.o test
如果想清除文件,在Makefile的结尾添加如下代码:
clean:
rm *.o test
然后在终端执行:make clean
就可以了。
当执行 make clean
的时候,就会在 Makefile
里面找到 clean
这个目标,然后执行里面的命令,这个写法有些问题,原因是目录里面没有 clean 这个文件,这个规则执行的条件成立,他就会执行下面的命令来删除文件。
但是如果该目录下有名为clean
文件时,执行make clean
会有如下提示:
make: \`clean' is up to date.
即没有执行删除操作。
解决办法:需要把目标定义为假象目标,用关键字PHONY
,用法:
# 把clean定义为假象目标。他就不会判断名为“clean”的文件是否存在
.PHONY: clean
改进2.2中的Makefile
如下:
test:a.o b.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm *.o test
.PHONY: clean
在makefile中有两种变量:
(1)简单变量(即时变量)
对于即时变量使用 :=
表示,它的值在定义的时候已经被确定了
# A的值即刻确定,在定义时即确定
A := xxx
(2)
对于延时变量使用=
表示。它只有在使用到的时候才确定,在定义等于时并没有确定下来。
# B的值使用到时才确定
B = xxx
常用的变量的定义如下:
:= # 即时变量
= # 延时变量
?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句
\+= # 附加, 它是即时变量还是延时变量取决于前面的定义
?=: # 如果这个变量在前面已经被定义了,这句话就会不会起效果,
实例:
Makefile
如下:
# 想使用变量的时候使用 $ 来引用
A := $(C)
B = $(C)
C = abc
#D = 100ask
D ?= weishen
# 如果不想看到命令时,可以在命令的前面加上 @ 符号,就不会显示命令本身。
all:
@echo A = $(A)
@echo B = $(B)
@echo D = $(D)
C += 123
执行make
后,结果如下:
A =
B = abc 123
D = weishen
分析:
# A为即使变量,在定义时即确定,由于刚开始C的值为空,所以A的值也为空。
A =
# B为延时变量,只有使用到时它的值才确定,当执行make时,会解析Makefile里面的所用变量,所以先
# 解析C= abc,然后解析C += 123,此时,C = abc 123,当执行:@echo B = $(B) B的值为 abc 123。
B = abc 123
# D变量在前面没有定义,所以D的值为weishen,如果在前面添加D = 100ask,最后D的值为100ask。
D = weishen
makefile
里面可以包含很多函数,这些函数都是make
本身实现的。函数调用的格式如下:
$(function arguments)
function
是函数名,arguments
是该函数的参数。参数和函数名之间是用空格或Tab隔开,如果有多个参数,它们之间用逗号隔开。这些空格和逗号不是参数值的一部分。
函数foreach
语法如下:
$(foreach var,list,text)
简单地说,就是 for each var in list, change it to text
。对list
中的每一个元素,取出来赋给var
,然后把var
改为text
所描述的形式。
实例:
A = a b c
B = $(foreach f, &(A), $(f).o)
all:
@echo B = $(B)
结果:
B = a.o b.o c.o
函数filter/filter-out
语法如下:
$(filter pattern...,text) # 在text中取出符合patten格式的值
$(filter-out pattern...,text) # 在text中取出不符合patten格式的值
实例:
C = a b c d/
D = $(filter %/, $(C))
E = $(filter-out %/, $(C))
all:
@echo D = $(D)
@echo E = $(E)
结果:
D = d/
E = a b c
语法如下:
$(wildcard pattern) # pattern定义了文件名的格式, wildcard取出其中存在的文件。
函数 wildcard
会以 pattern
这个格式,去寻找存在的文件,返回存在文件的名字。
实例:
在该目录下创建三个文件:a.c b.c c.c
files = $(wildcard *.c)
all:
@echo files = $(files)
结果:
files = a.c b.c c.c
也可以用wildcard
函数来判断,真实存在的文件,实例:
files2 = a.c b.c c.c d.c e.c abc
files3 = $(wildcard $(files2))
all:
@echo files3 = $(files3)
结果:
files3 = a.c b.c c.c
语法如下:
$(patsubst pattern,replacement,$(var))
patsubst
函数是从 var
变量里面取出每一个值,如果这个符合 pattern
格式,把它替换成 replacement
格式。
实例:
files2 = a.c b.c c.c d.c e.c abc
dep_files = $(patsubst %.c,%.d,$(files2))
all:
@echo dep_files = $(dep_files)
结果:
dep_files = a.d b.d c.d d.d e.d
使用到的程序如下:a.c b.c c.c c.h
//a.c
#include
int main(){
func_b();
func_c();
return 0;
}
//b.c
#include
void func_b(){
printf("This is B\n");
}
//c.c
#include
#include ""c.h
void func_b(){
printf("This is C = %d\n",C);
}
//c.h
#define C 1
如果按照上面的内容不难写出如下Makefile
test:a.o b.o c.o
gcc -o test $^
%.o:%.c
gcc -c -o $@ $<
clean:
rm *.o test
.PHONY:clean
编译运行之后,结果为:
This is B
This is C =1
但是如果修改c.h
中C=2
,重新编译运行,结果是不变的。说明编写的Makefile
是有问题的。原因是该Makefile
没有考虑头文件。
所以需要写出每个.c
文件依赖了哪些.h
文件。但是对于内核,有几万个文件,不可能为每个文件依次写出其头文件。
可以使用编译器的-M
选项,自动获取源文件中包含的头文件,并生成一个依赖关系。
所以首先需要先了解gcc
的-M -MF
等编译选项的用法。参考链接:
Linux Makefile 生成 *.d 依赖文件以及 gcc -M -MF -MP 等相关选项说明
总结如下:
gcc -M c.c // 打印出依赖
gcc -M -MF c.d c.c // 把依赖写入文件c.d
gcc -c -o c.o c.c -MD -MF c.d // 编译c.o, 把依赖写入文件c.d
下面给出改进:
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)中的文件,则包含(添加)进来
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
解释如下:
首先用obj变量将.o文件放在一块。利用前面讲到的函数,把obj里所有文件都变为.%.d格式,并用变量dep_files表示。利用前面介绍的wildcard函数,判断dep_files是否存在。然后是目标文件test依赖所有的.o文件。如果dep_files变量不为空,就将其包含进来。然后就是所有的.o文件都依赖.c文件,且通过-MD -MF生成.d依赖文件。清理所有的.o文件和目标文件,清理依赖.d文件。
这样,对头文件进行修改,再次编译运行即可看出改变。
还可以添加CFLAGS,即编译参数。例如
CFLAGS = -Werror -Iinclude
# -Werror选项是检查程序错误,即使是警告也会当成错误
# -Iinclude选项是指定头文件搜索路径为当前目录的 include 文件夹
参考Linux
内核的Makefile
编写了一个通用的Makefile
,它可以用来编译应用程序,有如下好处:
顶级目录结构如下:
知道了程序结构,这里就不再给出每个源程序的代码。
顶级目录的Makefile
如下:
# CROSS_COMPILE是交叉编译器前缀。如果是使用gcc编译器,则CROSS_COMPILE不用设置。如果是给arm版编译使用,则需要设置使用哪个编译器
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
# 使AS等变量在所有目录中都可见
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!
# make -C ./ -f $(TOPDIR)/Makefile.build相当于make -C ./ -f ./Makefile.build,-C是指定目录为 ./ -f是指定文件为Makefile.build
start_recursive_build:
make -C ./ -f $(TOPDIR)/Makefile.build
$(TARGET) : built-in.o
$(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)
顶级目录下的Makefile.build
如下:
PHONY := __build
__build:
# 变量清零
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=
# 包含Makefile,里面有obj-y += main.o sub.o a/
include Makefile
# 下边几行注释是把下面最近一行的命令拆分开讲解,目的就是从obj-y中获取 目录 而不是.o文件
# 而且obj-y := a.o b.o c/ d/只是一个假设,在本例子中,obj-y := main.o sub.o a/
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y)) : c/ d/
# __subdir-y : c d
# subdir-y : c d
# __subdir-y 中是顶级目录下的各个子目录的名字
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
# c/built-in.o d/built-in.o
# subdir_objs 是顶级目录下每个子目录中编译链接的 built-in.o文件的集合
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# a.o b.o
# cur_objs是顶级目录下 .c文件 对应生成的 .o 文件的集合
cur_objs := $(filter-out %/, $(obj-y))
# .$(f).是 .a.o.d和.b.o.d,它们分别记录了顶级目录下每个 .c 文件所依赖的文件信息。dep_files是这些 .d 文件的集合
dep_files := $(foreach f,$(cur_objs),.$(f).d)
# 判断这些.d文件是否存在,把存在的保存在dep_files中
dep_files := $(wildcard $(dep_files))
# ifneq表示如果当前目录有$(dep_files)中的文件,则包含(添加)进来
ifneq ($(dep_files),)
include $(dep_files)
endif
PHONY += $(subdir-y)
# 目标文件__build依赖于顶级目录下的built-in.o和每个子目录的built-in.o
# 这里解释一下:虽然$(subdir-y)是子目录的集合,但是由于Makefile.build是递归执行的,
# 子目录中返回的就是子目录生成的built-in.o
__build : $(subdir-y) built-in.o
# 子目录的built-in.o是通过顶级目录下的Makefile.build生成的
# make -C a/ -f Makefile.build
$(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
# .c生成.o
%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
# 设为假象目标
.PHONY : $(PHONY)
子目录a下的Makefile
如下:
EXTRA_CFLAGS := -D DEBUG
CFLAGS_sub3.o := -D DEBUG_SUB3
obj-y += sub2.o
obj-y += sub3.o
对整个项目编译的说明:
本程序的Makefile
分为3类:
它最简单,形式如下:
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设置它自己的编译选项, 可以不设置
注意:
subdir/
中的斜杠/
不可省略Makefile
中的CFLAGS
在编译任意一个.c
文件时都会使用CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o
三者组成xxx.c
的编译选项顶层目录的Makefile
除了定义obj-y
来指定根目录下要编进程序去的文件、子目录外,主要是:
CROSS_COMPILE
,CFLAGS
,LDFLAGS
,这些参数就是文件中用export
导出的各变量。
这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o
(1)把顶层Makefile
, Makefile.build
放入程序的顶层目录
在各自子目录创建一个空白的Makefile
(2)确定编译哪些源文件
修改顶层目录和各自子目录Makefile
的obj-y
:
obj-y += xxx.o
obj-y += yyy/
这表示要编译当前目录下的xxx.c
,要编译当前目录下的yyy
子目录
(3)确定编译选项、链接选项
修改顶层目录Makefile
的CFLAGS
,这是编译所有.c
文件时都要用的编译选项;
修改顶层目录Makefile
的LDFLAGS
,这是链接最后的应用程序时的链接选项;
修改各自子目录下的Makefile
:
EXTRA_CFLAGS
,它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置;
CFLAGS_xxx.o
,它给当前目录下的xxx.c
设置它自己的编译选项, 可以不设置
(4)使用哪个编译器?
修改顶层目录Makefile
的CROSS_COMPILE
, 用来指定工具链的前缀(比如arm-linux-
)
(5)确定应用程序的名字:
修改顶层目录Makefile
的TARGET
,这是用来指定编译出来的程序的名字
(6)执行make
来编译,执行make clean
来清除,执行make distclean
来彻底清除
(1)make
命令的使用
执行·make·命令时,它会去当前目录下查找名为Makefile
的文件,并根据它的指示去执行操作,生成第一个目标。
可以使用-f
选项指定文件,不再使用名为Makefile
的文件,比如:
make -f Makefile.build
可以使用-C
选项指定目录,切换到其他目录里去,比如:
make -C a/ -f Makefile.build
可以指定目标,不再默认生成第一个目标:
make -C a/ -f Makefile.build other_target
(2)变量的导出export
在编译程序时,我们会不断地使用make -C dir
切换到其他目录,执行其他目录里的Makefile
。如果想让某个变量的值在所有目录中都可见,要把它export
出来。
比如CC = $(CROSS_COMPILE)gcc
,这个CC
变量表示编译器,在整个过程中都是一样的。定义它之后,要使用export CC
把它导出来。
(3)Makefile
中可以使用shell
命令
比如:
TOPDIR := $(shell pwd)
这是个立即变量,TOPDIR
等于shell
命令pwd
的结果。
(4)在Makefile
中怎么放置第1个目标
执行make
命令时如果不指定目标,那么它默认是去生成第1个目标。
所以“第1个目标”,位置很重要。有时候不太方便把第1个目标完整地放在文件前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖与命令。比如:
First_target: // 这句话放在前面
.... // 其他代码,比如include其他文件得到后面的xxx变量
First_target : $(xxx) $(yyy) // 在文件的后面再来完善
command
(1)在Makefile
文件中确定要编译的文件、目录,比如:
obj-y += main.o
obj-y += a/
Makefile
文件总是被Makefile.build
包含的。
(2)在Makefile.build
中设置编译规则,有3条编译规则:
1)怎么编译子目录? 进入子目录编译:
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
2)怎么编译当前目录中的文件?
%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
3)当前目录下的.o
和子目录下的built-in.o
要打包起来
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
(3)顶层Makefile
中把顶层目录的built-in.o
链接成APP
$(TARGET) : built-in.o
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o