当前位置:网站首页>Makefile容易犯错的语法
Makefile容易犯错的语法
2022-08-02 14:08:00 【nginux】
1.引言
最近学习android的Build系统,接触最多的自然就是Makefile语法,发现很多容易出错的地方,不避开这些错误语法没法真正了解Makefile的内涵,下面就介绍遇到的一些让人困惑的语法错误
2.列举容易犯错的地方
- ifeq条件判断
ifeq($(fro),no)
endif
多么简单的语法,但是执行会报错如下:
Makefile:2: *** missing separator. Stop.
原因:
ifeq和左括号’(‘之间是必须有空格的。
- shell脚本的使用
我们知道Makefile中是可以使用shell脚本的,但是具体要在哪里使用呢?答案是当且仅当在Command里面,什么事command?我们知道Makefile的主要规则如下:
target:pre
command
上面所说的就是命令行command。
下面举例说明一错误情况,加深对于本条内容的理解:
all:
for dir in $(MODULES);do\
(cd $${dir};$(MAKE) all); \
done
$(shell echo "xxx")
执行结果:
xxx
make: xxx: Command not found
make: *** [all] Error 127
原因:从错误提示来看,编译器将xxx看做了shell脚本,为什么会如此?要理解这个就需要了解Make内嵌函数的工作原理,其实说来也是很简单的,引用Makefile手册里面的话就是:GUN make的函数提供了处理文件名、变量、文本和命令的方法,可以再需要的地方调用函数来处理指定的文本,函数在调用它的地方被替换为它的处理结果,函数调用(引用)和变量引用的展开方式相同。
怎么样,明白了吧,函数会直接被原地展开的呀。举例来说,$(shell echo “xxx”),shell函数的调用会被展开成:xxx,也就是,上面的Makefile代码其实被展开成这样:
all:
for dir in $(MODULES);do\
(cd $${dir};$(MAKE) all); \
done
xxx
这样编译器自然会提示找不到xxxshell命令喽!!
为了测试是否真的明白上面的描述,出个题目:
fro := no
ifeq ($(fro),no)
$(shell echo "xxx")
endif
MODULES = ant bee
all:
for dir in $(MODULES);do\
(cd $${dir};$(MAKE) all); \
done
$(shell echo "xxx")
这样子编译会通过么?那改成下面这样呢?
fro := no
ifeq ($(fro),no)
$(shell echo "xxx" >> test.mk)
endif
MODULES = ant bee
all:
for dir in $(MODULES);do\
(cd $${dir};$(MAKE) all); \
done
$(shell echo "xxx")
如果你理解了前面叙述的规则,自然会知道第一种情况是错误的,第二种情况是正确的。原因不再解释。
3.shell变量和Makefile变量
细心的读者在看上面代码的时候不知道是不是有疑问,为什么cd $$dir会有两个$符号呢?如果仅仅使用一个$符号会怎么样呢?下面来解答。
这条在网上有很多的介绍了,稍微说明一下,我们知道Makefile可以定义自己的变量,我们姑且成为Makefile变量,而且Makefile中可以使用shell脚本,如果shell脚本中又存在shell变量,编译器如果区分上面两种变量呢?看到这里你应该想到了,Makefile变量使用方式:(xxxx),而shell变量的使用方式是:$(xxxx)。
如果我们将上面的cd $$(dir)改为cd $(dir),执行结果如下:
for dir in ant bee;do\
(cd ;make all); \
done
编译器展开变量的时候(dir)当做是Makefile变量,而Makefile中又没有定义这个变量,那么就是cd到空目录喽!!如果是cd$(dir),编译器展开变量的时候就当做是shell变量,结果就是成功的。
4.Makefile执行流程(也是很重要的呀)
了解make如何解析makefile文件是非常重要的,GUN make的执行过程分为两个阶段:
- 读取所有的makefile文件,内建所有变量/函数,并建立目标和依赖之间的依赖关系
- 根据第一个阶段建立的依赖关系,决定重构哪些目标,并执行命令进行重建目标
了解make执行过程的两个阶段是非常重要的,它帮助我们更深入的了解执行过程中变量以及函数是如何被展开的。变量和函数的展开问题是书写Makefile时容易犯错和引起大家迷惑的地方,本节将对这些不同的结构的展开进行简单的总结(明确变量和函数的展开阶段,对正确使用变量非常有帮助)。
首先明确一个概念:在make执行的第一个阶段如果变量和函数被展开,那么称此展开是立即的,此时所有的变量和函数被展开在需要构建的结构链表的对应规则中,其它展开称为延后的,这些变量和函数延迟到某些规则需要使用时或make第二阶段展开。
- 条件语句的展开
-所有使用到条件语句在产生分支的地方,make会根据预设条件将正确的分支展开,就是说条件分支的展开是立即的,其中包括ifdef、ifeq、ifndef、ifneq所确定的分支命令。 - 规则的展开
IMMEDIATE : IMMEDIATE ; DEFERRED
DEFERRED
其中规则中的目标和依赖如果引用其他变量,则被立即展开。而命令中的引用会延迟展开。
有了前面的基础,下面引用make手册中的执行流程:
下面举个例子,从侧面验证上面的论述:
fro := no
ifeq ($(fro),no)
$(info 'xxx')
endif
MODULES = ant bee
droid:
all:
for dir in $(MODULES);do\
(cd $${dir};$(MAKE) all); \
done
droidcore:
echo "come into droidcore"
droid:droidcore
$(info 'yyy')
$(info ‘yyy’)函数是被立即展开的,所以会先输出这两句,才开始构建目标。
输出如下:
'xxx'
'yyy'
echo "come into droidcore"
come into droidcore
5.目标的重复定义
从上面的代码我们发现droid被定义了两次,这是允许的
6.使用define定义函数的使用方式
CALLED_FROM_SETUP:=false
define info-test
$(if $(filter true,$(CALLED_FROM_SETUP)),,$(info [COMMON-INFO]:$(1)))
endef
default:
$(call info-test,debai)
这里使用了GUN Makef的call函数,第七条我们来讲一下call函数
7.call函数
LOCAL_MODULES:=\
AppInstaller
LOCAL_MODULES2:=\
Browser
add = $(1)+$(2)
$(info $(call add,a,b))
define test
$(foreach m,$(1),$(shell echo $(m)))
endef
result=$(call test, \
$(LOCAL_MODULES) \
$(LOCAL_MODULES2) \
)
$(info $(shell echo $(result)))
default:
这里主要看函数test,那么这个函数的输出结果result等于多少呢?是AppInstaller?还是AppInstaller Browser呢?答案是后者,为什么?因为call调用的函数参数是以“,”分隔的,这里没有分隔符,所以LOCAL_MODULES和LOCAL_MODULES2
都看做是$(1),即都看做第一个参数。
8.foreach函数
$(foreach VAR,LIST,TEXT)
函数功能:这个函数的工作过程是这样的:如果需要(存在变量或者函数的引用),首先展开变量“VAR”和“LIST”的引用;而表达式“TEXT”中的变量引用不展开。执行时把“LIST”中使用空格分割的单词依次取出赋值给变量“VAR”,然后执行“TEXT”表达式。重复直到“LIST”的最后一个单词(为空时结束)。“TEXT”中的变量或者函数引用在执行时才被展开,因此如果在“TEXT”中存在对“VAR”的引用,那么“VAR”的值在每一次展开式将会到的不同的值。
注意到没有:LIST是以空格为分隔符的吆,为了深入理解这一点,我们来做如下实验:
names=a,b,c
files:=$(foreach n,$(names),$(n).o)
$(info $(files))
这里输出是a,b,c.o,看啊,foreach会把a,b,c看成一个整体的,如果names=a b c那么输出结果才是a.o b.o c.o
9.patsubst(pattern,replace,text)
这个函数其实是很容易用错的,话不多说,上例子:
$(patsubst res/%,%,drawable/icon.png res/copy.png)
返回值是:drawable/icon.png copy.png,原来如此,对于满足pattern的做替换处理,不满足的保持原样返回(并没有丢弃)
10.目标指定变量(makefile手册6.10)
举例:
LOCAL_CMD := @echo xxx
LOCAL_MODULE := debai.apk
$(LOCAL_MODULE):PRIVATE_CMD:=$(LOCAL_CMD)
$(LOCAL_MODULE):
@echo "Install: [email protected]"
$(PRIVATE_CMD)
输出结果:
Install: debai.apk
xxx
- filter-out函数
$(filter-out PATTERN...,TEXT)
比较容易忽略的是:PATTERN可以包含多个模式,并且每个模式之间使用空格来分隔的,举例:
modules_to_install := \
$(filter-out $(foreach p,$(overridden_packages),$(p) %/$(p).apk), \
$(modules_to_install))
代码片段来自Android的build系统,作用是将modules_to_install中overridden_packages去掉,即被覆盖的APP不需要安装的,这里就使用了两个模式,使用空格分开,第一个模式是$(p)
,第二个是%/$(p)
,在makefile中%代表通配符。
- eval函数
这个在Makefile里面好像是一个很难理解的函数,它的用法如下:
eval(text)
作用其实将text在Makefile中展开,在展开的过程中会进行解引用,比如$(x),就会展开成x的值。如果是$$(x),那么会展开成\$(x)。我们一个实例:
1 yunos-services := xxx
2
3 define add-jars-to-services
4 LOCAL_JAVA_LIBRARIES += $$(call jars-for-services)
5 endef
6
7 define jars-for-services
8 $(strip $(if $(filter false,false),
9 yunos-services \
10 ,)\
11 )
12 endef
13
14 define yunos-codebase-dirs-for-service
15 $(strip \
16 /yunos/framework-source_code/core/yunos-services/core/java \
17 )
18 endef
19
20 define yunos-codebase-test
21 $(strip \
22 $(if $(filter false,false),\
23 yunos-framework-base,) \
24 $(if $(filter false,false),\
25 yunos-framework-base-widget,)\
26 )
27 endef
28
29 $(eval $(call add-jars-to-services))
30 all:
31 $(info $(shell echo $(LOCAL_JAVA_LIBRARIES)))
32 #$(info $(shell echo $(call yunos-codebase-test)))
这个输出结果是怎么样的呢?会输出yunos-services,分析如下:
第29行执行的时候,首先执行内层的$(call add-jars-to-services),add-jars-to-services本质上是一个宏定义,所以会直接替换为:
LOCAL_JAVA_LIBRARIES += $$(call jars-for-services)
所以eval语句等价于下面:
eval(LOCAL_JAVA_LIBRARIES += $$(call jars-for-services)
eval做一次展开以后等价于:
LOCAL_JAVA_LIBRARIES += $(call jars-for-services)
上面语句执行以后等价于:
LOCAL_JAVA_LIBRARIES += yunos-services
第四行LOCAL_JAVA_LIBRARIES += $$(call jars-for-services)也可以写成下面:
LOCAL_JAVA_LIBRARIES += $(call jars-for-services)
这样输出结果也是一样的,为什么呢?我们来分析后面这种情况的展开,eval语句等价于这样:
eval(LOCAL_JAVA_LIBRARIES += $(call jars-for-services)
eval第一次展开的时候的结果:
LOCAL_JAVA_LIBRARIES += yunos-services
所以这种情况就在一次展开的时候获取到结果,而前面两个$的情况下,会在第二次展开的情况下获取到值。
边栏推荐
- 深度学习之 卷积网络(textCNN)
- ConstraintLayout从入门到放弃
- 再见篇:App专项技术优化
- [论文阅读] ACT: An Attentive Convolutional Transformer for Efficient Text Classification
- LLVM系列第二十八章:写一个JIT Hello World
- PyTorch(14)---使用现有的模型及其修改
- LLVM系列第六章:函数返回值Return
- Spark_Core
- App signature in flutter
- Win10 can't start WampServer icon is orange solution
猜你喜欢
随机推荐
Tensorflow常用函数
spark资源调度和任务调度
LLVM系列第二十一章:写一个简单的Loop Pass
IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but
LLVM系列第二十五章:简单统计一下LLVM源码行数
Visual studio代码中有红色波浪线解决办法
机器学习和深度学习中的梯度下降及其类型
PyTorch(14)---使用现有的模型及其修改
LLVM系列第三章:函数Function
拥抱Jetpack之印象篇
Scala连接Mysql数据库
spark中RDD与DF的关系
VS2017中安装visual assist X插件
6.如何使用CardView制作卡片布局效果
Handler你真的搞懂了吗?
Ffmpeg交叉编译
详解RecyclerView系列文章目录
内存申请(malloc)和释放(free)之上篇
Kubernetes资源编排系列之三: Kustomize篇
MapReduce流程