目录
本文代码点击此处跳转
https://download.csdn.net/download/liangwenhao1108/12256912
https://github.com/WHaoL/study/tree/master/Makefile/Demo01
1. Makefile介绍
简介:
当你启动make工具对工程进行编译时,make会自动读取Makefile中的编译规则,进行自动化编译
make:是一个命令工具(是一个解释Makefile中指令的命令工具)-->构建工具
Makefile:记录了编译规则 -->构建工具需要加载的文件
学习目标:
读懂别人写的Makefile
自己独立写Makefile
2. Makefile中的规则
2.1 文件命名
文件命名--两种书写格式: (首字母大小写都可以)
Makefile
makefile
注释使用'#'
2.2 Makefile中的构建规则
# make的构建规则
目标 ... : 依赖 ...
命令 # 需要有一个tab缩进//规则中的命令
...
//整体又可看作一个规则
# 目标: 最终要生成的文件, 一个或多个 -->通过命令生成
# 依赖: 生成目标需要使用的文件,一个或多个 -->在命令中被使用
# 命令: 一个或多个gcc命令, 通过对依赖进行编译生成目标
2.3 make的工作原理
Makefile文件中有多个规则
1、直接执行:make
执行make命令,生成的是第一条规则的目标,其他规则都是辅助性的
2、输入:make 子目标名字
生成的是子目标
1、命令在执行之前会检测依赖是否存在
1、如果依赖不存在:
make会向下检测其他的规则, 看有没有一条规则是用来生成这个依赖的
如果找到了这个规则, 执行规则中的命令, 得到了需要的依赖
2、依赖存在就不向下检测了
2、关于文件更新 -> 不适用于伪目标
执行make命令时, 规则中的依赖和目标,这些文件会进行一个文件时间早晚的比较
1、如果目标文件不存在
目标文件被生成 -> 执行对应的命令
2、目标存在时
1.如果依赖时间早, 目标时间晚 -> 正常
目标不需要重写更新, 不需要生成, 使用旧的就可以
2.依赖时间晚, 目标的时间早 -> 不正常
说明依赖被重新修改 -> 目标需要重新生成
对应的命令就被执行了
2.4 Makefile中的变量(提高Makefile的编写效率)
# 没有类型,默认按照字符串处理
1、自定义变量,变量名小写
# 规则: 变量名=变量值
# 举例:var=a.c b.c c.c
# 取变量的值: $(变量名)
2、默认的自带变量,内部变量, 变量名大写
如: CC等价于gcc
# 取值: var=$(CC)
CC = gcc #arm-linux-gcc
CPPFLAGS : C预处理的选项如:-I
CFLAGS: C编译器的选项–Wall –g -c
LDFLAGS : 链接器选项–L –l
%.o:%.c
$(CC) –c $(CFLAGS) $(CPPFLAGS) $< -o $@
3、自动变量,只能在'规则中的命令中'使用
$@: 规则中的目标
$<: 规则中的第一个依赖
$^: 规则中的所有的依赖
# 例子
app:a.o b.o c.o
gcc a.o b.o c.o -o app
# 改进
app:a.o b.o c.o
gcc $^ -o $@
2.5 Makefile中的模式匹配
# 语法
%.o:%.c
gcc -c $< -o $@
%: 通配符, 前后对应的是同一字符串
2.6 Makefile中的函数
makefile中所有的函数都是有返回值的, 因此所有函数的样式:
'$(函数名 参数1, 参数2, 参数3, ...)'
1、wildcard:获取指定目录下指定类型的文件列表
'$(wildcard PATTERN...)'
功能:
PATTERN 指的是某个或多个目录下的对应的某种类型的文件
如果有多个目录,一般使用空格间隔
返回值:
得到的若干个文件的文件列表, 文件名之间以空格间隔
示例:
'$(wildcard *.c , ./sub/*.c)'
* :代表的是任意字符串
返回值格式: a.c b.c c.c d.c e.c f.c ./sub/aa.c ./sub/bb.c
2、patsubst:模式字符串替换函数
$(patsubst <pattern>,<replacement>,<text>)
pattern:当前的字符串类型
replacement:替换后的字符串类型
text:src字符串
功能:
查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。
这里,<pattern>可以包括通配符`%`,表示任意长度的字串。如果<replacement>中也包含`%`,那么,<replacement>中的这个`%`将是<pattern>中的那个`%`所代表的字串。(可以用“\”来转义,以`\%`来表示真实含义的`%`字符)
返回值:
函数返回被替换过后的字符串。
示例: `$(patsubst %.c, %.o, x.c.c bar.c)`
把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o]
返回结果是“x.c.o bar.o”
举例:
第一参数的% 通配符 和第二个参数的 % 对应, 代表的是同一个字符串
(patsubst %.c, %.o, a.c b.c d.c e.c)
# 得到的返回值:
a.o b.o d.o e.o
2.7 伪目标
语法:
.PHONY:伪目标文件的名字
#声明为伪目标时,这个目标在磁盘上不生成实体,
#也就不会进行 目标依赖存在与否、目标依赖时间对比 等一些操作了,
#因此也就不会和磁盘上已经存在文件产生冲突
#在命令前加'-',如果碰到错误会继续执行下面的指令,并不会中断执行
#'-f' 强制执行命令,不会提示
.PHONY:clean
clean:
-rm $(obj) $(target) -f
clean命令中的特殊符号:
'-'此条命令出错,make也会继续执行后续的命令。如:“-rm main.o”
2.8 make命令的使用
make 默认执行第一个出现的目标,可通过make dest指定要执行的目标
make -C 指定目录: 进入指定目录,调用里面的Makefile
make -n :只打印要执行的命令,不会真正执行命令
make -p :显示隐含规则数据库中的信息
make -f :-f执行一个makefile文件名称,使用make -f 强制执行指定的makefile
3 Makefile例子
$ gcc a.c b.c c.c -o app
# 改成一个规则
###################### 版本1 #################
app:a.c b.c c.c
gcc a.c b.c c.c -o app
总结:
当只更新了一个源文件时,却需要重新编译所有的源文件,比较耗时,效率低
改进措施:
将这个唯一的规则拆分为若干个
###################### 版本2 #################
app:a.o b.o c.o
gcc a.o b.o c.o -o app
a.o:a.c
gcc -c a.c
b.o:b.c
gcc -c b.c
c.o:c.c
gcc -c c.c
总结:
改掉了版本1的弊端,却不适合源文件非常多的那种工程(只适合小工程),
只适合源文件较少的情况,太冗余
因为每个源文件都需要书写一个规则与之对应,这样导致Makefile书写起来比较冗长
改进措施:
使用变量进行精简
###################### 版本3 #################
使用:
Makefile中的变量和模式匹配进行改进
target=app
obj=add.o sub.o mult.o div.o main.o
$(target):$(obj)
gcc $(obj) -o $(target)
%.o:%.c
gcc -c $< -o $@
缺点:如果源文件比较多,在写obj(.o文件)时比较麻烦
改进:自动搜索指定目录下的源文件(.c文件),将.c-->.o文件
使用Makefile中的函数
###################### 版本4 #################
target=app
#搜索指定目录下的指定格式的文件.c
#得到.c文件列表
'src=$(wildcard ./*.c)'
#将得到的文件列表中文件的后缀.c->.o
'obj=$(patsubst %.c,%.o,$(src))'
$(target):$(obj)
gcc $(obj) -o $(target)
%.o:%.c
gcc -c $< -o $@
缺点:
功能不完善,只能编译代码,不能删除生成的中间文件
改进:
添加一个删除文件的规则
###################### 版本5 #################
target=app
#搜索指定目录下的指定格式的文件 .c
#得到.c文件列表
'src=$(wildcard ./*.c)'
#将得到的文件列表中文件的后缀.c->.o
obj=$(patsubst %.c,%.o,$(src))
$(target):$(obj)
gcc $(obj) -o $(target)
%.o:%.c
gcc -c $< -o $@
#在命令前加'-',如果碰到错误会继续执行下面的指令,并不会中断执行
#'-f' 强制执行命令,不会提示
.PHONY:clean
clean:
-rm $(obj) $(target) -f
###################### 版本6 #################
CC=gcc
CFlagesDebug=-g -w -ggdb -std=c11 -c
CXX=g++
CxxFlagesDebug=-g -w -ggdb -std=c++11 -fshort-wchar -c
#默认情况下,windows 下的 wchar_t 占两个字节的长度,而 linux 下的 wchar_t 占四个字节的长度,
#可以在使用 gcc/g++ 编译程序的时候再后面跟上 -fshort-wchar 来解决这个问题。
target=execute
src=$(wildcard *.c)
obj=$(src:.c=.o)
$(target):$(obj)
$(CC) -o $(target) $(obj)
%.o:%.c
$(CC) -MM $^
$(CC) $(CFlagesDebug) $^ -o $@
#'-M' : 自动找寻源文件中包含的头文件(.h文件),并生成一个依赖关系
#GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来
#此处:$(CC) -MM $^,就是先为*.c找到需要的*.h文件,并建立完整的依赖关系
#如 gcc -MM main.c -->输出为:main.o:main.c main.h
#清除:.o
.PHONY:DotObjs
DotObjs:
-rm -f $(obj)
#清除:可执行文件
.PHONY:DotExecute
DotExecute:
-rm -f $(target)
#清除:.o、可执行文件
.PHONY:clean
clean:DotObjs DotExecute #执行顺序依次为1.DotObjs的命令、2.DotExecute的命令、3.clean的命令
#如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令
.PHONY:cdpwd1
cdpwd1:
cd ../
pwd
.PHONY:cdpwd2
cdpwd2:
cd ../;pwd
# call函数代码测试如下
#reverse=$(1) $(2) #输出 a b 分别拿到第1个参数、第2个参数
reverse=$(2) $(1) #输出 a b 分别拿到第2个参数、第1个参数
foo=$(call reverse ,a,b)
.PHONY:testfoo
testfoo:
@echo $(foo)
###################### 版本6 #################
CC=gcc
CFlagesDebug=-g -w -ggdb -std=c11 -c
CXX=g++
CxxFlagesDebug=-g -w -ggdb -std=c++11 -fshort-wchar -c
Project=execute
src=$(wildcard *.c)
obj=$(src:.c=.o)
$(Project):$(obj)
$(CC) -o $(Project) $(obj)
%.o:%.c
$(CC) -MM $^
$(CC) $(CxxFlagesDebug) $^ -o $@
#清除:.o
.PHONY:DotObjs
DotObjs:
-rm -f $(obj)
#清除:可执行文件
.PHONY:DotExecute
DotExecute:
-rm -f $(Project)
#清除:.o、可执行文件
.PHONY:clean
clean:DotObjs DotExecute