1.基础
程序的编译过程
- 预处理:①把包含的头文件插入源文件中②展开宏定义③根据条件编译选择要使用的代码④最后把代码输出到一个
.i
格式的文件 - 编译:把C/C++代码(
.i
文件)翻译成.s
汇编代码 - 汇编:把
.s
文件翻译成.o
格式的机器代码 - 链接:把
.o
文件、库文件等链接起来,生成可执行文件。
规则:
目标:依赖1 依赖2 ...
<TAB> 命令
- 命令执行的条件:依赖文件比目标文件的时间新 或 没有目标文件
静态模式:
<targets ...>:<target-pattern>:<prereq-patterns...>
<commands>
targets
:定义一系列目标文件,可有通配符target-parrtern
:targets
的模式,即目标集模式prereq-parrterns
:目标的依赖的模式,它对target-parrtern
形成的模式再进行一次依赖目标的定义
例:
objects = a.o b.o c.S
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
由于target-parrtern
为%.o,所以取objects变量中的a.o
和b.o
,其分别依赖于a.c
和b.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.o
和bar.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
替换它们。pattern
和replacement
中可以使用通配符%
$(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
编译过程:
- 把display目录下的test目录的test.c编译成
test.o
,并将所有的.o文件打包成built-in.o
- 然后返回到上一层display目录,把
disp_manager.c
和fb.c
编译成.o文件 - 把
disp_manager.o
、fb.o
和test目录下的built-in.o
打包成display目录下的built-in.o
- 经过若干个目录里的.c文件编译打包成
built-in.o
后,main.c
也被编译成了main.o
,然后把main.o
与其所在目录对应的子目录的built-in.o
打包成顶层目录下的built-in.o
- 最后链接成目标文件
(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
命令即可生成最终的可执行文件。