gradle实践-build.gradle解析原理
Posted 陌路风尘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了gradle实践-build.gradle解析原理相关的知识,希望对你有一定的参考价值。
前言
基于gradle4.8源码,每个版本会有改动但是大体的设计理念还是不变的
本文主要通过源码解读gradle.build文件的解析过程,以及gradle 部分源码的设计思路。其中有些内容可能并不严谨,不正确的,随时可以指出,谢谢~~
groovy的MOP和闭包
在解析gradle配置文件运行机制前,我先简单的介绍下groovy的MOP(MetaObject Protocol元对象)基本概念,如果需要深入了解的可以去groovy官网自行学习。
GroovyObject
GroovyObject是所有grovvy对象的基类
public interface GroovyObject {
Object invokeMethod(String name, Object args);
Object getProperty(String propertyName);
void setProperty(String propertyName, Object newValue);
MetaClass getMetaClass();
void setMetaClass(MetaClass metaClass);
}
对于自定义的对象如果实现 GroovyObject
的 invokeMethod
, get/setProperty
方法,那么属性引用和方法调用都会通过自己的实现进行拦截。下面的例子可以很好的解释了其运行机制。
class Person implements GroovyInterceptable {
//GroovyInterceptable接口仅仅是一个标示,代表无论此对象是否有定义好的方法,
//都会调用invokeMethod方法,优先拦截作用
def name
def mapForStoreUnKnowProperty = new HashMap()//存储动态属性
Person(name) {
this.name = name
}
@Override
Object getProperty(String s) {
System.out.println "getProperty $s"
if (s == "name") {
return this.name
}
//如果动态属性也没有,那么就返回unknow
if (mapForStoreUnKnowProperty.containsKey(s)) {
mapForStoreUnKnowProperty.get(s)
} else {
"unknow property $s"
}
}
//如果没有此属性,那么使用动态属性容器进行存储。
@Override
void setProperty(String s, Object o) {
System.out.println "setProperty $s"
if (s == "name") {
this.name = o
} else {
mapForStoreUnKnowProperty.put(s, o)
}
}
//拦截方法
@Override
Object invokeMethod(String s, Object o) {
System.out.println "invokeMethod $s $o"
if (s == "sayHelloWord") {
System.out.println "hello word"
} else {
System.out.println "no method"
}
}
static void main(String[] args) {
def person = new Person("god")
System.out.println "person ${person.name} ${person.noProperty}"
person.noProperty = "动态属性value"
System.out.println "person ${person.noProperty}"
person.sayHelloWord()
}
}
输出内容:
getProperty name
getProperty noProperty
person god unknow property noProperty
setProperty noProperty
getProperty noProperty
person 动态属性value
invokeMethod sayHelloWord []
hello word
MetaClass
groovy对象除了自身可以重写代理拦截方法和属性外,还可以通过设置MetaClass的方式进行属性和方法的拦截。metaClass的定义如下:
public interface MetaClass extends MetaObjectProtocol {
//拦截方法
Object invokeMethod(Class var1, Object var2, String var3, Object[] var4, boolean var5, boolean var6);
//拦截属性
Object getProperty(Class var1, Object var2, String var3, boolean var4, boolean var5);
void setProperty(Class var1, Object var2, String var3, Object var4, boolean var5, boolean var6);
//如果没有重写invokeMethod同时又没有定义此方法,会回调到此处
Object invokeMissingMethod(Object var1, String var2, Object[] var3);
....//省略一些~~
}
所有的groovy对象,可以调用setMetaClass是进行设置元对象,具体的例子就不提供了。
metaClass的属性和方法拦截优先级高于自身实现。
Groovy闭包Closure
Groovy对于用大括号包起来的代码块会生成一个Closure闭包对象,闭包有两个对象 Delegate
和 owner
对象,对于代码块的中属性和方法调用,首先会委托owner处理,如果未找到,则会使用deletegate处理,同时闭包支持动态修改这两个对象,这也是Groovy的语言特点,也是groovyDSL的基础,更加详细关于闭包的内容可以看groovy的官网。
gradle配置脚本评估(evaluate)
gradle的ASM
gradle使用java和groovy语言共同构建,同时使用了ASM(字节码操作)对很多内部类进行了改造,比如:为属性增加get或者set方法,为类增加接口和实现,增加DSL特性等,所以在解析源码的时候可能会遇到接口判断,方法不存在等,但是却又能正常执行的情况。凡是在调试的时候类名的描述加上了 __Decorated
都是改造后的类.
有几个比较重要的改造内容:
为类增加DynamicObjectAware接口,返回DynamicObject对象。这个操作非常的普遍,很多类都增加了这个实现,然后调用getAsDynamicObject返回与之关联的dynamicObject。
很多方法的参数是Action,gradle会默认生成一个调用action的Closure的方法。
为java类增加metaclass,dynobj等属性,并提供get方法。
build.gradle解析入口
gradle的执行是通过groovy进行脚本解析和运行,每一个buid.gradle都会生成一个对应的Project对象,这个project对象就是这个脚本文件的执行环境。(那对于这个对象产生,执行环境的生成是很复杂的过程,并且使用了ASM,有兴趣的可以自行解读)
直观上看,就好比build.gradle是一个闭包,而 BaseScript
是这个闭包的delegate对象。
DynamicObject 动态对象
上图中的DynamicObject,是gradle源码中非常重要的组成部分,主要负责脚本属性和方法的委托,它有几个重要的实现类,使用了装饰着模式进行代理。下图是DynamicObject的类图关系
由于截图比较小,可以查看我的原始图
ConfigureDelegate 配置代理
配置代理对象是对DynamicObject执行的封装,具体的可以看源代码,比较简单,它有个常用的子类 NamedDomainObjectContainerConfigureDelegate
,这个子类扩展了动态创建dsl,函数的能力,主要于 NamedDomainObjectContainer
配合使用(后面会详细介绍 NamedDomainObjectContainer
)
ConfigureDelegate
还有个工具类 ConfigureUtil
,提供便捷的ConfigureDelegate对象构造,执行的函数。
评估脚本配置
为了更加方便的解读,在github上创建了个gradle的项目configuration,后面分析全都是以该项目的build.gradle为基础的,build.gradle文件比较长, 就不贴出来了。同时为了方便理解,我把此文件划分了几个部分,后面会一一说明。
脚本解读的代码入口是 BasicScript
invokeMethod/getProperty/setProperty方法,它则会直接调用 ScriptDynamicObject
的相关方法,而 ScriptDynamicObject
中有个成员变量 dynamicTarget
一般此 DynamicObject
是 ExtensibleDynamicObject
对象,所以只需要在 ScriptDynamicObject
相关方法打上断点,就可以拦截到执行的过程。
在project中的extensibleDynamicObject对象和上面的ExtensibleDynamicObject是同一个对象,而project的属性和方法查找也会执行到ExtensibleDynamicObject中,应该是project的metaClass使用project的extensibleDynamicObject对象进行操作。所以在脚本里project.name和name执行的效果是一样的。project有个getProject方法,返回了自己。
在 ExtensibleDynamicObject
中有个很重要的容器 objectDynamicObject[]
,它一般含有四个对象
(版本不同插件不同可能会有所不同,但是原理是一样的,我这里使用了最基础的java插件)
BeanDynamicObject
此对象方法和属性的查找委托给了自身代理的Bean也就是project对象,bean通过MetaClass进行查找。ExtraPropertieDynamicObjectAdapter
只能读取/修改属性,不能代理方法,此处代理的对象其实是DefaultConvention
中的extensionsStorage
里的ext
,其实应该是为了加快属性读取和修改效率的。因为这两个对象是同一个。DefaultConvention.ExtensionsDynamicObject
。DefaultConvention
负责管理插件提供的约束(DSL配置),同时还负责extensionsStorage
中扩展配置,例如:ext
的扩展属性,reporting
的报告配置等。DefaultTaskContainer(DefaultNamedDomainObjectCollection).ContainerElementsDynamicObject
存储所有task信息,支持task的属性访问和方法调用
ok,现在了解基本的配置情况后,看一下实际的运行情况。
第一部分 project属性配置
plugins {
id 'java' //java插件
id 'com.demon.yu' //自定义的插件
}//不做分析
/**第一部分*/
//defaultProject中的方法
group 'com.demon.yu.gradle'
project.version '1.0-SNAPSHOT'
//defaultProject属性
rootProject.version = "1.0-SNAPSHOT"
看下 group'com.demon.yu.gradle'
这行脚本,会被BaseScript解析成方法调用,方法名称为 group
参数为 'com.demon.yu.gradle'
字符串。然后BaseScript对象,会通过dyanamicObject对象调用到 CompositeDynamicObject
的 tryGetMethod
方法中, CompositeDynamicObject
的 tryInvokeMethod
方法实现如下:
@Override
public DynamicInvokeResult tryInvokeMethod(String name, Object... arguments) {
for (DynamicObject object : objects) {
DynamicInvokeResult result = object.tryInvokeMethod(name, arguments);
if (result.isFound()) {
return result;
}
}
return DynamicInvokeResult.notFound();
}
遍历我们前面所说的容器,如果某个 dynamicObject
找到对应的方法就处理,如果没有找到则返回。 对于 group'com.demon.yu.gradle'
脚本块来说,由于 defaultProject
的metaClass有group(String),其内部调用了defaultProject.setGroup(String)方法,所以在容器第一个beanDynamicObject中就找到并反射调用。这样此代码块就执行了。
前面说过defaultProject经过了ASM改造,所以有个metaClass属性和getMetaClass方法,而且对于group属性也生成了group(String) 方法。
对于 project.version'1.0-SNAPSHOT'
和 rootProject.version="1.0-SNAPSHOT"
执行的过程类似,只不过是先找到 DefaultProject
的 project和rootProject
属性,然后通过metaClass,DefaultProject调用setVertion(String)方法或者直接给属性vertion赋值
此处对于直接赋值比较好理解,但是对于调用setVertion可能有些疑惑,其实这里在找到project属性后,后面跟着名字+空格+参数,那么脚本解释器会把这些组成CallSite调用点执行对象,直接通过project的metaClass查找方法并反射调用。
第二部分 ext属性配置
第二部分主要解析扩展属性变量
//ext是DefaultExtraPropertiesExtension
ext.vertionCode = 1
ext {//闭包环境为DefaultExtraPropertiesExtension
targetSDK = 28
}
首先来看 ext.vertionCode=1
,和第一部分一样,首先查找ext属性,在容器第三个对象 DefaultConvention.ExtensionsDynamicObject
中,通过源码可以看到,它并不是查找自身属性,而是通过两个容器对象 extensionsStroage
和 plugins
进行查找,看下源码:
@Override
public DynamicInvokeResult tryGetProperty(String name) {
Object extension = extensionsStorage.findByName(name);
if (extension != null) {
return DynamicInvokeResult.found(extension);
}
...//省略其他的
return DynamicInvokeResult.notFound();
}
extensionsStorage
是个工具类,封装了一个 LinkedHashMap<String,Object>
容器,用来存储键值对象。在DefaultConvention初始化的时候,默认添加了个 ext:String->extraProperties:DefaultExtraPropertiesExtension
数据,所以gradle默认就可以处理ext DSL的数据和闭包,javaBasePlugin和javaPlugin等等插件也会通过 DefaultConvention
往 extensionsStorage
中添加数据。除了 extensionsStorage
, DefaultConvention
还会查询plugins,plugins存放的是插件的Convention对象后面第三部分会详细讲解。在找到ext里的 ExtraPropertiesExtension
对象后,函数返回,然后开始处理 DefaultExtraPropertiesExtension.vertionCode=1
的脚本。
DefaultExtraPropertiesExtension
实现了 GroovyObjectSupport
重写了get/setProperty方法,所以属性调用会执行get/setProperty方法,这样DefaultExtraPropertiesExtension会存储相应键值对数据了。 对于代码段
ext {
targetSDK = 28
}
执行原理类似,不过此处会执行 DefaultConvention
的 tryInvokeMethod
方法,因为脚本解释器解析的结果是方法调用 - ext(Closure).
@Override
public DynamicInvokeResult tryInvokeMethod(String name, Object... args) {
if (isConfigureExtensionMethod(name, args)) { //判断包含ext同时第1个参数是闭包,那么就会执行configureExtension(name, args)
return DynamicInvokeResult.found(configureExtension(name, args));
}
....
return DynamicInvokeResult.notFound();
}
private boolean isConfigureExtensionMethod(String name, Object[] args) {
return args.length == 1 && args[0] instanceof Closure && extensionsStorage.hasExtension(name);
}
而 configureExtension(name,args)
方法的最终结果是以 DefaultExtraPropertiesExtension
为deletegate执行闭包。最后把闭包的数据添加到 DefaultExtraPropertiesExtension
中数据容器里。此时的结果 DefaultExtraPropertiesExtension
存储了两个数据 vertionCode=1
和 targetSDK=28
,这个时候会发现在ExtensibleDynamicObject中的第二个dynamiobject(ExtraPropertiesDynamicObjectAdapter)中的 ExtraPropertiesExtension
也会直接包含了两个数据,所以后面的脚本可以直接读取/修改数据,但是不可以设置新的数据。
public class ExtraPropertiesDynamicObjectAdapter extends AbstractDynamicObject {
private final ExtraPropertiesExtension extension;//和defaultConvention中extensionsStorage存储的ext对应对应的DefaultExtraPropertiesExtension是同一个对象。
public ExtraPropertiesDynamicObjectAdapter(Class<?> delegateType, ExtraPropertiesExtension extension) {
this.delegateType = delegateType;
this.extension = extension;
}
...//省略其他的
@Override
public DynamicInvokeResult tryGetProperty(String name) {
if (extension.has(name)) {
return DynamicInvokeResult.found(extension.get(name));
}
return DynamicInvokeResult.notFound();
}
@Override
public DynamicInvokeResult trySetProperty(String name, Object value) {
if (extension.has(name)) {
extension.set(name, value);
return DynamicInvokeResult.found();
}
return DynamicInvokeResult.notFound();
}
...//省略其他
}
第三部分 DefaultConcention属性配置
这个部分主要解析DefaultConvention相关内容
/**第三部分*/
//javaPluginConvention
sourceCompatibility = 1.8
//javaPluginConvention //执行的context
sourceSets {
//sourceSetContainer
main {
//sourceSet
java {
//sourceDirectorySet
srcDirs = ["src/java"]
exclude 'some/unwanted/package/**'
}
}
namain{java{}}//可以随便写的
}
首先看fu下 sourceCompatibility=1.8
,还是老套路,ExtensibleDynamicObject容器中第三个DynamicObject(ExtensionsDynamicObject)找到结果。看一下它的实现。
@Override
public DynamicInvokeResult trySetProperty(String name, Object value) {
checkExtensionIsNotReassigned(name);
if (plugins == null) {
return DynamicInvokeResult.notFound();
}
for (Object object : plugins.values()) {
BeanDynamicObject dynamicObject = asDynamicObject(object).withNotImplementsMissing();//以Convention对象封装一个BeanDynamicObject实例,然后执行属性设值。
DynamicInvokeResult result = dynamicObject.trySetProperty(name, value);
if (result.isFound()) {
return result;
}
}
return DynamicInvokeResult.notFound();
}
plugins存储的是插件添加的Convention实例,例如:java插件的JavaPluginConvention。在JavaPluginConvention中找到sourceCompatibility属性并赋值(这个地方有点特殊,找到的是MultipleSetterProperty属性,需要调用其关联的set方法进行赋值的)
再来看 sourceSets{闭包}
部分,同样在JavaPluginConvention中找到sourceSets(Closure)方法,其内部实现直接调用 DefaultSourceSetContainer.configure(Closure)
。 DefaultSourceSetContainer
是 NamedDomainObjectContainer
的子类, NamedDomainObjectContainer
是键值对容器,方法调用(只有一个闭包参数的方法)和属性读取/赋值都是查找是否存在对应键名,它和 NamedDomainObjectContainerConfigureDelegate
形成一个动态创建domain的功能,调用关系和组织结构如下:
在找到 DefaultSourceSetContainer
后,继续执行main{闭包},此时在 DefaultSourceSetContainer
中找到名字为main的sourceSet,然后就继续上面类似的操作。
JavaPlugin在DefaultSourceSetContainer添加了main和test两个DefaultSourceSet,DefaultSourceSet默认设置了源码文件夹和资源文件夹等数据
第四部分 task定义配置相关
task是gradle非常重要的部分,task的定义和使用主要涉及到ExtensibleDynamicObject容器中 BeanDynamicObject
和 DefaultNamedDomainObjectCollection.ContainerElementsDynamicObject
。
//直接反射到defaultProject的task方法
task helloWord(dependsOn: 'jar') {
//DefaultTask
println("配置task helloWord时执行")
group="groupName"
description = "任务描述"
doLast {
println("执行helloWord doLast")
}
}
println helloWord.description
//很多种定义task的写法,具体可以看下DefaultProject的实现
def map = ["dependsOn": 'helloword']
task map, "helloNext", {
}
task noThing
task函数,都位于 DefaultProject
中,基本上直接调用成员变量 DefaultTaskContainer
的create和configure方法。 DefaultTaskContainer
同样是 NamedDomainObjectContainer
子类,主要负责创建存储Task,同时他的祖先类中的变量D efaultNamedDomainObjectCollection.ContainerElementsDynamicObject
负责查找和执行Task。task定义过程和第三部分过程差不多。唯一的不同 helloWord.description
这种任务读取使用的是 DefaultNamedDomainObjectCollection.ContainerElementsDynamicObject
委托。
第五部分 其他
第五部分简单说下gradle的configuration
/**第五部分*/
//configurationContainer是NamedDomainObjectContainer。sourceSetContainer类似的东西
configurations {
myConfiguration
compile.transitive = true
}
//defaultProject
artifacts { //DefaultArtifactHandler
myConfiguration jar //jar是task名字,根据源码还可以支持很多种类型,
}
//defaultProject
dependencies {//DependencyHandler
compile gradleApi()//DependencyHandler有个gradleApi函数,还有几个其他的
}
configuration代表的是一组文件的集合以及它的依赖, ConfigurationContainer
是 NamedDomainObjectContainer
子类,主要用于创建和管理configuration。configurations{闭包}处理流程和 DefaultSourceContainer
, DefaultTaskContainer
容器都差不多,只是具体细节和目的不同。在处理myConfiguration会创造一个不做任何事情的configuration, compile.transitive = true则会找到名字为compile的configuration进行处理。其余的都可以按照以前的思路进行处理了。
总结处理流程
其实,整个gradle评估的处理流程非常的类似,就是一个巨大的容器,属性的查找和方法调用都是在遍历容器,同时,子容器同样支持遍历的操作。
以上是关于gradle实践-build.gradle解析原理的主要内容,如果未能解决你的问题,请参考以下文章
Android Studio 中build.gradle文件的详细解析
即使在 build.gradle 中指定,也无法解析符号“EnableJpaRespositories”
同步 gradle 的问题:无法解析外部依赖项 com.android.tools.build:gradle,因为没有定义存储库
无法在 app/build.gradle 中在线导入 com.android.build.OutputFile 解析符号“build”