Makefile简略犯错的语法(不懂这些完全没法做工程)

Makefile容易犯错的语法(不懂这些完全没法做工程)

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)MakefileMakefilecdcd$(dir),编译器展开变量的时候就当做是shell变量,结果就是成功的。

4.Makefile执行流程(也是很重要的呀)

了解make如何解析makefile文件是非常重要的,GUN make的执行过程分为两个阶段:

  • 读取所有的makefile文件,内建所有变量/函数,并建立目标和依赖之间的依赖关系
  • 根据第一个阶段建立的依赖关系,决定重构哪些目标,并执行命令进行重建目标

了解make执行过程的两个阶段是非常重要的,它帮助我们更深入的了解执行过程中变量以及函数是如何被展开的。变量和函数的展开问题是书写Makefile时容易犯错和引起大家迷惑的地方,本节将对这些不同的结构的展开进行简单的总结(明确变量和函数的展开阶段,对正确使用变量非常有帮助)。

首先明确一个概念:在make执行的第一个阶段如果变量和函数被展开,那么称此展开是立即的,此时所有的变量和函数被展开在需要构建的结构链表的对应规则中,其它展开称为延后的,这些变量和函数延迟到某些规则需要使用时或make第二阶段展开。

  • 条件语句的展开
    -所有使用到条件语句在产生分支的地方,make会根据预设条件将正确的分支展开,就是说条件分支的展开是立即的,其中包括ifdef、ifeq、ifndef、ifneq所确定的分支命令。
  • 规则的展开
IMMEDIATE : IMMEDIATE ; DEFERRED
DEFERRED

其中规则中的目标和依赖如果引用其他变量,则被立即展开。而命令中的引用会延迟展开。

有了前面的基础,下面引用make手册中的执行流程:
Makefile简略犯错的语法(不懂这些完全没法做工程)

下面举个例子,从侧面验证上面的论述:

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被定义了两次,这是允许的

版权声明:本文为博主原创文章,未经博主允许不得转载。