Makefileeasy犯错的语法

1.引言

近期学习android的Build系统,接触最多的自然就是Makefile语法。发现非常多easy出错的地方,不避开这些错误语法没法真正了解Makefile的内涵。以下就介绍遇到的一些让人困惑的语法错误

2.列举easy犯错的地方

  • 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时easy犯错和引起大家迷惑的地方,本节将对这些不同的结构的展开进行简单的总结(明白变量和函数的展开阶段。对正确使用变量非常有帮助)。

首先明白一个概念:在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)

这个函数事实上是非常easy用错的,话不多说。上样例:

$(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
  1. filter-out函数
$(filter-out PATTERN...,TEXT)

比較easy忽略的是: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中%代表通配符。

  1. 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

所以这样的情况就在一次展开的时候获取到结果,而前面两个$的情况下,会在第二次展开的情况下获取到值。

时间: 2024-12-13 10:45:47

Makefileeasy犯错的语法的相关文章

软件构造-犯错的艺术——健壮性与正确性,异常,防御式编程,debugging与test的思考与总结

健壮性与正确性 健壮性与正确性是不同的——一个倾向于使程序尽可能保持运行,即使遇到错误,一个倾向于使程序尽可能正确,不在意保持运行 异常 异常分为两种——checked exception与unchecked exception 二者的区别在于: checked exception需要显式的处理,说白了就是编程者必须要么用catch抓住它,然后在try中想办法处理掉,要么显式的将这个异常扔到调用的上一级方法,也就是甩锅.总而言之,你永远不能无视checked exception unchecke

哪些要素在做产品需求的时候容易犯错?

产品经理是一个思考的工种,而多想多思考成为产品经理成长最为关键点.而经验会帮助产品经理在产品需求认知和产品设计上不会犯错误,而快速进入角色.很多新手却办不到这一点. 一.判别需求是否真实存在?需求真伪性 当产品经理接到一个业务部门的需求,一个老板的需求.很多人惯性的思维是如何完成这个需求.而展开对需求思考.但是在此之前,我们需要去判别这个需求的真伪性.如果是伪需求,那么做出来是对我们的产品没有任何的意义.需求是否真实存在,真实性如何?是否有场景可以承接这个需求本身. 二.表面需求后,没有去了解更

javascript sort方法容易犯错的地方

sort方法用来对数组排序非常方便.但是sort(func)这个func参数的构造却很容易混淆. 这个func的作用是,把排序结果里任意相邻两项a,b放入到func里来执行,如果返回值都为-1,则为正序排列,如返回值都为1,则为逆序排列. 例如,[1,3,65,97,45,6,2] 如果要正序,就应该写成[1,3,65,97,45,6,2].sort(function(a, b){return a - b;}), 如果要逆序:[1,3,65,97,45,6,2].sort(function(a,

致DBA:为什么你经常犯错,是因为你做的功课不够

专职做DBA已经6年多的事件了,看同行.同事犯了太多的错误,自己也犯了非常多的错误.一路走来,感触非常深.然而绝大多数的错误其实都是很低级的错误.有的是因为不了解某个引擎的特性导致:有的是因为对线上环境不了解导致:有的是因为经验不足导致:一路上,跌跌撞撞,从小公司DBA,到腾讯高级DBA,再到现在的金融数据库DBA. 不由得想起5年前的我,刚进入DBA行业,缺乏经验,经常犯错误,不是我不够努力,更多的是初来咋到的我根本不知道应该在哪方面下功夫.本文就是基于这方面的考虑,根据自己在DBA这个职业上

作用域--高手都容易犯错的地方

在js中,我们都知道,this的指向是由它被调用时的上下文所决定的.例如: window.id = 888; var data = { id : 999, getId : function(){ console.log(this.id) } } data.getId(); // 999; var getId = data.getId; getId(); // 888 当我们在data上调用getId方法时,this显然是指向data对象的.这个很好理解,我们需要注意的是,当我们把data上的ge

应试教育的死穴,恰在于堵死了孩子“犯错”的空间

今天周日,闲着没事,来公司梳理一下最近的项目心得,又翻起了前段时间看到的文章<应试教育的死穴,恰在于堵死了孩子"犯错"的空间>,来浅谈一下自己的感受吧! 还是从自身谈起,小时候手笨,脑子不是很灵活,于是会出现各种的问题,这时候老师就开始发火.生气,轻则骂几句,重则棍棒伺候.于是在这种环境下,总是害怕犯错事,害怕犯错误. 还有一种情景,就是所谓的考试了,尤其是语文考试,相比大家都经历过,对于一篇阅读理解的分析,每个人都有自己的见解,写写自己的心理体会不就得了,可总是会有一种所

JAVA 犯错汇总

ResultSet-->next() //伪代码 ResultSet rs = null; rs1 = stmt.executeQuery(); //if(!rs.next()){ //这里就是坑我代码 // return false; //} while(rs.next()) { //rs.next()这个方法坑了我,让我总是得不到第一条数据 执行一次,往下走一回,我靠,不带这么坑的 } JAVA 犯错汇总

[C/C++]_[初级]_[编程容易犯错的地方]

场景: 1. 这里总结一些日常的容易犯错的细节. 问题1:一个类A有成员变量int deleted,给定一个A的对象指针 *a, 判断deleted为真的时候输出一个语句. 一般情况下新手会这样写: if(a) { if(a->deleted) { cout << "deleted" << endl; } } 但这样其实不够精简和浪费行数, 应该这样. if(a && a->deleted) { cout << "

勇于犯错才能更好的成长

朋友,假如你希望你一生都不犯错误,假设你真如愿以偿,那我会告诉你:你生最大的错误就是你没有任何过错. 意大利的朗根尼西说过:“不要给我忠告,让我自己去犯错误.”这似乎只是一句妙语,事实上含有很深刻的意义.一个人怕犯错,就是畏惧现实,一个人想逃避犯错,就是逃避现实,他永远不会在生活中独立,自强.不要简单地否认它,想一想,试一试,你就会理解朗根尼西的话. 过错在一定意义上并不是坏事.任何事物都具有其二重性,有好的一面也有坏的一面.过错也不例外.它虽然会给你带来一些损失,但要知道,一个人如果没有相对的