Makefile学习笔记

前言

一直想系统学习一下Makefile,搜了一下网上教程,排前面的还是很多年前的那篇陈浩大佬写的《跟我一起写Makefile》(斯人已逝,感谢陈浩大佬)。下面是我看这篇教程的学习笔记。

直观体验

基本规则

target ... : prerequisites...
	recipe
	...
	...

prerequisites:翻译过来是“先决条件”,可以理解成target的条件,好理解一点的就是依赖文件
recipe:翻译是“处方,食谱”的意思,这里可以理解成生成这个target的方法,好理解一点就是执行的命令
“…”表示可以有多个target和多个preprequiste
核心规则:当preprequistes中存在一个以上的文件比target要新(时间戳新),则执行recipe
make执行过程
(转载自https://blog.csdn.net/An1540879349/article/details/51221857)

  1. 依次读取变量“MAKEFILES”定义的makefile文件列表
  2. 读取工作目录下的makefile文件(根据命名的查找顺序“GNUmakefile”,“makefile”,“Makefile”,首先找到那个就读取那个)
  3. 依次读取工作目录makefile文件中使用指示符“include”包含的文件
  4. 查找重建所有已读取的makefile文件的规则(如果存在一个目标是当前读取的某一个makefile文件,则执行此规则重建此makefile文件,完成以后从第一步开始重新执行)
  5. 初始化变量值并展开那些需要立即展开的变量和函数并根据预设条件确定执行分支
  6. 根据“终极目标”以及其他目标的依赖关系建立依赖关系链表
  7. 执行除“终极目标”以外的所有的目标的规则(规则中如果依赖文件中任一个文件的时间戳比目标文件新,则使用规则所定义的命令重建目标文件)–(这一点在《跟我一起写Makefile》里面的描述是有错误的,并不是一开始检查第一个target依赖,然后确定是否执行第一个recipe)
  8. 执行“终极目标”所在的规则
    理解make命令执行过程非常重要,有助于理解变量和函数的展开,比如理解‘=’和‘:=’的区别。

变量(Variable)

obj=a.o b.o c.o
edit:$(obj)
	gcc -o edit $(obj)
a.o:a.h b.h c.h
	gcc -c a.o
b.o:a.h b.h c.h
	gcc -c b.o
c.o:a.h b.h c.h
	gcc -c c.o
.PHONY:clean
clean:
	rm $(obj)

使用$引用变量

自动推导

利用make自动推导,上面的可以写成

obj=a.o b.o c.o
edit:$(obj)
	gcc -o edit $(obj)
a.o:b.h c.h
b.o:a.h  c.h
c.o:a.h b.h
.PHONY:clean
clean:
	rm $(obj)

make可以自动推导出a.o依赖的a.h文件,以及生成a.o需要执行的命令,这个就成为make的“隐式规则”-implicit rules

真正来了解Makefile文件

make默认执行当前目录下名为Makefile或者makefile文件,如果是其他文件名,需要使用make -f或者–file来指定,比如

make -f ./directory/a.mk

可以在makefile中包含其他文件,跟c语言中#include一样,在当前位置展开包含的文件,如:

include a.mk b.mk

还可以使用通配符

include *.mk

如果include文件件不存在,会报错误终止,如果不想让它报错误可以使用-include
make支持三种通配符,‘*’,‘?’,‘~’。
‘*’:匹配一个或者多个字符
‘?’:匹配单个字符
‘~’: 表示用户目录

注意点:
如果定义变量使用通配符,如
obj=*.o
这里不会展开!也就是说obj的值就是*.o,并不是所有.o文件!
要想让obj的值为所有.o文件,可以这么做

obj := $(wildcard *.o)

wildcard为make内置函数,返回值为复合规则的文件列表

伪目标

例子

clean:
	-rm *.o

这里的目标clean没有对应的依赖,前面我们了解到,要是这里的命令被执行,要满足两个条件中的一个,第一个就是依赖文件要比目标要新,另外一个目标文件要存在。这个clean目标由于没有依赖,所以它不满足第一个条件,而如果当前目录下有一个名为clean的文件,那么第二个条件也不会满足,这样我们如果执行make clean,则不会去执行后续的命令,为了每次都能执行rm操作,我们需要申明这个clean是一个伪目标,对于伪目标而言,不会去检查前面说的两个条件,而是每次都会执行后续的命令操作。

.PHONY:clean
clean:
	-rm *.o

总结一下,伪目标有下面两个特性
1.不会生成伪目标对应的文件
2.永远会执行recipe
利用上面两个特性我们可以实现生成多个最终target的目的

.PHONY:all
all:tar1 tar2 
tar1:a.o b.o c.o
	gcc -o tar1 a.o b.o c.o
tar2:d.o e.o f.o
	gcc -o tar2 d.o e.o f.o

上面的例子中,执行make,会找到第一个target,那就是all,(.PHONY这个target由于是.开头,不会认为是第一个target),由于all是一个伪目标,所以不会生成all文件,而是去检查tar1和tar2的依赖关系。

静态模式

直接上例子

obj = a.o b.o c.a
$(obj) : %.o : %c
	gcc -c $< -o $@

基本格式

<targets ...> : <target-pattern> : <prereq-patterns ...>
    <commands>
    ...

targets…为目标列表
target-pattern…为目标列表中匹配这个pattern的所有target集合
prereq-pattern…为target-pattern的依赖集合

前面的例子就可以理解为,从obj列表中取出所有.o文件,依赖为所有.o对应的.c文件

Makefile的recipe

recipe有两种写法

第一种,写在同一行,用分号隔开
tar:prereq;recipe
第二种,写在下一行,必须用tab开头
tar:prereq
	recipe

注释符号为#

显示命令

在命令前面加@就不会回显命令,而是直接显示命令的执行过程
比如

tar:prereq
	echo hello
这里执行,在打印的是
echo hello
hello

如果是
tar:prereq
	@echo hello
则直接打印hello

make如果使用-n参数,那么只显示执行的命令,不会真正去执行,这个可以用来调试Makefile。

命令出错

如果recipe中执行的命令出错,则会立马停止整个make的执行,可以在前面加-来忽略命令执行错误,继续执行后面的命令。

嵌套make

subsystem:
	make -C subdir

上面这个规则会执行subdir里面的的makefile。有的时候我们需要传递变量到下层makefile,可以使用export来实现

var = hello
export var
subsystem:
	make -C subdir

上面这个var变量在subdir目录的makefile中也是可见的。

定义命令包

命令包是一系列命令的集合,类似函数一样,命令包以define开头以endef结束。调用时,跟变量引用一致。
比如

define print
@echo hello
endef
all:
	$(print)

命令包还可以传参

define print
gcc -o $@ $<
endef
all:a.o b.o
	$(print)

这里面的$@就是all,$<就是a.o b.o

变量

要区分=和:=,=是在整个makefile变量的值被展开之后才去赋值,而:=则是顺序赋值。比如

a=hell
b=$(c)
c=world

上面这种,在makefile所有变量展开之后,b的值是world,也就是不管变量c在中途被改了多少次,b就是最终的c值。这样做有个好出就是,不管c的值在makefile中任意位置发生了改变,b的值就是最终的值。而

a:=hell
b:=$(c)
c:=world

:=会顺序展开,这里就会报错,因为b赋值的时候c还未定义
+=用来追加赋值
?=表示如果这个变量没有定义则定义,如果已经定义了则还是使用之前定义的值

环境变量

系统定义的环境变量在makefile中也能被引用,如果makefile定义的变量和环境变量一致,则使用makefile中定义的变量(如果使用-e参数,则系统环境变量会覆盖makefile中变量),类似于全局变量和局部变量的概念。还有一种通过make命令把变量定义传递进去,这种情况下,传递的变量会覆盖makefile中的变量。

目标变量

有的时候我们想定义一个变量,这个变量只对某个目标有用,对其他目标没有用,比如我们针对某个文件指定编译参数可以这样

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
    $(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
    $(CC) $(CFLAGS) prog.c
foo.o : foo.c
    $(CC) $(CFLAGS) foo.c
bar.o : bar.c
    $(CC) $(CFLAGS) bar.c

这里的CFLAGS变量只针对prog这个目标,以及生成它的所有依赖有效。

模式变量

%.o: CFLAGS = -O

这里的意思是变量 CFLAGS只对所有的.o文件有效

条件判断

ifeq/ifneq()
	xxx
else
	xxx
endif

使用函数

调用语法

$(function arguments)

make内置的函数并不多

subst-字符串处理函数
$(subst  <from>,<to>,<string>)
将string中的from字符串替换成to

patsubst-按模式替换字符串
$(patsubst <pattern>,<replacement>,<string>)
示例: $(patsubst %.c,%.o,a.c b.c c.c)
将a.c b.c c.c替换成a.o b.o c.o并返回

strip--替换字符串开头和结尾的空格(奇怪,有啥用)
$(strip <string>)

findstring-查找字符串
$(findstring <find>,<in>)
示例
$(findstring hello,hello world)
返回hello

filter-按模式筛选出字符串
$(filter <pattern>,<string>)
示例
sources := foo.c bar.c baz.s ugh.h
$(filter %.c %.s,$(sources) )
返回foo.c bar.c baz.s

fiter-out-反向过滤
$(filter <pattern>,<string>)
过滤掉复合pattern的字符串

shell-执行shell命令
示例:
s_files := $(shell find -name *.c)
返回当前目录下.c文件

error-输出error并终止
warning -输出警告信息可以用于调试打印

...(用到的再去查)

make参数

make -f 指定makefile文件
make target 指定目标
make -n 只打印命令,不执行,这个可以用来检查命令执行过程

隐含规则

这些规则就是约定俗称的,make会根据这些规则,自动推导出命令。make有一套隐含规则库,比如.o文件会依赖对应的.c等等

隐含变量

make中存在一些内置的隐式变量,这些变量不用定义就可以使用,当然用户也可以重新赋值,去改变它们的值。
比如CC,默认值就是gcc,CFLAGS默认指代CC的编译选项。
AR : 函数库打包程序。默认命令是 ar
AS : 汇编语言编译程序。默认命令是 as
CC : C语言编译程序。默认命令是 cc
CXX : C++语言编译程序。默认命令是 g++
CO : 从 RCS文件中扩展文件程序。默认命令是 co
CPP : C程序的预处理器(输出是标准输出设备)。默认命令是 $(CC) –E
FC : Fortran 和 Ratfor 的编译器和预处理程序。默认命令是 f77
GET : 从SCCS文件中扩展文件的程序。默认命令是 get
LEX : Lex方法分析器程序(针对于C或Ratfor)。默认命令是 lex
PC : Pascal语言编译程序。默认命令是 pc
YACC : Yacc文法分析器(针对于C程序)。默认命令是 yacc
YACCR : Yacc文法分析器(针对于Ratfor程序)。默认命令是 yacc –r
MAKEINFO : 转换Texinfo源文件(.texi)到Info文件程序。默认命令是 makeinfo
TEX : 从TeX源文件创建TeX DVI文件的程序。默认命令是 tex
TEXI2DVI : 从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是 texi2dvi
WEAVE : 转换Web到TeX的程序。默认命令是 weave
CWEAVE : 转换C Web 到 TeX的程序。默认命令是 cweave
TANGLE : 转换Web到Pascal语言的程序。默认命令是 tangle
CTANGLE : 转换C Web 到 C。默认命令是 ctangle
RM : 删除文件命令。默认命令是 rm –f

ARFLAGS : 函数库打包程序AR命令的参数。默认值是 rv
ASFLAGS : 汇编语言编译器参数。(当明显地调用 .s 或 .S 文件时)
CFLAGS : C语言编译器参数。
CXXFLAGS : C++语言编译器参数。
COFLAGS : RCS命令参数。
CPPFLAGS : C预处理器参数。( C 和 Fortran 编译器也会用到)。
FFLAGS : Fortran语言编译器参数。
GFLAGS : SCCS “get”程序参数。
LDFLAGS : 链接器参数。(如: ld )
LFLAGS : Lex文法分析器参数。
PFLAGS : Pascal语言编译器参数。
RFLAGS : Ratfor 程序的Fortran 编译器参数。
YFLAGS : Yacc文法分析器参数

自动化变量

所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。
$@ : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合。

$% : 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 foo.a(bar.o) ,那么, $% 就是 bar.o , $@ 就是 foo.a 。如果目标不是函数库文件(Unix下是 .a ,Windows下是 .lib ),那么,其值为空。

$< : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。

$? : 所有比目标新的依赖目标的集合。以空格分隔。

$^ : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份。

$+ : 这个变量很像 $^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。

$* : 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b ,并且目标的模式是 a.%.b ,那么, $* 的值就是 dir/foo 。这个变量对于构造有关联的文件名是比较有效。如果目标中没有模式的定义,那么 $* 也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么 $* 就是除了后缀的那一部分。例如:如果目标是 foo.c ,因为 .c 是make所能识别的后缀名,所以, $* 的值就是 foo 。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 $* ,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么 $* 就是空值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值