4、数码相框之编写通用的Makefile

文章目录

  • 1、程序的编译过程
    • 介绍
    • VC下程序的编译
  • 2、在 linux 下面实现像 VC 下程序的编译的过程
    • 使用命令a. gcc -o test a.c b.c编译
    • 使用Makefile编译
  • 3、重新写 show_files 工程的 Makefile
    • 子目录 Makefile
    • 顶层目录 Makefile
    • 顶层目录 Makefile.build
    • 上面涉及makefile的函数或命令
      • filter函数
      • patsubst函数
      • foreach函数
      • filter-out函数
      • wildcard函数
      • 包含include
      • PHONY的作用
  • 4、Makefile使用

本节源码在 github仓库Makefile

1、程序的编译过程

介绍

4、数码相框之编写通用的Makefile_第1张图片

  • 1、预处理:检查语法,包含头文件,条件编译,展开宏,去掉注释;
  • 2、编译:将 xxx.c ->xxx.s (汇编文件);
  • 3、汇编:将 xxx.s -> xxx.o (机器码);
  • 4、链接:xxx.o + 库文件 = 可执行程序;

gcc –o a a.c 可把1、2、3、4四个步骤一起完成。
4、数码相框之编写通用的Makefile_第2张图片

VC下程序的编译

4、数码相框之编写通用的Makefile_第3张图片
VC 下程序的编译过程总结:
4、数码相框之编写通用的Makefile_第4张图片

2、在 linux 下面实现像 VC 下程序的编译的过程

代码为07.通用的Makefile_源码_文档_图片\09.Makefile\source\test中的Makefile
在这里插入图片描述

使用命令a. gcc -o test a.c b.c编译

  • 对于a.c:预处理、编译、汇编
  • 对于b.c:预处理、编译、汇编
  • 最后链接

优点:命令简单
缺点:如果文件多即使只修改了一个文件,但所有的文件都要重新"预处理、编译、汇编",效率低

使用Makefile编译

核心:规则
目标:依赖1 依赖2
	 命令
命令执行的条件:
i. "依赖"文件 比 "目标"文件 新
ii.没有"目标"这个文件

第一个 Makefile :
在这里插入图片描述
第二个 Makefile :
4、数码相框之编写通用的Makefile_第5张图片
这种写法,只修改了头文件 a.h 不会重新编译。

第三个 Makefile:使用通配符
4、数码相框之编写通用的Makefile_第6张图片
$@:表示目标;
$<:表示第一个依赖;
$^:表示所有的依赖;
这种写法,只修改了头文件 a.h 会重新编译;

第四个 Makefile : 使用通配符 + 自动生成依赖文件

objs := a.o b.o

test:$(objs)
	gcc -o test $^

# .a.o.d .b.o.d
# foreach函数:对于变量objs里的每一个成员改为.$(f).d(加一个前缀“.”和一个后缀“.d”)
# wildcard函数:查看dep_files中是否有“.d”文件,有的话就取出来
dep_files := $(foreach f,$(objs),.$(f).d)
dep_files := $(wildcard $(dep_files))

# 如果dep_files变量不为空的话,就把dep_files包含进来
ifneq ($(dep_files),)
  include $(dep_files)
endif

# -Wp,-MD,[email protected]:表示生成依赖文件a.o.d、b.o.d
%.o : %.c
	gcc -Wp,-MD,.$@.d -c -o $@ $<

clean:
	rm *.o test
  • 1、执行make命令时,使用命令gcc -o test $^生成第一个目标testtest依赖于objsobjs为两个.o文件;

  • 2、.o文件依赖于.c文件,使用命令gcc -Wp,-MD,[email protected] -c -o $@ $<生成。

  • 其中 -Wp,-MD,[email protected]:表示生成依赖文件a.o.db.o.d

  • 3、对于.d文件来说:

  • 使用dep_files := $(foreach f,$(objs),.$(f).d)函数和dep_files := $(wildcard $(dep_files))函数生成。

  • 其中foreach函数:对于变量objs里的每一个成员改为.$(f).d(加一个前缀“.”和一个后缀“.d”)

  • wildcard函数:查看dep_files中是否有“.d”文件,有的话就取出来

  • 4、再将dep_files变量包含进来

  • 如果dep_files变量不为空的话,就把dep_files包含进来

  • 使用命令:include $(dep_files)包含

这样编写Makefile之后就能自动包含依赖文件了。

3、重新写 show_files 工程的 Makefile

代码为07.通用的Makefile_源码_文档_图片\09.Makefile\source\09.show_file下的_Makefile

子目录 Makefile

obj-y += xxx1.o
obj-y += xxx2.o
obj-y += subdir1/ #想进入某个子目录的时候:

顶层目录 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

# Wall表示列出所有警告 -O2表示优化选项 -g表示加上调试信息
# =表示延时变量,使用时确定其值,且不能在后面追加内容
# :=表示立即变量,其值立即确定
# +=表示追加内容
# -I表示指定目录
# (shell pwd)表示获得当前目录
CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include
# -l表示指定库
# -lm表示数学库、-lfreetype表示freetype库
LDFLAGS := -lm -lfreetype
# 导出变量
export CFLAGS LDFLAGS

# 定义顶层目录为TOPDIR
TOPDIR := $(shell pwd)
export TOPDIR

# 定义目标
TARGET := show_file

# 追加子各个编译文件
obj-y += main.o
obj-y += display/
obj-y += draw/
obj-y += encoding/
obj-y += fonts/

# 这里使用-f是因为:
# 缺省情况下,当make寻找makefile文件时,它试图搜寻具有如下的名字的文件,按顺序:‘GNUmakefile’、‘makefile’和‘Makefile’。
# 通常情况下您应该把您的makefile文件命名为‘makefile’或‘Makefile’。
# (我们推荐使用‘Makefile’,因为它基本出现在目录列表的前面,后面挨着其它重要的文件如‘README’等。)。
# 虽然首先搜寻‘GNUmakefile’,但我们并不推荐使用。除非您的makefile文件是特为GNU make编写的,在其它make版本上不能执行,
# 您才应该使用‘GNUmakefile’作为您的makefile的文件名。
# 如果make不能发现具有上面所述名字的文件,它将不使用任何makefile文件。
# 这样您必须使用命令参数给定目标,make试图利用内建的隐含规则确定如何重建目标。详细内容参见使用隐含规则一节。
# 如果您使用非标准名字makefile文件,您可以使用‘-f’或‘--file’参数指定您的makefile文件。
# 参数‘-f name’或‘--file=name’能够告诉make读名字为‘name’的文件作为makefile文件。
# 如果您使用 ‘-f’或‘--file’参数多于一个,意味着您指定了多个makefile文件,所有的makefile文件按具体的顺序发生作用。
# 一旦您使用了‘-f’或‘--file’参数,将不再自动检查是否存在名为‘GNUmakefile’、‘makefile’或‘Makefile’的makefile文件。
all : 
	make -C ./ -f $(TOPDIR)/Makefile.build
	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o

# 清除,使用 make clean
clean:
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)

# 彻底清除,使用 make distclean
distclean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)

其中-f选项:
4、数码相框之编写通用的Makefile_第7张图片
4、数码相框之编写通用的Makefile_第8张图片

顶层目录 Makefile.build

# 假目标,第一个目标
PHONY := __build
__build:

# 赋一个空值
obj-y :=
subdir-y :=

# 包含当前目录下的Makefile,里面有要包含的obj-y
include Makefile

# 若:obj-y := a.o b.o c/ d/
# 那么函数:filter %/, $(obj-y 表示将不符合%/的内容去除,既保留c/、d/
# $(filter %/, $(obj-y))   : c/ d/
# 函数patsubst %/,%,$(filter %/, $(obj-y))表示在 $(filter %/, $(obj-y) 中,将 %/ 替换成 %
# 那么__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
# 那么函数:filter-out %/, $(obj-y 表示将符合%/的内容去除,既去除c/、d/
# $(foreach f,$(cur_objs),.$(f).d) 生成依赖文件
# $(wildcard $(dep_files)) 查看dep_files中是否有“.d”文件,有的话就取出来
cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))

# 如果dep_files变量不为空的话,就把dep_files包含进来
ifneq ($(dep_files),)
  include $(dep_files)
endif

# 追加假象目标
PHONY += $(subdir-y)

# __build依赖于后面两项
__build : $(subdir-y) built-in.o

# 递归地进行处理子目录使用Makefile.build进行编译
$(subdir-y):
	make -C $@ -f $(TOPDIR)/Makefile.build

# 生成built-in.o
built-in.o : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^

# 依赖文件,其中”=“表示用到的时候才会赋值
dep_file = .$@.d

# 生成.o文件
%.o : %.c
	$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<

# 假象目标”.PHONY“依赖于”PHONY“
.PHONY : $(PHONY)

上面涉及makefile的函数或命令

filter函数

4、数码相框之编写通用的Makefile_第9张图片

假设 obj-y := a.o b.o c/ d/
$(filter %/, $(obj-y)) 最后返回 c/ d/

patsubst函数

在这里插入图片描述

所以最终上面一段的结果就是(假设 obj-y := a.o b.o c/ d/):
__subdir-y : c d
subdir-y : c d

foreach函数

4、数码相框之编写通用的Makefile_第10张图片
在这里插入图片描述

把第二个参数的内容分别传递给第一个参数
去执行第三个语句
中间变量没什么意义,只是临时的,所以上面的f只是个临时局部变量
由于subdir-y 为 c d,分别放到第三个参数中运行,上面的最终结果为
subdir_objs := c/built-in.o d/built-in.o

filter-out函数

4、数码相框之编写通用的Makefile_第11张图片

wildcard函数

4、数码相框之编写通用的Makefile_第12张图片
在这里插入图片描述
最后一句的目的是什么呢?

这句的目的这里是由于我们要用到wildcard这个函数,作用就是在当前目录下查找符合该函数的参数的文件,并返回。

所以我们要先将所有的文件生成依赖文件的格式,用来确定查找的范围,在用wildcard函数判断在这个范围内的依赖文件哪些是存在的。

包含include

在这里插入图片描述
如果依赖文件存在,则包含依赖文件,我们知道dep_files里面的内容就是各个.h,也就是包含了这些.h

PHONY的作用

在这里插入图片描述
继续分析
在这里插入图片描述
最终我们想在顶层makefile.build生成一个__build文件,他依赖的目标有两个,一个是顶层目录下的所有飞文件夹的文件,之前假设当前目录下有obj-y := a.o b.o c/ d/,则a.o b.o最终生成built-in.o,一个是顶层目录下的目录里面生成的subdir-y

接下来就是完成上面两个依赖的规则:
在这里插入图片描述
TOPDIR是我们在顶层makefile定义的宏,为当前目录,并且已经export了,所以可以用之前我们假设了obj-y := a.o b.o c/ d/,然后subdir-y : c d

由于subdir-y有多个值,所以这个多目标的规则,应该如果展开这个规则呢?
继续分析第二个依赖:
在这里插入图片描述
展开为:

Built-in.o : a.o b.o c/built-in.o d/built-in.o
	$(LD) -r -o $@ $^

其中

subdir_objs :=c/built-in.o d/built-in.o
	cur_objs := a.o b.o

之前我们分析了每一层的文件最终都会生成一个built-in.o文件,而这个built-in.o又由两部分组成

  • 1、当前层的.c文件(不一定只有.c,只要不再当前文件内的子目录下的都是)
  • 2、当前层的子目录下的文件最终也会生成一个build-in.o

所以一层的最终目标文件的依赖为当前层的obj文件,和当前层子目录下的build-in.o

我们的 Makefile 是想生成什么样的东西?
每个子目录里面把生成的 xxx.o 打包成 built-in.o,然后几个 built-in.o 链接到一起。
4、数码相框之编写通用的Makefile_第13张图片

4、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,数码相框)