Gradle脚本的变量及其作用域

Gradle脚本的变量及其作用域

  • Gradle脚本的变量及其作用域
    • 引言
    • 从groovy说起
    • gradle脚本中的project和this
    • gradle脚本中的变量和作用域
    • 小发现
    • 结语

引言

网上的gradle教程很多,涉及 gradle脚本中变量具体用法 的却不是很详细,导致大家在编写gradle脚本时有些茫然。本文旨在通过 反汇编 解析gradle脚本(特别是build.gradle)中 变量的定义和调用 ,来帮助大家更好地理解gradle的运行过程,从而随心所欲地在gradle脚本中使用变量。

从groovy说起

Gradle是基于groovy语言的构建工具,所以gradle脚本也是一种groovy源文件。很多让大家一头雾水的gradle脚本语法都源自于groovy。下面讲一个我见到的曾让我懵逼的语法,看看是否也让大家困惑。

def func() {
    println 'whatever'
}

project.ext.function = this.&func

之前我一直不知道this.&是啥意思,后来看到大神Johnny Yan的文章才知道this.&将func()这个函数转化成了闭包,并将闭包的owner设置为this(this具体指谁我们后面会具体讨论),注意这里的this和&是不能分开使用的。自此,我知道了想深入理解gradle最好的方法就是学好groovy,希望大家意识到这一点。

gradle脚本中的project和this

切入正题,gradle脚本中处理的变量的方式是我在 阿拉神农 大神的深入理解Android 之Gradle一文中看到的。
首先概述一下gradle对build.gradle脚本的处理,脚本中的代码大致分为两块,task的定义会生成task对象(实现了org.gradle.api.Task接口),可以作为project对象(实现了org.gradle.api.Project接口)的属性访问;不属于task范畴的代码会由script对象(实现了org.gradle.api.Script接口)执行,注意,是 执行
以上概念来自于快乐的想飞就飞大神的文章。
这也就是说build.gradle脚本中task外部的代码都是由script对象处理的,我们来验证一下。
//file:build.gradle
println this.class

结果如下
class build_85td99nvdh1hj64jxutijzevx
咦,这是什么鬼?
别急,让我们在.gradle目录下寻找一番(一般在user目录下,可以通过环境变量gradle_user_home来指定)。在cache是的子目录scripts下我们发现了 build_85td99nvdh1hj64jxutijzevx 文件夹,里面的proj\classes下的 build_85td99nvdh1hj64jxutijzevx.class 文件就是秘密所在。用jd-gui打开一看

public class build_85td99nvdh1hj64jxutijzevx
  extends ProjectScript

这不就是build.gradle脚本的this指向的对象的类吗?由此我们可以判定,build.gradle脚本中的this是一个ProjectScript的子类对象,所以说脚本中非task范畴的代码都是由script对象执行的。
我们再看看project对象是什么类的实例
println project.class
结果如下
class org.gradle.api.internal.project.DefaultProject_Decorated
这下大家应该大概知道script对象和project对象的区别了吧,下面我们继续研究脚本中的变量。

gradle脚本中的变量和作用域

既然build.gradle文件被转换成ProjectScript的子类,那么脚本中定义的变量是否是该类的成员变量呢?让我们试试

def a = "I am A"
def printA(){
    println a
}
printA()

这里我们直接在命令行输入gradle执行默认task(告诉大家个秘密,win7下shift+右键可以打开当前目录下的命令行窗口,不用苦逼地cd了),如果没有配置过,会执行:help。
结果如下

* What went wrong:
A problem occurred evaluating root project '临时工程'.
Could not find property 'a' on root project '临时工程'.

这里我们不得不提到脚本中变量的查找顺序

Generally, a Script object will have a delegate object attached to it. For example, a build script will have a Project instance attached to it, and an initialization script will have a Gradle instance attached to it. Any property reference or method call which is not found on this Script object is forwarded to the delegate object.

如文档所说,会先在script对象中寻找变量,找不到就会去找project对象中有没有此变量,当然还包括project对象的extra属性和task属性,就是project.ext.xxx和声明的task对象,我这里说的不全面,project对象中变量的查找完全的顺序参照下面的Project的property方法文档

public Object property(String propertyName)
Returns the value of the given property. This method locates a property as follows:

If this project object has a property with the given name, return the value of the property.
If this project has an extension with the given name, return the extension.
If this project's convention object has a property with the given name, return the value of the property.
If this project has an extra property with the given name, return the value of the property.
If this project has a task with the given name, return the task.
Search up through this project's ancestor projects for a convention property or extra property with the given name.
If not found, a groovy.lang.MissingPropertyException is thrown.
throws:
MissingPropertyException When the given property is unknown.
Parameters:
propertyName - The name of the property.
Returns:
The value of the property, possibly null.

回到上面的问题

def a = "I am A"
def printA(){
    //println a
}
//printA()

报错说变量a未在project对象中定义,那根据官方文档的说法,script对象中也应该未定义变量a,是不是这样呢?让我们看注释掉报错部分后,反汇编得到的类的全部属性

public build_85td99nvdh1hj64jxutijzevx() //构造函数
  {
    build_85td99nvdh1hj64jxutijzevx this;
    CallSite[] arrayOfCallSite = $getCallSiteArray();
  }

  public Object run() //执行的代码
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    Object a = "I am A"; //变量a所在的位置
    return a;return null;
  }

  public Object printA() //成员函数
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();return null;return null;
  }

可以看到,脚本中定义的方法printA()被处理成成员函数,而变量a则成了run()方法中的局部变量,所以不能被printA()方法所引用。
那该怎么把变量a声明成script对象的成员变量呢?按照下面的方式即可

import groovy.transform.Field //不导入则无法使用Field注解
@Field a = 'I am A'
def printA(){
    println a
}
printA()

来看看反编译的结果吧

Object a; //这里明确将变量a定义为成员变量 

  public Object printA()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    return arrayOfCallSite[1].callCurrent(this, this.a);
    //callCurrent的参数是( caller, argument ),arrayOfCallSite[1]
    //应该是println()方法,所以这一句相当于this.println( this.a )
    return null;
  }

  public Object run()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();null;
    if ((__$stMC) || (BytecodeInterface8.disabledStandardMetaClass())) {
      return arrayOfCallSite[0].callCurrent(this);
    } else {
      return printA();
    }
    return null;
  }

  public build_a49a9yrt6abyt0qoeix6srvlu() //构造函数
  {
    build_a49a9yrt6abyt0qoeix6srvlu this;
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    String str = "I am A";
    this.a = str; //这里可以看到变量a已经成为了成员变量
  }

小发现

执行下面两行代码

project
this.logger

反编译找处对应的java源码如下

arrayOfCallSite[0].callGroovyObjectGetProperty(this);
//this.project = this.getProject()
return arrayOfCallSite[1].callGroovyObjectGetProperty(this);
//this.logger = this.getLogger()

两行是不是差不多?已知logger是script对象的属性,这说明project对象也是script对象的属性,而且脚本里的project的调用者都是script对象,所以我们可以在脚本里写this.project 甚至 this.getProject() ,这是没有问题的。这也印证了 gradle脚本中的非task代码都是由script对象执行的这一观点。

结语

在文章的最后,我来总结一下gradle脚本几个重要的特点
1.build.gradle与project对象是一一对应的
2.script对象包含对project对象的引用
3.可以通过@Field声明script对象的成员变量,但是必须
import groovy.transform.Field
4.def声明的变量(在脚本的最外层)并不是script对象的属性(成员变量或方法),而是run()方法中的局部变量,但是可以在脚本的最外层和task的范围中使用。
5.最重要的一点,gradle脚本中所有非task范围的代码都是script对象执行的,记住这一点你就不会被this和project所困扰

最后感谢那些提供gradle教程的前辈们,这篇文章属于入门级别,论证也不够严谨,希望读者朋友们能指出不太恰当的地方,如果对您有帮助请点赞或回复我,转载请注明作者和原文地址 谢谢!

你可能感兴趣的:(gradle)