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
流程也就是
- 第一次的时候,没有依赖文件,全编译,同时生成.d的依赖文件
- 第二次的时候,已经有依赖文件,我们会
include
这个文件,文件的内容形如a.o: a.c a.h xxx
,这就是完整的依赖了 - 依靠
%.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是个子目录,会继续进去编译的
总结
- 我们先进入最底层的一个没有子目录的目录,生成一个built-in.o
- 然后回到上一层,如果还有其他子目录,则继续进入子目录
- 本级的子目录都处理完,那么与本目录其他的目标再链接生成一个新的built-in.o
- ...
- 回到顶层,与顶层的目标再链接生成built-in.o
- 最后再链接生成target
Tips
- 我们可以使用
gcc -c -o a.out a.c -Wp,-MD,a.d #-c 编译不链接
来查看头文件路径 :=
可以追加,=
确定的不能追加