makefile 入门基础知识
一.makefile格式
1.makefile 文件一般命名 为makefile 即可,放在目录下面,直接调出 terminal ,输入make 即会执行makefile文件的第一个命令,如果需要执行制定的命令xxx可以 make xxx.
2.makefile 文件的基本语法:
1.目标
<target>:<prerequisites>
<table><commands>
解释:target 部分叫目标,也就是make 执行的xxx;prerequisties 叫着前置条件;第二行必须用table键起,紧接着是具体的执行命令。target 是必须的;prequisites,和command是可选的,但是两者必须有一个。每条指令的意思
例如:
new:
touch a.txt
2.伪目标
如果在当前目录下已经存在 文件,并且文件名(new)和目标(new)是一致的时候,使用make new命令时,并不会执行指令.例如下面的输出,显示new 已经是最新的,并不执行new的指令.
输出结果.png
为了避免对已经存在和目标相同的文件,而不执行目标的情况,有一种叫"伪目标"的语法出现了.写法如下
.PHONY: new
new:
touch new
这样每次执行make new ,都会执行.如下:
伪目标结果.png
3.前置条件
前置条件通常是一组文件名,以空格分开。他指定目标是否需要重新构建的标准.
1.前置条件如果是空,则每次都会触发;
2.有一个或者多个前置条件的,只需要有一个前置条件不存在,或者有更新过(通常根据lastmodification的时间戳比目标的时间戳新),目标就会重新构建。
例如:
result.txt:a.txt
cp a.txt result.txt
a.txt:
echo "这是A" > a.txt
开始时候,没有创建任何文件,输出一下内容:
/media/leonzhu/document/工作文档/makefile$ make
echo "这是A" > a.txt
cp a.txt result.txt
修改下makefile的 内容,连续再次执行make 指令,由于a.txt 没有任何改动,所以这次目标不会执行
result.txt:a.txt
cp a.txt result.txt
a.txt:
echo "这是B" > a.txt
输出结果:
/media/leonzhu/document/工作文档/makefile$ make
make: 'result.txt' is up to date.
查看result.txt 文件,可知里面的内容并没有改变成B.
image.png
4.命令
命令默认是用table键开头,紧接着由一行或多行的Shell命令组成命令.
- 如果不想用table开头怎么办?
- 如果需要执行多个命令怎么办?
- 如果需要使用上条命令的值怎么办?
1.考虑到用table键开头,空格可能不习惯.可以采用
.RECIPEPREFIX = >
all:
>touch all
这里的 > 指的是自己想代替table键的字符,理论上可以采用单个任意的非定义的特殊字符.例如"<",")"等等.
2.执行多条命令:
.RECIPEPREFIX =>
.PHONY:all
all:
>touch all
>echo "ddasfaafdsa" > all
先创建 all 文件,再往文件里面写入一些字符串"ddasfaafdsa".
需要注意的是,每一行的命令都是在单独的shell中执行,这些命令之间没有继承关系.
3.获取前条指令的值的方法,有三种:
- 1 把多条命令写在一行中,用分号(;)隔开
var-kept:
export foo="foood";echo "foo=[$$foo]";export foo2=$$foo"dd";echo "foo2=[$$foo2]"
输出结果:
;输出结果.png
- 2 用连接符[;\],下条指令按正常格式换行.
var-kept:
export foo="foood";\
echo "foo=[$$foo]";\
export foo2=$$foo"dd";\
echo "foo2=[$$foo2]"
输出结果:
;\连接符结果
- 3 使用 .ONESHELL:
.ONESHELL:
var-kept:
export foo="foood"
echo "foo=[$$foo]"
export foo2=$$foo"dd"
echo "foo2=[$$foo2]"
输出结果:
.ONESHELL:结果
二.makefile 文件语法
2.1 注释
注释用# 表示.
2.2 回声
正常情况下,makefile会在控制台打印每一条命令,这叫回声.在第一行命令前增加 @ 符号,就可以关闭回声,这样就不会打印命令.
回声:
.ONESHELL:
var-kept:
# 这是注释
export foo="foood"
echo "foo=[$$foo]"
export foo2=$$foo"dd"
echo "foo2=[$$foo2]"
输出结果:
回声输出结果
没错,最后的输出结果把所有的命令都输出了,甚至是注释文件都出来了.
关闭回声的做法
.ONESHELL:
var-kept:
@# 这是注释
export foo="foood"
echo "foo=[$$foo]"
export foo2=$$foo"dd"
echo "foo2=[$$foo2]"
关闭回声结果
2.3 通配符
通配符(wildcard)用来指定一组符合条件的文件名。Makefile 的通配符与 Bash 一致,主要有星号(*)、问号(?)和 [...] 。比如, *.o 表示所有后缀名为o的文件。
clean:
rm -f *.o
2.4 模式匹配
makefile可以用% 来匹配makefile 文件中的目标.%表示任何非空字符串. % 和通配符中的* 的区别在于他们使用的作用域不一样:* 指的运用在系统中,而%运用在makefile文件中.
例如:对于模式规则“%.o : %.c”,它表示的含义是:所有的.o文件依赖于对应的.c文件。我们可以使用模式规则来定义隐含规则。
2.5 变量和赋值
定义变量通常只需要写变量名=xxx,在后面的调用中用$(变量) 即可.
#变量
testvar=你好吗?
printvar:
@echo $(testvar)
如果使用shell变量,需要$$,因为makefile 在执行shell变量的时候会对$转义如下
VAR=3
target:
echo $(VAR) #(1)
VAR=4 #(2)
echo $(VAR) # (3)
echo $$VAR # (4)
有时,一个变量可以指向另一个变量:
var1=$(var2)
这里会带来一个问题var1的值是在定义的时候扩展(静态扩展)还是在运行时扩展(动态扩展).不同的时候扩展结果会不一样.
为了解决类似问题,Makefile一共提供了四个赋值运算符 (=、:=、?=、+=),它们的区别请看
VARIABLE = value
# 在执行时扩展,允许递归扩展。
VARIABLE := value
# 在定义时扩展。
VARIABLE ?= value
# 只有在该变量为空时才设置值。
VARIABLE += value
# 将值追加到变量的尾端。
1> 递归展开变量(=):用=或define关键字都可以定义这种变量,如果变量的定义引用了其它的变量,那么引用会一直展开下去,直到找到被引用的变量的最新的定义,并以此作为改变量的值返回。
var = I love
variable = linux
var += $(variable)
variable = magic
variable = last
allvar:
echo $(var)
这里首先赋值variable=linux,然后var 引用了variable,最后variable=last,由于引用,最后输出l love last
=引用结果
2>简单扩展变量(:=) 用这种方式定义的变量,会在定义处按照变量扩展开,值不会在后面随着变量的赋值而变化.
m = mm
x = $(m)
y := $(x) bar
m=xx
x = later
simpleEqual:;echo $(x) $(y)
由于y变量采用简单扩展,所以y的值为mm bar,如果后面对y引用的变量值进行改变都不会对y值有影响.例如改变m的值(xx),改变x的值(later) 对后面的$(y)一直是 mm bar.
输出结果为
简单扩展变量结果
3>可以通过+=为已定义的变量添加新的值
当变量从前没有被定义过, +=和=是一样的,它定义一个递归展开的变量,但是,当变量已经有定义的时候,+=只是简单
的进行字符的添加工作。
如果起初你用:=定义变量,那么+=只是利用变量的当前值进行添加
如果起初用=定义变量,+=的行为就变得有些古怪,它并不会在使用+=的地方马上进行变量展开,而是会把展开工作推后,
直到它找到最后变量的定义,这和=定义变量的行为是类似的
:=情况
m = mm
x := $(m)
y = $(x) bar
m=mm2
y+=y later
simpleEqual:;echo $(x) $(y)
:=图
=的情况
m = mm
x = $(m)
y = $(x) bar
m=mm2
y+=y later
simpleEqual:;echo $(x) $(y)
=的情况
4> ?=
赋默认值,如果没有初始化该变量,就给它赋上默认值。如果已经赋值了便不会起作用.
# ?=测试
ARCH=arm
ARCH ?= i386
questionEqual:;echo $(ARCH)
2018-03-24 21-40-13 的屏幕截图.png
2.6 内置变量(Implicit Variables)
在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值,或是在make的命令行中传入这些值,或是在你的环境变量中设置这些值,无论怎么样,只要设置了这些特定的变量,那么其就会对隐含规则起作用。当然,你也可以利用make的“-R”或“--no– builtin-variables”参数来取消你所定义的变量对隐含规则的作用。例如,第一条隐含规则——编译C程序的隐含规则的命令是“$(CC) –c $(CFLAGS) $(CPPFLAGS)”。Make默认的编译命令是“cc”,如果你把变量“$(CC)”重定义成“gcc”,把变量“$(CFLAGS)”重定义成 “-g”,那么,隐含规则中的命令全部会以“gcc –c -g $(CPPFLAGS)”的样子来执行了。
内置变量分两种:一种是命令相关的,例如"CC" ;
一种是参数相关的如"CPPFLAGS"
1、关于命令的变量。
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”。
2、关于命令参数的变量
下面的这些变量都是相关上面的命令的参数。如果没有指明其默认值,那么其默认值都是空。
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文法分析器参数。
2.7自动变量(Automatic Variables)
(1)$@
指代当前目标,就是make中的当前执行的目标.例如 make Foo,$@ 表示 Foo
# $@ 测试
touchA.txt touchB.txt:
touch $@
2018-03-25 13-31-30 的屏幕截图.png
(2)$<
指代第一个前置条件. 比如规则 p: a1 a2 那么$< 表示a1.
(3) $?
指代比目标更新的(时间戳比目标时间戳大)所有前置条件.
(4) $^
指代所有的前置条件,之间用空格隔开
(5)$*
指代 匹配符中$的部分.
(6)$(@D) ,$(@F)
$(@D) 指代 $@变量的目录部分,也就是目标的目录部分.
$(@F) 指代$@的文件部分,也就是目标的文件部分
"$(D)""$(F)"和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,"$(D)"返回"dir",而"$(F)"返回"foo"
"$(%D)""$(%F)"分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。
"$(<D)" "$(<F)"分别表示依赖文件的目录部分和文件部分。
"$(^D)" "$(^F)"分别表示所有依赖文件的目录部分和文件部分。(无相同的)
"$(+D)" "$(+F)"分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)
"$(?D)""$(?F)"分别表示被更新的依赖文件的目录部分和文件部分。
最后想提醒一下的是,对于"$<",为了避免产生不必要的麻烦,我们最好给$后面的那个特定字符都加上圆括号,比如,"$(< )"就要比"$<"要好一些。
dest/%: src/%
@[ -d dest ] || mkdir dest
cp $< $(@D)
这段代码的意思是把 src中的目标文件拷贝到 dest目录下,其中目标文件 为 make dest/test.txt 中的test.txt ,因为% 作为makefile文件中的目标匹配,表示 src中的 文件名;
第三行中的 $< 表示前置的第一个元素也就是 src/%==>src/test.txt;
$(@D) 表示目标的 目录 即dest/% 的目录==>dest
2.8 判断和循环
makefile中的判断 一般采用ifxxx ... else ... endif 的形式;
条件判断 有 ifeq ... else ...endif, ifneq ... else ... endif ,ifdef ...else ... endif,ifndef ... else ... endif
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
循环,
for/do/done
LIST = one two three
allLoop:
@for i in $(LIST); do echo $$i; done
while/do/done
# while/do/done
n ?= 0
loopwhile:
@n=$(n); \
while [ $${n} -lt 10 ] ; do \
echo $$n ; \
n=`expr $$n + 1 `;\
done; \
其中@n=$(n) 表示在定义一个命令变量n= 之前的n,所以后面用到的命令n都是这时定义的n,n=expr $$n + 1
表示一个表达式赋值给n,值为n的值+1.结果如下:
2.9 函数
makefile 使用函数的格式
${function arg1 arg2} 或者 $(function arg1 arg2)
常见的内置函数
包括
- 字符串处理函数
1.字符串替换 $(subst from,to,text)
$(subst ee,EE,feet on the street)
结果 ‘fEEt on the strEEt’.
2.模式匹配替换 $(patsubst pattern,replacement,text)
$(patsubst %.c,%.o,x.c.c bar.c)
有写简写
比如:$(VAR:PATTERN=REPLACEMENT)
objects = foo.o
testPatsub2:
test=$(objects:.o=.c)
echo $${test}
这里面,看 demo var 变量可以传多个 字符,但是实际测试无法使用,不清楚原因
https://www.gnu.org/software/make/manual/html_node/Text-Functions.html#Text-Functions