08.通用Makefile+Makefile基础

0.程序的编译过程

(1)预处理
C/C++源文件中,以"#“开头的命令被称为预处理命令,如包含命令、宏定义命令、条件编译命令等。预处理就是将要包含的文件插入到源文件中、宏定义展开、根据条件编译命令选择使用的代码,最后将这些代码输入到一个”.i"文件中等待进一步处理,预处理用到arm-linux-cpp工具。
(2)编译
把C/C++代码(或者.i)翻译成汇编,所用到的工具是cc1.
(3)汇编
汇编是将第二步输出的汇编代码翻译成一定格式的机器码,LINUX系统上一般表现为ELF文件(OBJ文件),用到的工具是arm-linux-as。
(4)连接
连接就是将上一步生成的OBJ文件和系统库的OBJ文件、库文件连接起来,最终生成可以在特定平台运行的可执行文件,用到的工具是arm-linux-ld。

1.Makefile基础

makefile基础知识

1、赋值符号的区别:

  • = 是最基本的赋值,用到了之后才赋值,不能在变量后追加内容

  • := 是覆盖之前的值,立即赋值,可以在变量后追加内容

  • ?= 是如果没有被赋值过就赋予等号后面的值

  • += 是添加等号后面的值

2、自动变量:

  • $< 第一个依赖文件的名称

  • $? 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚

  • $@ 目标的完整名称

  • $^ 所有的依赖文件,以空格分开,不包含重复的依赖文件

3、常用的函数:

  • $(patsubst %.c,%.o,x.c.cbar.c)
    把字串“x.c.cbar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”

  • $(filter <pattern…>,< text> )
    以模式过滤 字符串中的单词,保留符合模式的单词。可以有多个模式。

  • $(filter-out <pattern…>,< text> )
    找到< pattern>中所有包含< text>的变量

  • $(foreach < var>,< list>,< text> )   
    把参数< list>中的单词逐一取出放到参数< var>所指定的变量中,然后再执行< text>所包含的表达式。每一次< text>会返回一个字符串,循环这个过程。

  • shell函数,例如files := $(shell echo *.c)

2.Makefile

1.规则

目标:依赖1 依赖2
	命令
	
test: a.c  b.c  a.h
	gcc -o test a.c b.c

a.“依赖”文件 比 “目标”文件 新
b.没有“目标”文件
2.优化
(1)每次执行上面一条Makefile,都会把所有的源文件执行一遍,不普适

test: a.o  b.o                #依赖于两个目标文件
	gcc -o test a.o b.o
a.o: a.c a.h				 
	gcc -c -o a.o a.c
b.o: b.c
	gcc -c -o b.o b.c

(2)通配符:文件越多,不能一个一个写,改成通配符

test: a.o  b.o              
	gcc -o test a.o b.o
a.o:a.c a.h				     #当a.h改变时,会重新编译a.c
%.o: %.c			 
	gcc -c -o $@ $<

(3)生成依赖文件:以上缺点是所有.o的依赖文件都需要一个一个去写,gcc中有自动生成依赖的选项,保存在a.d文件中

gcc -c -o a.o a.c -Wp,-MD,a.d

再改Makefile

test:a.o b.o
	gcc -o test a.o b.o

%.o : %.c
	gcc -Wp,-MD,.$@.d -c -o $@ $<

(4)利用自动生成的依赖文件:第一次执行的时候会生成 %.o.d 文件,这个文件中包含对应.c源文件的依赖关系,这个依赖关系的格式也是Makefile格式的,如下所示
在这里插入图片描述
所以,把这个文件包含到主Makefile中,就会把对应.o文件的依赖关系也加进去,如下:

objs := a.o b.o

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

dep_files := $(foreach f,$(objs), .$(f).d)       #dep_files = 所有目标文件名.d  ,即a.o.d  b.o.d
dep_files := $(wildcard $(dep_files))			 #用wildcard函数来看dep_files中所存在的文件,然后再赋值给他自己
ifneq($(dep_files) , )							 #如果dep_files不为空的话,就代表编译过,生成过依赖文件了
	include $(dep_files)						 #就包含这些依赖文件
endif

%.o: %.c									     #第一次执行的时候没有依赖文件,只依赖于.c文件,编译过一次后,就会有依赖文件,之后就会依赖于依赖文件
	gcc -Wp,-MD,.$@.d -c -o $@ $<

clean:
	rm *.o test

3.通用的Makefile

用法:每次使用时,只需要将Makefile和Makefile.build拷贝到需要的工程里,然后更改Makefile中编译和连接的FLAGS(CFLAGS/LDFLAGS),然后各子目录中创建Makefile,增加需编译的目标文件即可。

框架如下图所示,碰到目录先进入目录,直到进入最里面的目录,编译出built_in.o,再返回上一级目录,与该目录下的.o文件共同生成built_in.o文件,直到返回顶层目录,将所有文件夹下的built_in.o文件共同编译成built_in.o文件。

  • 顶层目录的Makefile
  • 顶层目录的Makefile.build
  • 各级子目录的Makefile

在这里插入图片描述
(1)各级子目录的Makefile

obj-y += file.o		      #表示把当前目录下的file.c编进程序里
obj-y += subdir/		  #表示要进入subdir这个子目录下去寻找文件,然后编进程序里,哪些文件由subdir目录中的Makefile决定。
						  #subdir后的 / 不可省略

(2)顶层目录的Makefile

  • 定义obj-y来指定根目录下要编进程序去的文件、子目录
  • 定义工具链、编译参数、链接参数,就是文件中用export导出的各变量,给Makefile.build用的
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											#设置一些编译时的选项(-Wall:打开所有警告;-g:产生调试信息,GBD可使用;-O2:多优化一些,增加了编译时间,提高生成代码运行效果)
CFLAGS += -I $(shell pwd)/include -I /usr/include/freetype2		#设置一些头文件的路径,Makefile换工程使用时,根据需要改一下这个

LDFLAGS := -lm -lfreetype -lvga -lvgagl							#设置连接时的一些库,Makefile换工程使用时,根据需要改一下这个

export CFLAGS LDFLAGS

TOPDIR := $(shell pwd)							#把当前目录设置为顶层目录,主要是给各级编译时,需要用到顶层目录的Makefile.build
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,但是当作Makefile来用。 
												 #这句的意思就是在 “从当前目录,一直到obj-y及下级obj-y中所包含的” 所有目录下执行Makefile.build
	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o	 #上一条已经把各级Makefile中加入的文件(包括顶层目录的main.o)统共编译成一个built-in.o,这里直接根据这个文件连接成目标可执行文件


clean:
	rm -f $(shell find -name "*.o")			#删除所有.o文件
	rm -f $(TARGET)							#删除生成的目标文件

distclean:
	rm -f $(shell find -name "*.o")		
	rm -f $(shell find -name "*.d")			#删除生成的依赖文件
	rm -f $(TARGET)
	

(3)顶层目录的Makefile.build

PHONY := __build
__build:		# make时,进来首先执行这个

obj-y :=
subdir-y :=

include Makefile		#包含当前目录的Makefile,这里面存着需要在当前目录编译的目标文件

__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))	#找到当前目录中,所有%/格式的文件,再把/符号去掉,作为变量传给__subdir-y
subdir-y	+= $(__subdir-y)     						# subdir-y =  __subdir-y

subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)	#各个下级目录下编译成的built-in.o,放到这个subdir_objs变量中

cur_objs := $(filter-out %/, $(obj-y))				#从当前目录下得到当前目录需要编译的源文件
dep_files := $(foreach f,$(cur_objs),.$(f).d)		#依赖文件,把当前目录下所有的.o文件加上.d变成 %.o.d 赋值给dep_files
dep_files := $(wildcard $(dep_files))				#dep_files  从这个变量中找到实际存在的文件,赋值给其自己

ifneq ($(dep_files),)						#如果不为空,就include这些文件
  include $(dep_files)
endif

PHONY += $(subdir-y)

__build : $(subdir-y)   built-in.o		#__build依赖于子目录 和 当前目录的built-in.o

$(subdir-y):							#对当前目录的所有子目录执行以下,这个没有依赖文件,所以是无条件执行的
	make -C $@ -f $(TOPDIR)/Makefile.build

built-in.o : $(cur_objs)  $(subdir_objs)		#本目录下的built-in.o 依赖于 当前目录所有的.o 和 各下级目录生成的 built-in.o
	$(LD) -r -o $@ $^				 		    #把当前目录下所有的.o 和 各级目录下所有 built-in.o 编译成一个本目录的 built-in.o

dep_file = .$@.d

%.o : %.c					#当前目录的源文件,依赖于.c
	$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<		#当前目录下,所有的.o文件依赖于.c,编译生成.o文件,并创建对应的依赖文件
	
.PHONY : $(PHONY)

总结Makefile.build:

  • 执行时,主要生成的是__build这个伪文件,设置这个伪文件的目的就是执行它的各个依赖,而不是真正的要去生成它
  • __build : $(subdir-y) built-in.o: 伪文件的依赖有两个,一个是子目录(包括子目录的个数和名字等),另外一个就是当前目录的built-in.o,两个只要其中一个有变动,就会重新执行build。
  • $(subdir-y):
    make -C $@ -f $(TOPDIR)/Makefile.build

    检测 __build时, ( s u b d i r − y ) 是 其 依 赖 文 件 , 而 (subdir-y)是其依赖文件,而 (subdiry)(subdir-y): 这里是没有依赖文件,无条件执行的,所以每次都会执行这个,只要调用了Makefile.build
  • built-in.o : $(cur_objs) $(subdir_objs) :本目录的built-in.o依赖于本文件的.o和下级目录的.o
    而本目录下的.o文件又依赖于.c(或者之前生成的依赖文件),这样本文件下的.c又会得到编译

先进入每个子目录,编译各自的.c文件(根据.c或者之前生成的依赖文件),再将该子目录下的所有.o编译成build-in.o,再递归向上,每一层目录出来都只有一个build-in.o文件,最终递归到顶层目录,各子目录和顶层的main.o合并得到顶层目录的build-in.o,最后在Makefile(不是Makefile.build)中,将顶层的build-in.o连接成可执行文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值