一、写在前面
对于前面电子书的Makefile文件,只是简单地列出所有编译所需的目标文件,但是当修改某个头文件时并不会
重编译包含该头文件的源文件。另一方面,当存在成千上万个源文件,这样一 一列出xxx.o是不现实的。
因此很有必要写出一个更通用的编译体系。
二、电子书的源码树
./
|-- display
| |-- disp_manager.c
| |-- fb.c
| |-- Makefile
| `-- test
| |-- Makefile
| `-- test.c
|-- draw
| |-- draw.c
| `-- Makefile
|-- encoding
| |-- ascii.c
| |-- encoding_manager.c
| |-- Makefile
| |-- utf-16be.c
| |-- utf-16le.c
| `-- utf-8.c
|-- fonts
| |-- ascii.c
| |-- fonts_manager.c
| |-- freetype.c
| |-- gbk.c
| `-- Makefile
|-- include
| |-- config.h
| |-- disp_manager.h
| |-- draw.h
| |-- encoding_manager.h
| `-- fonts_manager.h
|-- log.txt
|-- main.c
`-- Makefile
说明:源码树来源自韦老师课堂笔记(幸苦了!
三、通用Makefile 的说明
本程序的Makefile分为3类:
1. 顶层目录的Makefile
2. 顶层目录的Makefile.build
3. 各级子目录的Makefile
一、各级子目录的Makefile:
它最简单,形式如下:
obj-y += file.o
obj-y += subdir/
"obj-y += file.o"表示把当前目录下的file.c编进程序里,
"obj-y += subdir/"表示要进入subdir这个子目录下去寻找文件来编进程序里,
是哪些文件由subdir目录下的Makefile决定。
注意: "subdir/"中的斜杠"/"不可省略
二、顶层目录的Makefile:
它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,主要是定义工具链、编译参数、链接参数──
就是文件中用export导出的各变量。
三、顶层目录的Makefile.build:
这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,
打包为built-in.o
四、怎么使用这套Makefile:
1.把顶层Makefile, Makefile.build放入程序的顶层目录
2.修改顶层Makefile
2.1 修改工具链
2.2 修改编译选项、链接选项
2.3 修改obj-y决定顶层目录下哪些文件、哪些子目录被编进程序
2.4 修改TARGET,这是用来指定编译出来的程序的名字
3. 在各一个子目录下都建一个Makefile,形式为:
obj-y += file1.o
obj-y += file2.o
obj-y += subdir1/
obj-y += subdir2/
4. 执行"make"来编译,执行"make clean"来清除,执行"make distclean"来彻底清除
说明:来源自韦东山老师课堂笔记
四、实现通用Makefile
实际,表达为通用Makefile并不准确,更准确来说,应该是一套Makefile编译体系。
下面讲解实现这编译体系的思路
1)在各子目录下编写Makefile,形式如
obj-y += xxx.o
obj-y += subdir/
2) 编写顶层Makefile文件
2.1> 参考内核顶层Makefile,定义工具链、编译参数、链接参数
然后再把这些 export 导出去供子目录的Makefile使用
2.2> 定义目标TARGET以及生成该目标的依赖文件(obj-y += subdir/ 或 main.o )
并编写生成TARGET的命令,最关键是执行make -C ./ -f $(TOPDIR)/Makefile.build
以生成最终的依赖built-in.o
2.3> 定位伪目标clean 及 distclean ,方便清理重新make。(distclean相比clean只是多了清楚生成的依赖文件.xxx.d )
3) 编写 Makefile.build 文件
该文件的这套编译体系的核心,对于啥是Makefile.build呢?通俗来说,就是定义了通用规则的一个Makefile文件。
何为通用?即它适用所有子目录包括顶层目录,对于子目录,它首先包含子目录的Makefile(获得obj-y ),若该子目录还有其他目录
则进入其他目录递归调用make以生成build-in.o,最后该built-in.o与该子目录的所有 *.o 目标文件链接生成 build-in.o
作为顶层Makefile的依赖文件。对于顶层目录,也会首先包含顶层Makefile,然后依次进入子目录递归make,将所有子目录生成的
build-in.o 和 顶层目录下的所有*.o目标文件“链接”生成最终的目标TARGET
如何编写呢? 首先明确思路,然后逐步实现。
始终不要忘记这一目的:生成"最终"的 build-in.o (= *.o 或 *.o + build-in.o )
编写思路:
3.1> 参考内核顶层目录下的scripts目录的Makefile.build文件
首先定义PHONY变量(其值为__build )(后面会声明为伪变量), 并把它的值作为Makefile.build文件的最终目标
并写出生成这一目标的依赖:当前目录下所有*.o 目标文件 + 当前目录下所有子目录的build-in.o(subdir/build-in.o)
3.2> 生成当前目录所有子目录的build-in.o
套路都一样, 命令都为: make -C $@ -f $(TOPDIR)/Makefile.build
即进入到子目录,然后适用顶层目录下的Makefile.build编译生成build-in.o
3.3> 生成当前目录所有*.o 目标文件
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
3.4> 链接 3.2、3.3生成的依赖文件以生成最终的build-in.o
build-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
备注:Makefile.build还涉及根据当前目录的obj-y 变量提取出 *.o 和 /subdir1 /subdir2(最终被替换为 subdir1 subdir2 )
总结来说,Makefile.build实现的是通用的规则,差异性由子目录或顶层目录的Makefile实现。
下面,按上面的步骤编码这套编译体系。(省略子目录的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)
PHONY := __build
__build:
obj-y :=
subdir-y :=
include Makefile
__subdir-y := $(patsubst %/, %, $(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
subdir_objs := $(foreach f, $(subdir-y), $(f)/build-in.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)
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
__build: $(subdir-y) build-in.o
build-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
dep_file = .$@.d
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
.PHONY: $(PHONY)
最后执行make即可生成show_file,这里就不贴出编译过程了,太占篇幅。
五、Makefile用到的参数说明
下面解释上面两个文件用到的一些命令和参数。
make -C <dir_name> -f <file_name>
-C <dir_name>: 即进入dir_name目录
-f <file_name> : 指定make的文件<file_name>
.PHONY : $(PHONY) 声明$(PHONY)变量的值为伪变量
编译、链接时用到的参数详解:
CFLAGS := -Wall -O2 -g 指定编译的参数
-Wall : 打印所有的警告信息
-O2 :
-O0~3编译器的优化选项的4个级别,-O0表示没有优化, -O1为缺省值,-O3优化级别最高
级别越大优化效果越好,但编译时间越长。知道用于优化即可,深究可参考其它文章。
-I<dir>
指定从<dir>查找头文件,否则在标准库路径查找
-L<dir>
指定搜索库的路径为<dir>,否则在标准库路径查找
放在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接了,但如果库文件没放在这三个目录里,
而是放在其他目录里,这时我们只用-l参数的话,链接还是会出错,出错信息大概是:“/usr/bin/ld: cannot find -lxxx”,
也就是链接程序ld在那3个目录里找不到libxxx.so,这时另外一个参数-L就派上用场了,比如常用的X11的库,
它放在/usr/X11R6/lib目录下,我们编译时就要用-L/usr/X11R6/lib -lX11参数,-L参数跟着的是库文件所在的目录名。
再比如我们把libtest.so放在/aaa/bbb/ccc目录下,那链接参数就是-L/aaa/bbb/ccc -ltest
-l<lib_name>
指定程序要链接的库<lib_name>
比如我们自已要用到一个第三方提供的库名字叫libtest.so,那么我们只要把libtest.so拷贝到/usr/lib里,
编译时加上-ltest参数,我们就能用上libtest.so库了
(当然要用libtest.so库里的函数,我们还需要与libtest.so配套的头文件)。
-g
在编译的时候,产生调试信息。
-M
生成文件关联的信息。包含目标文件所依赖的所有源代码你可以用 gcc -M hello.c 来测试一下,很简单。
-MD
和-M相同,但是输出将导入到.d的文件里面
-r
--relocateable 产生可重定向的输出
arm-elf-ar
将多个可重定位的源目标模块归档为一个函数库文件。采用函百数库文件,应用程序能够从该文件中自动装载要参考的函数模块,
同时将应用程序中频知繁调用的函数放入函数库文件中,易于应用程序的开发管理道。arm-elf-ar支持ELF格式的函数库文件
1) 创建test.a静态库
arm-linux-gcc -c a.o a.c
arm-linux-gcc -c b.o b.c
arm-linux-ar -rc test.a a.o b.o
2) 查看test.a库中目标文件
arm-linux-ar -t test.a
arm-linux-ar –tv test.a
v显示出具体创建库的时间
3) 提取test.a库中的目标文件
arm-linux-ar -x test.a
4) 修改test.a库中加入c.o目标文件
arm-linux-ar –r test.a c.o
5) 删除test.a库中c.o目标文件
arm-linux-ar –d test.a c.o
arm-linux-nm
列出目标文件中的符号。
几个函数:
$(patsubst %/, %, $(filter %/, $(obj-y)))
假设: obj-y := a.o b.o c.o display/ draw/
则$(filter %/, $(obj-y)) --->得到 display/ draw/
patsubst 将得到的 display/ draw/ 替换为 display draw
cur_objs := $(filter-out %/, $(obj-y))
去掉$(obj-y)变量中所有符合 %/ 的 值,返回不匹配的值
dep_files := $(foreach f, $(cur_objs), .$(f).d)
从$(cu_objs)变量遍历取出一个值f ,并替换为 $(f).d
后缀为.d 表明是dependent 依赖文件
dep_files := $(wildcard $(dep_files))
从dep_files变量中取出所有后缀 .d 的文件
OK,本文讲到这里,上面的文件已经放到我的代码集合(https://blog.csdn.net/qq_42800075/article/details/105670841);