zoukankan      html  css  js  c++  java
  • 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)MakefileMakefilecd

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

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

  • 相关阅读:
    asp.net中控件的enableviewstate属性 的设置
    一个怎样得到treeView值的小例子
    实现表格边框的样式表
    GridView的精妙使用
    无法向会话状态服务器发出会话状态请求。
    Microsoft Visual Studio 2008 快捷键大全
    我每天学习一句英语今天要学的是
    C语言学习心得
    怎么样把GridView里的数据导出到Excel里
    怎么样在c#程序中放音乐
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/7097180.html
Copyright © 2011-2022 走看看