通用Makefile


title: 通用Makefile
date: 2019/3/18 19:03:23
toc: true

通用Makefile

引入与参考

内核代码在make的时候,使用make V=1,能够显示出具体的信息,我们可以参考内核的makefile来实现一个通用的Makefile

规则

最基本的规则最核心的是目标与依赖,当我们执行make target的时候,会去判断后面的xxx依赖是否更新,如果更新则执行,否则不执行.如果为空的话则一直执行,或者没有target的时候,也执行

target: xxx
	dosomething

简单例子

这里我们有源代码a.c,b.c来生成test,其中a.c包含a.h

test:a.o b.o
	gcc -o test a.o b.o
%.o : %.c 
	gcc -c -o $@ $<     #$@ 表示目标,这里也就是%.o $< 表示第一个依赖,也就是%.c

依赖

缺陷

这里a.c比如包含了a.h,那么在这个规则里,如果a.h改变,test并不会更新,所以我们需要a.c的依赖加入a.h,也就是需要这么修改

test:a.o b.o
	gcc -o test a.o b.o

a.o:a.c a.h

%.o : %.c 
	gcc -c -o $@ $<

那么很明显的,每个c里面的头文件都是其依赖,我们如何自动生成依赖?

自动生成依赖

尝试如下命令

gcc -c -o a.out a.c -Wp,-MD,a.d    #-c 编译不链接

可以看到自动生成如下依赖a.d文件,格式就是a.o: a.c 头文件列表

a.o: a.c /usr/include/stdc-predef.h /usr/include/stdio.h 
 /usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h 
 /usr/include/x86_64-linux-gnu/bits/wordsize.h 
 /usr/include/x86_64-linux-gnu/gnu/stubs.h 
 /usr/include/x86_64-linux-gnu/gnu/stubs-64.h 
 /usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h 
 /usr/include/x86_64-linux-gnu/bits/types.h 
 /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h 
 /usr/include/_G_config.h /usr/include/wchar.h 
 /usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h 
 /usr/include/x86_64-linux-gnu/bits/stdio_lim.h 
 /usr/include/x86_64-linux-gnu/bits/sys_errlist.h a.h

进一步优化这个Makefile,这样就会生成依赖了

test:a.o b.o
	gcc -o test a.o b.o

a.o:a.c a.h

%.o : %.c 
	gcc -c -Wp,-MD,$@.d  -o $@ $<

使用自动生成的依赖

直接上例子

objs := a.o b.o

test:$(objs)
	gcc -o test $^ 	#所有依赖

# 遍历obj的每一个元素 也就是 f=a.o f=b.o
# 然后命名为 .f.d 也就是 .a.o.d .b.o.d
dep_files := $(foreach f,$(objs),.$(f).d)

# wildcard 判断是否存在文件
# 在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。
# 所以这个生成的就是文件列表了
dep_files := $(wildcard $(dep_files))

# 这里直接include 的是一系列的文件
ifneq ($(dep_files),)
  include $(dep_files)
endif

# 第一次的时候,没有依赖文件生成
# 第二次的时候,存在了新的依赖文件,我们上面的include 已经有了 a.o: 依赖的c 依赖的h
%.o : %.c 
	gcc -Wp,-MD,.$@.d -c -o $@ $<

clean:
	rm *.o test

流程也就是

  1. 第一次的时候,没有依赖文件,全编译,同时生成.d的依赖文件
  2. 第二次的时候,已经有依赖文件,我们会include这个文件,文件的内容形如a.o: a.c a.h xxx,这就是完整的依赖了
  3. 依靠%.o %.c 来执行编译了

目录遍历

同一个目录下,先编译子目录,再编译同级的文件,也就是先编译,b.c再a.c

a.c
/b
	/b.c

顶层的Makefile


CROSS_COMPILE = arm-linux-
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

# -g 调试信息 -O2 优化等级 -Wall 显示所有警告
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include

LDFLAGS := -lm -lfreetype

export CFLAGS LDFLAGS

TOPDIR := $(shell pwd)
export TOPDIR

TARGET := show_file

obj-y += main.o
obj-y += display/
obj-y += draw/
obj-y += encoding/
obj-y += fonts/

all : 
	# -f 或者 -file 是指定makefile的文件,也就是只用指定的makefile来执行接下去的make
	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

# 伪目标,也就是总是执行的意思
PHONY := __build
__build:

obj-y :=
subdir-y :=

# 包含了当前的Makefile,也就是引入了顶层定义的目标和编译工具链
include Makefile


# 目的:提取目录名,去除/
# 1. $(filter %/, $(obj-y)) 这个就是保留带有 /结尾的元素 也就是 c/ d/ ,筛选出目录
# 2. $(patsubst %/,%,xxx)   这个xxx就是上面调出来的 c/ d/
# 2. 然后使用替换 把%/ 替换成 %  也就是变成  c d,提取目录的名字
__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)

# 标记1---
# 对于每个当前目录下的子目录,会生成子目录下的 c/built-in.o  d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)

# 这里筛选出当前目录下的文件 a.o
cur_objs := $(filter-out %/, $(obj-y))
# 遍历当前目录的目标文件,产生依赖 .xxx.d
dep_files := $(foreach f,$(cur_objs),.$(f).d)
# 产生文件列表,wildcard 它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表
dep_files := $(wildcard $(dep_files))
# 如果文件存在,则包含这些依赖
ifneq ($(dep_files),)
  include $(dep_files)
endif

# 当前目录下的子目录的名字 这个PHONY 其实就是一个名字 可以写作PHONY123,但是最下面是指定了他是伪目标
PHONY += $(subdir-y)

# subdir-y 表示子目录 也就是 c d  这里已经去除了/  subdir-y 这里是一系列的目录名
__build : $(subdir-y) built-in.o
	#echo  zzz... $(subdir-y) zzzz.  # 这里就打印出一些列的

# 子目录,使用顶层的Makefile.build 也就是本文件make
# 这里 -C $@ 也就是说明进入子目录,使用本文件make
# 可以看出来,其实 $(subdir-y) 是个每次都执行的目标,也就是每次都是进入子目录,尝试运行
$(subdir-y):
	make -C $@ -f $(TOPDIR)/Makefile.build

# 依赖为本目录下的  cur_objs目标文件,和 subdir_objs,这个是子目录的c/built-in.o d/built-in.o
# 在标记1处生成的
built-in.o : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^

# $@ 目标
dep_file = .$@.d

# 编译 产生了.目标.d的依赖
%.o : %.c
	$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
	
.PHONY : $(PHONY)

子目录的Makefile

obj-y += a.o	# 表示当前的目标文件
obj-y += c/		# 这里的/ 表示c是个子目录,会继续进去编译的

总结

  1. 我们先进入最底层的一个没有子目录的目录,生成一个built-in.o
  2. 然后回到上一层,如果还有其他子目录,则继续进入子目录
  3. 本级的子目录都处理完,那么与本目录其他的目标再链接生成一个新的built-in.o
  4. ...
  5. 回到顶层,与顶层的目标再链接生成built-in.o
  6. 最后再链接生成target

mark

Tips

  • 我们可以使用gcc -c -o a.out a.c -Wp,-MD,a.d #-c 编译不链接 来查看头文件路径
  • := 可以追加,=确定的不能追加
原文地址:https://www.cnblogs.com/zongzi10010/p/10554980.html