本文介绍一种适用于管理大型项目的通用Makefile写法,不同的项目可直接套用,分为三个部分,子目录makefile,顶层目录makefile和顶层目录的makefile.build
子目录Makefile写法
子目录Makefile写法比较简单,如果有子目录的话加上dir/
obj-y += xxx.o
#进入子目录test
obj-y += test/
顶层目录Makefile写法
顶层目录Makefile也比较简单,主要包括以下几个功能:
- 定义obj-y来指定根目录下要编进程序去的文件、子目录
- 定义工具链、编译参数、链接参数,并用export导出
#交叉编译工具链
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
# 导出变量给子makefile使用
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
obj-y += main.o
obj-y += a/
obj-y += b/
TARGET := test
# make的默认目标
# 把子目录下的built-in.o文件链接成目标
all:
# 进入某个目录,使用Makefile.build来编译
make -C ./ -f $(TOPDIR)/Makefile.build
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
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写法
顶层目录Makefile.build写法比较复杂,而且难以理解,我在代码中都给了注释
makefile的分析需要从终极目标一层一层往下分析:
1 –build是第一个目标,make最终要生成这个目标
2 –build依赖于子目录$(subdir-y) 和当前目录的built-in.o,$(subdir-y)在之前定义,是子目录,built-in.o是当前目录的built-in.o文件,下面分别处理这两个依赖,看步骤3、4
__build : $(subdir-y) built-in.o
3 进入子目录,递归使用Makefile.build进行编译
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
4 built-in.o文件,依赖于当前目录所有的.o文件$(cur_objs)和子目录的built-in.o文件$(subdir_objs)
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
5 .o文件依赖于.c文件,执行Makefile文件里的CC指令
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_files) -c -o $@ $<
- 完整代码
PHONY := __build
# 第一个目标
__build:
obj-y :=
subdir-y :=
# Makefile中含有obj-y,知道编译哪些子目录
include Makefile
# 子目录
# 例:obj-y := a.o b.o c/ d/,那么 subdir-y := c/ d/,那么怎么把 c/ d/取出来?
# filter 函数:obj-y中符合 %/ 形式的文件取出来
# patsubst函数:把%/ 替换成 %
subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
# 子目录下的built-in.o文件
# c/built-in.o d/built-in.o,取出子目录下的built-in.o文件
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# 取出当前目录下的.o文件
# obj-y := a.o b.o c/ d/ 取出a.o b.o
# filter-out 函数:从 obj-y 中把不匹配 %/ 的文本取出来,即取出文件,过滤掉目录
cur_objs := $(filter-out %/, $(obj-y))
# 依赖文件,形式 .xx.o.d
# foreach : 修改cur_objs里面的.o 为 .(xx.o).d 的形式
dep_files := $(foreach f,$(cur_objs),.$(f).d)
# 取出已经存在的.x.o.d文件放在dep_files里面
dep_files := $(wildcard $(dep_files))
# 如果dep_files不为空,则包含进来
ifneq ($(dep_files),)
include $(dep_files)
endif
# __build 依赖于子目录和当前目录下的built-in.o
__build : $(subdir-y) built-in.o
PHONY += $(subdir-y)
# 递归,进入子目录,使用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_files = .$@.d
# 生成.o文件和依赖文件
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_files) -c -o $@ $<
.PHONY : $(PHONY)
这种Makefile的写法适合项目,修改起来比较方便,可以直接套用。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!