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: $@"
        $(PRIVATE_CMD)
输出结果:
Install: debai.apk
xxx
- 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中%代表通配符。
- 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
所以这样的情况就在一次展开的时候获取到结果,而前面两个$的情况下,会在第二次展开的情况下获取到值。
Makefileeasy犯错的语法的更多相关文章
- 致DBA:为什么你经常犯错,是因为你做的功课不够
		
专职做DBA已经6年多的事件了,看同行.同事犯了太多的错误,自己也犯了非常多的错误.一路走来,感触非常深.然而绝大多数的错误其实都是很低级的错误.有的是因为不了解某个引擎的特性导致:有的是因为对线上环 ...
 - 朱晔和你聊Spring系列S1E6:容易犯错的Spring AOP
		
阅读PDF版本 标题有点标题党了,这里说的容易犯错不是Spring AOP的错,是指使用的时候容易犯错.本文会以一些例子来展开讨论AOP的使用以及使用过程中容易出错的点. 几句话说清楚AOP 有关必要 ...
 - Linux用户态定时器用法以及犯错总结【转】
		
转自:http://blog.csdn.net/csdn_logo/article/details/48525703 版权声明:本文为博主原创文章,欢迎转载,转载请注明出处,多谢合作. 采样的时候要用 ...
 - m_Orchestrate learning system---二十、如何写代码不容易犯错
		
m_Orchestrate learning system---二十.如何写代码不容易犯错 一.总结 一句话总结:能排序多排序 这次查错的启示: 1.代码数据更规整:要是取出的数据排序的话可以减少很多 ...
 - java中最容易犯错的特殊字符
		
问题背景 能准确说出下面的java 执行完毕后会打印出什么? System.out.println( String.class.getName()+ ".class"); Syst ...
 - java面试题最容易犯错
		
1. static 和 final 的用法 static 的作用从三个方面来谈,分别是静态变量.静态方法.静态类. 静态变量:声明为 static 的静态变量实质上就是全局变量,当声明一个对象时,并不 ...
 - vue  router引入路由与路由配置容易犯错的地方与常见的报错与处理报错
		
首先npm安装vue-router插件,就不说了其次: 先看下我本地的目录结构吧 第一步:在src目录下新建一个专门存放router的index.js文件里面的内容为: import Vue from ...
 - webstorm中.vue报错(es6语法报错)-转
		
1.webstorm中es6语法报错,解决方法: 打开 Settings => Languages & Frameworks => Javascript把 Javascript L ...
 - react 犯错
		
1. import a from './xx' 一定要有 ./ 2. export default const x={} 错 改为 const x={}; export default x; 也 ...
 
随机推荐
- A - Diverse Team
			
Problem description There are n students in a school class, the rating of the i-th student on Codeho ...
 - 消除svn选定(checkout)桌面上文件显示一大堆问号。
			
图片: 解决方法一: 桌面右键选择TortoiseSVN——>点击Settings,如下图,选中Icon Overlays(图标覆盖),去勾选Fixed drives(本地磁盘),点击确定,按F ...
 - C#之考勤系统
			
闲来无聊,搞搞C#,下面就是我写的一个Demo 员工类 using System; using System.Collections.Generic; using System.Linq; using ...
 - 第二章	API的理解和使用
			
2.1.1全局命令 Key * 查看所有键,(慎用,会把所有键都遍历一次并列出) Dbsize 查看键总数,不会遍历所有键,只是从内置函数中读取一个数 Exists [key] 检查键是否存在 Del ...
 - 【sqli-labs】 less28 GET- Error based -All you Union&Select Belong to us -String -Single quote with parenthesis(GET型基于错误的去除了Union和Select的单引号带括号字符串型注入)
			
这个不是基于错误的吧,看源码可以知道错误并没有输出 那就使用;%00和order by试一下 http://192.168.136.128/sqli-labs-master/Less-28/?id=1 ...
 - GDI 映射模式(11)
			
概述 调用 SetMapMode 函数可以设置映射模式: int SetMapMode( HDC hdc, // 设备环境句柄 int fnMapMode // 要设置的映射模式 ); 同样,调用 G ...
 - GDI 画笔(9)
			
使用现有画笔 Windows 提供三种备用画笔(Stock Pen):BLACK_PEN(黑色画笔).WHITE_PEN(白色画笔).NULL_PEN(不绘制任何图形的画笔). 调用 GetStock ...
 - 关于node对文件的读取
			
设计: 通过终端git / cmd 获取用户输入路径,然后遍历路径下所有的文件,打印输出. 因为需要命令行交互,所以引入prompt库 (https://github.com/flatiron/pro ...
 - VUE常见问题解决
			
1.vue模板加载顺序 computed:例如分页的配置: created:dom加载前一般用来生成dom mounted:dom加载后用来覆盖渲染或者基于dom的操作 2.关于this指向的问题 通 ...
 - [luogu2054 AHOI2005] 洗牌 (数论)
			
传送门 Solution 我们考虑每一步牌的变化: 前半部分的牌位置*2 后半部分的牌位置*2-n-1 那么我们可以看做是\(x\times 2^m\equiv l \pmod n\) 于是求个逆元就 ...