本文介绍一种适用于管理大型项目的通用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 协议 ,转载请注明出处!

UNIX环境编程概述 上一篇
从0移植4.9内核(1) 下一篇