Android开发经验谈Android知识Go

makefile 入门基础知识

2018-03-23  本文已影响127人  浩林Leon

一.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命令组成命令.

1.考虑到用table键开头,空格可能不习惯.可以采用

.RECIPEPREFIX = >
all:
>touch all

这里的 > 指的是自己想代替table键的字符,理论上可以采用单个任意的非定义的特殊字符.例如"<",")"等等.

2.执行多条命令:

.RECIPEPREFIX =>
.PHONY:all
all:
>touch all
>echo "ddasfaafdsa" > all

先创建 all 文件,再往文件里面写入一些字符串"ddasfaafdsa".
需要注意的是,每一行的命令都是在单独的shell中执行,这些命令之间没有继承关系.
3.获取前条指令的值的方法,有三种:

var-kept:
    export foo="foood";echo "foo=[$$foo]";export foo2=$$foo"dd";echo "foo2=[$$foo2]"

输出结果:


;输出结果.png
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]"

输出结果:


.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.结果如下:

while/do/done.png
2.9 函数

makefile 使用函数的格式
${function arg1 arg2} 或者 $(function arg1 arg2)
常见的内置函数
包括

$(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

2018-03-25 17-16-21 的屏幕截图.png
上一篇下一篇

猜你喜欢

热点阅读