Makefile基础、常用函数及通用Makefile

1.基础

程序的编译过程

  1. 预处理:①把包含的头文件插入源文件中②展开宏定义③根据条件编译选择要使用的代码④最后把代码输出到一个.i格式的文件
  2. 编译:把C/C++代码(.i文件)翻译成.s汇编代码
  3. 汇编:把.s文件翻译成.o格式的机器代码
  4. 链接:把.o文件、库文件等链接起来,生成可执行文件。

规则

目标:依赖1 依赖2 ...
<TAB> 命令
  • 命令执行的条件:依赖文件比目标文件的时间新 或 没有目标文件

静态模式

<targets ...>:<target-pattern>:<prereq-patterns...>
    <commands>
  • targets:定义一系列目标文件,可有通配符
  • target-parrterntargets的模式,即目标集模式
  • prereq-parrterns:目标的依赖的模式,它对target-parrtern形成的模式再进行一次依赖目标的定义

例:

objects = a.o b.o c.S
$(objects): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@

由于target-parrtern为%.o,所以取objects变量中的a.ob.o,其分别依赖于a.cb.c,上面的Makefile等价于

a.o:a.c
	$(CC) -c $(CFLAGS) $< -o $@
b.o:b.c
	$(CC) -c $(CFLAGS) $< -o $@

当然也可以省略targets,直接写<target-pattern>:<prereq-patterns...>,这样所有满足<target-pattern>的文件都会被匹配,而不是在targets中找满足<target-pattern>的文件。
隐式规则
常见的用法:

foo:foo.o bar.o
    gcc -o foo foo.o bar.o

以上Makefile文件中没有写出foo.o以及bar.o文件的生成规则,但是根据隐式规则,make命令将自动寻找foo.c以及bar.c并调用cc命令将源文件生成foo.obar.o
赋值方法

  • = :最基本的赋值
  • ?= :如果没有被赋值过就赋予等号后面的值
  • := :覆盖之前的值
  • += :添加等号后面的值

=?=定义的是延时变量,即当真正使用时这个变量的值才确定。:=是立即变量,在定义时它的值就确定。+=由前面的变量决定是什么变量。

$@:指代当前规则下的目标文件列表
$< :指代依赖文件列表中的第一个依赖文件
$^ :指代依赖文件列表中的所有依赖文件
$? :指代依赖文件列表中新于对应目标文件的文件列表
概念区分
(1)%*的区别:二者都是通配符。*针对Linux系统;%针对当前Makefile文件的内容,如目标、依赖中。
(2)$()${}:在shell中$()内跟一个指令,${}内跟一个变量;在Makefile中$()${}内跟变量时等价,若想调用指令需加上“shell”:$(shell 指令)
其它
(1)根据交叉编译器找到libgcc库的路径

arm-linux-gcc -print-libgcc-file-name

(2)export 变量
变量和它的值将被加入到当前工作的环境变量中,以后在make执行的所有规则的命令都可以使用这个变量,一般用来给子Makefile用。
(3)VPATH
make可以识别Makefile中定义的名为VPATH的变量,其定义了依赖文件的搜索目录
(4).PHONY伪目标

常用函数
这里只列举常用的几个,函数调用格式为:

$(function arguments)

1.patsubst:寻找text中符合格式pattern的内容,用replacement替换它们。patternreplacement中可以使用通配符%

$(patsubst pattern,replacement,text)
$(patsubst %.c,%.o,x.c.c bar.c)//结果为x.c.o bar.o

2.filter:取出text中符合格式pattern...的内容

$(filter pattern...,text)
$(filter %.c %.s,foo.c bar.c baz.s ugh.h)//结果为foo.c bar.c baz.s
  • filter-out是除去符合格式pattern...的内容

3.wildcard:获取目录下的文件

$(wildcard pattern)
当前目录下有文件1.c、2.c、1.h、2.h,则:
c_src := $(wildcard *.c)//结果为1.c 2.c

4.foreach:循环操作
循环将list中的每一项取出赋给var,然后执行text中的指令(指令中一般含有var)

$(foreach var,list,text)
objs := a b c d
dep_files := $(foreach f,$(objs),.$(f).d)//结果为a.d b.d c.d d.d

5.变量中的文本替换(以替换后缀名为例)

var := a.c b.c
$(var:%.c=%.o)${var:.c=.o}  #结果为a.o b.o

2.编写通用Makefile

先来看一个Makefile

test:a.o b.o
	gcc -o test a.o b.o
%.o:%.c
	gcc -c -o $@ $<

如果a.c有一个头文件a.h,用户仅修改了a.h,而因为a.h不在依赖中,则gcc不会被执行。而如果每个.c文件的头文件都写到Makefile中,如a.o:a.c a.h,又太麻烦。
解决:通过GCC编译选项-Wp,-MD生成.c文件的依赖文件。
如a.c文件执行gcc -Wp,-MD,.a.o.d -c -o a.o a.c,则会生成一个a.o.d,内容如下

a.o:a.c xx.h yy.h  #假设a.c包含了头文件xx.h和yy.h

以如下工程结构为例,仿照Linux内核写一个通用Makefile
在这里插入图片描述
编译过程:

  1. 把display目录下的test目录的test.c编译成test.o,并将所有的.o文件打包成built-in.o
  2. 然后返回到上一层display目录,把disp_manager.cfb.c编译成.o文件
  3. disp_manager.ofb.o和test目录下的built-in.o打包成display目录下的built-in.o
  4. 经过若干个目录里的.c文件编译打包成built-in.o后,main.c也被编译成了main.o,然后把main.o与其所在目录对应的子目录的built-in.o打包成顶层目录下的built-in.o
  5. 最后链接成目标文件

(1)顶层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

#导出变量给Makefile.build用
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP

# 指定编译参数: -Wall:开启全部警告; -O2: 优化选项; -g: 加上调试信息
CFLAGS	:= -Wall -O2 -g
# 指定编译头文件目录  //若没有指定头文件目录,默认去/usr/include找
CFLAGS	+= -I $(shell pwd)/include
# 指定连接参数: -lm: 表示数学库; -lfreetype: 表示freetype库
LDFALGS := -lm -lfreetype
# 导出 CFLAGS LDFALGS 
export CFLAGS LDFALGS 
TOPDIR := $(shell pwd)
export TOPDIR 

# 最终编译出来的目标文件 show_file
TARGET := show_file
#obj-y赋值为顶层的main.o和所有目录
obj-y += main.o
obj-y += display/
obj-y += draw/
obj-y += encoding/
obj-y += fonts/

# 第一个规则
all : 
	#-C后面跟一个目录,即进入顶层目录执行make,-f后跟一个文件,即用Makefile.build递归编译每个目录
	make -C ./ -f $(TOPDIR)/Makefile.build
	#在Makefile.build中递归最终生成顶层的built-in.o,将其链接生成最终文件
	$(CC) $(LDFALGS) -o $(TARGET) built-in.o
# 清除
clean:
	rm -rf $(shell find -name "*.o")
	rm -rf $(TARGET)
distclean:
	rm -rf $(shell find -name "*.o")
	rm -rf $(shell find -name "*.d")
	rm -rf $(TARGET)

由上面的目标all下面的指令可知,所有目录都通过Makefile.build这个文件里面的规则来编译,Makefile.build中会递归每个目录,最终由顶层目录的build-in.o生成可执行文件$(TARGET),即showfile
(2)Makefile.build

# 伪目标,即本文件的目标
PHONY := __build
__build:   #类似C函数声明,具体依赖在后面定义,目的是声明为第一个目标
#初始化所有会使用到的变量:确保类型正确、不会继承环境变量中的值
obj-y :=
subdir-y :=

# 包含当前目录的Makefile,里面会对obj-y赋值
include Makefile

# 取出obj-y定义的子目录,并把"/"去掉(规定目录后面要加一个/)
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))  
subdir-y += $(__subdir-y) 

# 所有子目录的built-in.o,即subdir_objs = dir1/built-in.o dir2/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) 

# 取出obj-y定义的xxx.o
cur_objs := $(filter-out %/, $(obj-y))

# 依赖文件
dep_files := $(foreach f,$(cur_objs),.$(f).d) #目录下xxx.o文件对应的依赖文件为.xxx.d
dep_files := $(wildcard $(dep_files)) #若.xxx.d不存在,则从变量中移除
ifneq ($(dep_files),)    #非空则包含依赖文件
	include $(dep_files)
endif

PHONY += $(subdir-y)  #子目录的每个目录名都定义为伪目标

#__build为伪目标,也是第一个声明的目标,伪目标依赖对应的命令100%会被执行
__build:$(subdir-y) built-in.o    #依赖于子目录和该目录的built-in.o

# 进入子目录编译(顶层目录和每个子目录都递归用保存在顶层的Makefile.build来编译)
$(subdir-y):
	make -C $@ -f $(TOPDIR)/Makefile.build    #-C为进入那个目录,-f使用顶层目录的Makefile.build来编译
#当前目录的所有xxx.o和子目录打包好的built-in.o生成当前目录的built-in.o
built-in.o: $(cur_objs)  $(subdir_objs)  
	$(LD) -r -o $@ $^   #-r:见下方解释

#生成当前目录下的依赖文件,如xx.c文件对应生成.xx.d依赖文件,该变量用在下方
dep_file = .$@.d  #前面声明的是dep_files

%.o : %.c     #所有.o文件的生成规则,同时生成依赖
	$(CC) $(CFLAGS) -Wp,-MD, $(dep_file) -c -o $@ $<

.PHONY : $(PHONY) #如PHONY变量中的subdir-y,即会无条件执行上面 $(subdir-y):的make命令

其中ld -r-r为relocateable,即产生可重定位的输出。即产生一个输出文件,它可再次作为’ld'的输入,这经常被叫做"部分连接"。
(3)每个目录下的Makefile
Makefile.build文件可知,其声明了一个变量obj-y,然后会include每个目录下的Makefile,所以每个目录下的Makefile只需将该目录下的文件和目录添加到变量obj-y中,其中目录最后要加一个/,以display目录下的Makefile为例:

obj-y += disp_manager.o
obj-y += fb.o
obj-y += test/    #test是子目录下的子目录,里面也有一个Makefile

最后在顶层目录执行make命令即可生成最终的可执行文件。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tilblackout

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值