目的:编写一个适用于所有的应用程序的Makefile,在这之前可以先参考:Makefile规则以及函数,包含一些基本语法与函数的使用
目录
一、需要编译的文件
需要编译的工程
进去display目录
进去test目录
可以看到在顶层有一个Makfile,然后各目录也有一个Makefile,不同的是顶层Makefile还有一个Makefile.build,这个Makefile.build也就是此次的重点
二、Makfile内容
2.1 子目录Makefile
除了顶层的Makefile,其他子目录的内容例如dispaly目录下的Makfile如下,除了顶层的Makfile和Makefile.build外其他的Makfile都是很简单的,只需要添加该目录的下的.o文件和目录文件
obj-y += disp_manager.o
obj-y += fb.o
obj-y += test/
2.2 顶层Makfile与Makefile.build
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
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 :
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 :=
include Makefile
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y)) : c/ d/
# __subdir-y : c d
# subdir-y : c d
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
# c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# a.o b.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
$(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) -Wp,-MD,$(dep_file) -c -o $@ $<
.PHONY : $(PHONY)
三、分析
编译过程概述:此show_file工程,顶层有main.c等C文件还有各种目录,在目录中还有子目录,仿造内核的结构,首先进去顶层目录,顶层Makefile发现有目录就会分别进入子目录,在子目录中也会有Makefile,如果目录中还有子目录,会再进到子目录中去例如test目录,然后将里面的文件利用顶层的makefile.build来编译为.o文件,然后把所有的.o文件打包为该目录下的built-in.o,然后再回去上一层目录中,再把里面所有的文件利用顶层的makefile.build来编译为.o文件,然后把所有的.o文件和子目录的built-in.o打包为该目录下的built-in.o,然后再回去最顶层的目录,同样也用makefile.build所有的.o文件和子目录的built-in.o打包为该目录下的built-in.o,然后再进行链接
3.1 实现的机制
主要是参考内核的Makefile思想来编写一个通用的Makefile,在内核中对于子目录的Makefile都比较简单,只有变量加上*.o或者dir/,例如上面所说的dispaly目录
3.2 顶层Makfile
- 在顶层Makfile中,仿造内核首先定义工具链,例如CROSS_COMPILE,如果需要交叉编译就加上arm-linux-,不需要空着
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导出来是因为进入各个子目录去编译之后,可以使用这些变量
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP
- CFLAGS变量用来优化选项,其中Wall选项可以打印出编译时所有的错误或者警告信息,-g是加上调试信息,编译器找头文件或者库有默认的系统目录,我们还可以同-I来指定我们的目录的,这样系统的时候还会去指定的目录找我们的头文件,加上-L可以指定库文件目录
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include
- LDFLAGS变量用来加上需要链接的库
LDFLAGS := -lm -lfreetype
export CFLAGS LDFLAGS
- TOPDIR变量存放顶层目录,让子目录编译的时候用顶层的Makefile.build来编译
TOPDIR := $(shell pwd)
export TOPDIR
- TARGET变量是make之后想生成的目标,obj-y为顶层的.o文件和目录
TARGET := show_file
obj-y += main.o
obj-y += display/
obj-y += draw/
obj-y += encoding/
obj-y += fonts/
- all是make之后想生成的第一个目标,因此有执行里面的命令,其中执行"make -C ./ -f $(TOPDIR)/Makefile.build",用-C选项进入./然后用-f执行Makefile.build,这话执行完之后会在顶层得到一个built-in.o,然后执行"$(CC) $(LDFLAGS) -o $(TARGET) built-in.o",进行最终的链接,链接的输出文件就是TARGET,也就是最终的可执行文件
all :
make -C ./ -f $(TOPDIR)/Makefile.build
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
- 清除工作,clean是一个目标没有依赖,例如执行"make clean",就会想生成clean这个虚假文件然后执行clean的内容
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)
3.3 顶层Makfile.Build(重点)
此文件是整个编译的重点
- make之后,会进入Makefile.build,其中第一个生成的目标是__build,"include Makefile",需要包含当前目录的Makfile,因为当前目录含有oby-j编译文件,知道需要编译哪些文件和哪些子目录
PHONY := __build
__build:
obj-y :=
subdir-y :=
include Makefile
- subdir-y变量会得到当前的子目录有哪些,然后后面会优先进入子目录去再利用顶层的makefile.build来编译
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y)) : c/ d/
# __subdir-y : c d
# subdir-y : c d
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
- subdir_objs得到各个目录的built-in.o文件
# c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
- cur_objs得到当前目录下obj-y的.o文件,这些.o文件最终会在后面%.c编译得到,dep_files得到依赖文件,第一次编译的时候不会生成依赖文件,就不会把依赖文件包含进来,修改了.h文件后,执行第二次编译,就把第一次生成的依赖文件包含进来,这样就会根据依赖来找到.h依赖的.c文件并编译
# a.o b.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依赖于子目录的built-in.o和子目录,因为会先进入子目录来执行"make -C $@ -f $(TOPDIR)/Makefile.build",这条命令是递归进入每个子目录,都用顶层的Makefile.build来编译,都中会都生成built-in.o,然后执行"$(LD) -r -o $@ $^",将子目录的built-in.o和当前目录的bojs-y中的.o文件用LD工具链打包,最终会在顶层生成一个built-in.o,在根据顶层的Makefile中"$(CC) $(LDFLAGS) -o $(TARGET) built-in.o"命令来实现链接生成TARGET可执行文件
__build : $(subdir-y) built-in.o
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
- 这里dep_file为瞬时变量,用到的时候才加载,在编译.o文件的时候顺便把所有文件的依赖文件都生成出来
- 对于.PHONY:特殊目标,.PHONY的依赖是假想目标,假象目标是这样一些目标,make无条件的执行它的命令和目录下是否存在改文件以及它最后一次更新的时间没有关系
dep_file = .$@.d
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
.PHONY : $(PHONY)