在Linux中使用 make 命令来编译程序,特别是大程序;而 make 命令所执行的动作 依赖于 Makefile 文件。以下是最简单的Makefile文件:
首先,包含如下文件:
Makefile文件内容如下所示:
然后,直接执行 make 命令编译程序:
执行 make clean 命令 即可清除编译出来的结果:
make命令 根据文件更新的时间戳来决定哪些文件需要重新编译,这使得可以避免已经编译过的、没有变化的程序,可以大大提高编译效率。
一、Makefile规则与示例讲解
1、规则
规则如下所示:
目标(target): 依赖(prerequiries)
<tab>命令(command)
需要特别注意,必须以Tab键进行缩进,不能以空格键缩进。
如果 “依赖文件” 比 “目标文件” 更加新,那么执行 “命令” 来重新生成 “目标文件”。
命令执行的两个条件:依赖文件比目标文件新,或者是 目标文件没有生成。
这里介绍两个函数:
(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文件
2、示例讲解
(1)简单粗暴,效率低:
test : main.c sub.c sub.h
gcc -o test main.c sub.c
(2)效率高,相似规则太多太啰嗦,不支持检测头文件:
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)效率高,精炼,不支持头文件检测:
test : main.o sub.o
gcc -o test main.o sub.o
%.o : %.c
gcc -c -o $@ $<
clean:
rm *.o test -f
(4)效率高,精炼,支持头文件检测(但是需要手动添加头文件规则):
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)效率高,精炼,支持自动检测头文件:
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
二、通用Makefile的使用
1、解析
(1)make 命令的使用
执行 make 命令时,它会去 当前目录下 查找名为 “Makefile” 的文件,并根据它的指示去执行操作,生成 第一个 目标。
我们可以使用 “ -f ” 选项指定文件,不再使用名为 “Makefile” 的文件,比如:
make -f Makefile.buid
我们也可以使用 “ -C ”选项指定目录,切换到其他目录里去,比如:
make -C a/ -f Makefile.buid
我们可以指定目标,不再 默认生成 第一个 目标:
make -C a/ -f Makefile.buid other_target
(2)即时变量、延时变量、自动变量
=
延迟赋值,在Makefile运行时才会被赋值:=
立即赋值,立即赋值是在真正运行前就会被赋值?=
空赋值,如果变量没有设置过才会被赋值+=
追加赋值,可以理解为字符串的加操作
变量的定义语法如下所示:
A = xxx // 延时变量
B ?= xxx // 延时变量,只有第一次定义时赋值才成功;如果曾定义过,此赋值无效
C := xxx // 立即变量
D += yyy // 如果 D 在前面是延时变量,那么现在它还是延时变量;
// 如果 D 在前面是立即变量,那么现在它还是立即变量
上面语句中,变量A是延时变量,它的值在使用时才展开、才确定。比如:
A = $@
test:
@echo $A
上述Makefile中,变量A的值在执行时才确定,它等于test,是延时变量。
如果使用 “ A := $@ ”,这是立即变量,这时" $@ "为空,所以A的值就为 空。
自动变量:
Makefile有很多自动变量,这里只介绍几个常用的,分别是 $<、$^、$@,其它的可以去参考Makefile文档。
$< 表示第一个依赖的文件,例如:
test: test.o test2.o
echo $<
test.o:
test2.o:
最终的结果是打印了 test.o,也就是test第一个依赖。
$^ 表示所有依赖,例如:
test: test.o test2.o
echo $^
test.o:
test2.o:
最终结果是打印了 test.o test2.o,是test全部的依赖。
$@ 表示目标,例如:
test: test.o test2.o
echo $@
test.o:
test2.o:
最终结果打印的是 test,也就是Makefile中的目标。
(3)变量的导出(export)
在编译时,我们会不断地使用 " make -C dir " 切换到其它目录,执行其他目录里的 Makefile。如果 想让某个变量的值在所有目录中都可见,要把它 export 出来。比如:
CC = $(CROSS_COMPILE)gcc
上面的CC变量表示编译器,在整个过程中都是一样的。定义它之后,要使用 “ export CC ” 把它导出来。
(4)Makefile中可以使用shell命令
比如:
TOPDIR := $(shell pwd)
这是一个即时变量,TOPDIR 等于 shell 命令pwd 的结果。
(5)在Makefile中怎么放置 第一个 目标
执行 make 命令时,如果不指定目标,那么它默认是去生成 第一个 目标。
所以 “ 第一个目标 ” ,位置很重要。有时候不太方便把 第一个目标 完整地放在文件前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖与命令。比如:
First_target: // 这句话放在前面
.... // 其他代码,比如 include 其他文件得到后面的 xxx 变量
First_target : $(xxx) $(yyy) // 在文件的后面再来完善
command
(6)假想目标
比如,我们的 Makefile 文件中有这样的目标:
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
如果当前目录下,恰好有名为 “ clean ” 的文件,那么执行 “ make clean ” 时,它就不会执行上面那些删除命令。
这时,我们需要把 “ clean ” 这个目标,设置为 “ 假想目标 ”,这样可以确保执行 “ make clean ” 时,那些删除命令肯定可以得到执行。
使用下面的语句 把 “ clean ” 设置为假想目标:
.PHONY : clean
2、通用Makefile的设计思想
(1)在Makefile文件中确定要编译的文件、目录
比如:
obj-y += main.o
obj-y += a/
“ Makefile ” 文件总是被 “ Makefile.build ” 包含的。
(2)在 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 和 子目录下的 build-in.o 要打包起来:
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
(3)顶层 Makefile 中把顶层目录的 build-in.o 链接成 APP
$(TARGET) : built-in.o
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
(4)情景演绎
使用情景:
./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) : 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)
./a/Makefile:
EXTRA_CFLAGS := -D DEBUG
CFLAGS_sub3.o := -D DEBUG_SUB3
obj-y += sub2.o
obj-y += sub3.o
Makefile.build:
PHONY := __build
__build:
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=
include Makefile
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.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 debug
debug:
@echo cur_objs = $(cur_objs)
@echo obj-y = $(obj-y)
@echo subdir_objs = $(subdir_objs)
@echo subdir-y = $(subdir-y)
$(subdir-y):
@echo subdir-y = $(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)
对上述情景的理解如下:
整理,简化,大致是这样的:
首先,在顶层执行 make,执行顶层Makefile,第一个目标为 all,而其第一个依赖为 start_recursive_build ,接着根据这个依赖,会去调用 make -C ./ -f $(TOPDIR)/Makefile.build ,可以替换为 make -C ./ -f $(shell pwd)/Makefile.build ,即在当前目录下,去执行 Makefile.build。
因为 Makefile.build 脚本中包含了 include Makefile,所以这时候,会根据顶层Makefile,给 Makefile.build 中的 obj- 和 subdir-y 进行赋值,那么就有:
obj-y = main.o sub.o a/
cur_objs = main.o sub.osubdir-y = a
subdir_objs = a/built-in.o
第一个目标为 __build ,且其第一个依赖为 $(subdir-y),也就是a目录,先运行第一个依赖的命令,然后调用命令为 make -C $@ -f $(TOPDIR)/Makefile.build,替换后为 make -C a -f $(shell pwd)/Makefile.build。意思为:去子目录a下,运行Makefile.build。
接着进入子目录运行 Makefile.build,同样根据子目录下的 Makefile 去给 obj- 和 subdir-y 进行赋值,由于没有子目录了,所以 subdir-y 为空,如下所示:
obj-y = sub2.o sub3.o
cur_objs = sub2.o sub3.o
subdir_objs =
subdir-y =
第一个目标还是 __build
,其依赖项两个:$(subdir-y)
和built-in.o
。前者为空,因此不需要再次递归调用了。第二个目标是 built-in.o,找到其对应的依赖为$(cur_objs) $(subdir_objs)
,而subdir_objs
又为空,因此不需要执行。
由于已经子目录下已经执行完了,退回顶层目录文件夹下 的 Makefile.build 脚本中继续执行,这时,由于 __build 目标的第一个依赖 $(subdir-y) 已经执行结束,则开始执行第二个依赖 built-in.o ,而此时对应的两个依赖,$(cur_objs) $(subdir_objs),第一个生成了 %.o ,第二个为 a/built-in.o ,已经在上一步生成了。因此链接生成顶层的 built-in.o ,此时,目标生成,退出 Makefile.build 脚本,回到顶层的 Makefile 中。
回到顶层的 Makefile ,继续执行第一个目标 all 的 第二个依赖 $(TARGET) ,而 $(TARGET) 的依赖 built-in.o 也生成了,执行最后的命令 $(CC) $(LDFLAGS) -o $(TARGET) built-in.o 就结束 顶层Makefile 脚本了。