前言
基于韦东山三期视频通用 Makefile 一节写的个人笔记
相关源码可去直接参考韦东山三期数码相框第 7 课找
解释
3. 编写一个通用的Makefile
编译test_Makefile的方法:
a. gcc -o test a.c b.c
对于a.c: 预处理、编译、汇编
对于b.c:预处理、编译、汇编
最后链接
优点:命令简单
缺点:如果文件很多,即使你只修改了一个文件,但是所有的文件文件都要重新"预处理、编译、汇编"
效率低
b. 写Makefile
核心:规则
目标:依赖1 依赖2
【TAB】命令
命令执行的条件:
i. "依赖"文件 比 "目标"文件 新
ii.没有"目标"这个文件
/*************************************/
$@--目标文件
$^--所有的依赖文件
$<--第一个依赖文件
/***********************************/
一个例子:
test:a.o b.o
gcc -o test a.o b.o
a.o:a.c a.h
%.o : %.c
gcc -c -o $@ $<
这样写可以自动将 .c 文件并生成可执行文件,但是有个问题
就是包括的 a.h 文件被修改时,他不会自动包括依赖关系,并
不会统对应的 .c 文件,所以会加上
a.o:a.c a.h
这样比较麻烦,需要自动生成依赖关系才行。
解决过程:
1. 网上搜,一个个试,这样比较麻烦
2. 直接修改内核的某个头文件,然后执行
make V=1
这样可以直接看内核是怎么解决这个问题的。
结果发现内核编译时加上了 -Wp,-MD,a.d【依赖输出到哪个文件】
做这样修改:
%.o : %.c
gcc -Wp,-MD,[email protected] -c -o $@ $<
但是这样还是很麻烦, 我们还不能自动使用生成的依赖文件。
/*********************************************************************************/
对于
$@--目标文件
$^--所有的依赖文件
$<--第一个依赖文件
的理解的一个简单例子。
假设有一个 main.c 它调用了 mytool1.c mytool2.c 中的函数。
则其 makefile 可以写成这样:
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
main.o:main.c mytool1.h mytool2.h
gcc -c $<
mytool1.o:mytool1.c mytool1.h
gcc -c $<
mytool2.o:mytool2.c mytool2.h
gcc -c $<
简化:根据默认规则 .o 文件都是由同名 .c 文件生成的,则这个 makefile 可以写成这样:
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
.o:.c
gcc -c $<
/***************以下是参照内核的 makefile 修改的。**********************************************************/
objs := a.o b.o
test:$(objs)
gcc -o test $^
# .a.o.d .b.o.d【依赖文件格式定义 .文件名.d 】
dep_files := $(foreach f,$(objs),.$(f).d) # 依赖文件名为 .a.o.d .b.o.d
dep_files := $(wildcard $(dep_files)) # 根据依赖文件名,判断这些文件是否存在,存在则 dep_files 等于他们
ifneq ($(dep_files),) # 如果依赖文件存在,说明不是第一次执行,则包含依赖文件
include $(dep_files)
endif
%.o : %.c
gcc -Wp,-MD,[email protected] -c -o $@ $< # 这里编译时生成依赖文件,并根据依赖文件生成可执行文件
clean:
rm *.o test
/******************** 编写一个通用的Makefile *******************************************************************************************/
程序结构:
./
|-- display 【子目录生成一个 built-in.o 】
| |-- 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
| 【会根据子目录下的 built-in.o 以及本地的 built-in.o 生成目标文件 】
|-- log.txt
|-- main.c
|-- Makefile.build
`-- Makefile
根目录的 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 # 切换到 -C 指定的目录,使用 -f 指定的 Makefile 文件【这里就是递归进各个子目录生成 built-in.o 再生成根目录的 built-in.o】
$(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 解读:【用于生成目录下的 built-in.o】
PHONY := __build # 特殊目标.PHONY的依赖是假想目标。假想目标是这样一些目标,make 无条件的执行它命令,和目
录下是否存在该文件以及它最后一次更新的时间没有关系
__build:
obj-y :=
subdir-y :=
include Makefile # 包含当前目录下的 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))) # 获得根目录下的文件夹名称,如 display/ 变成 display
subdir-y += $(__subdir-y)
# subdir_objs := c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) # 遍历获得所有子目标下生成 built-in.o
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y)) # 获得子 makefile 中添加的 obj-y 中所有文件名,如 crt.o 经过这个处理就成了 crt
dep_files := $(foreach f,$(cur_objs),.$(f).d) # 根据文件名,转换成 .【文件名】.d 的依赖文件,如 .crt.d
dep_files := $(wildcard $(dep_files)) # 判断这些文件是否存在
ifneq ($(dep_files),) # 如果这些依赖文件存在,说明不是第一次编译,包括进来
include $(dep_files)
endif
PHONY += $(subdir-y) # PHONY = 子目录名
__build : $(subdir-y) built-in.o # __build 依赖于 子目标 及 built-in.o
$(subdir-y): # 对于 subdir-y 这具目标文件,递归调用 Makefile.build 的 make 文件处理
make -C $@ -f $(TOPDIR)/Makefile.build
built-in.o : $(cur_objs) $(subdir_objs) # 对于当前目录的目标文件 built-in.o ,使用当前目录下 .c 生成的 .o 文件生成
$(LD) -r -o $@ $^
dep_file = [email protected] # 要生成的依赖文件是以所有文件名单独命令的,如 .crt.d .utf-16be.d 等等,dep_file = 这些名字
%.o : %.c # 对所有 .c 文件,都生成对应的 .o 文件
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $< # 依次生成各个目标 .o 文件以及依赖文件
.PHONY : $(PHONY)
本程序的 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"来彻底清除