Gradle 生态系统源码分析

Gradle 进阶 第三篇

破山中贼易,破心中贼难

Gradle Convention and Extension

接着上文所说的 DynamicSystem,这一节会进而引入 Convention and Extension 的概念,继续扩展整个系统的灵活性,在 Gradle 的 api 官网里有直接介绍 Extension 相关的 https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtensionAware.html
但是没有讲解关于 Convention 相关的。
Convention 的字面意思是指约定,而 Extension 的字面意思是扩展,从 Gradle 源码来看,Gradle 把这两者变成了一个有机的结合。节选 DefaultProject 类里的一段源码:

    @Override
    public ExtensionContainerInternal getExtensions() {
        return (ExtensionContainerInternal) getConvention();
    }

可以看到其实外部 get 到的 Extension 其实就是 Convention,我对于这段代码的理解是:只要对内满足一定的约定,可以被 Gradle 系统识别的一些 code,其实就是对整个系统的一种扩展。
这个是怎么用的呢,我在这里节选一段 JavaBasePlugin 的代码一作试例:

 private JavaPluginConvention addExtensions(final ProjectInternal project) {
        ...
        project.getConvention().getPlugins().put("java", javaConvention);
        project.getExtensions().create(JavaPluginExtension.class, "java", DefaultJavaPluginExtension.class, javaConvention, project, jvmPluginServices, toolchainSpec);
        project.getExtensions().add(JavaInstallationRegistry.class, "javaInstalls", javaInstallationRegistry);
        project.getExtensions().create(JavaToolchainService.class, "javaToolchains", DefaultJavaToolchainService.class, getJavaToolchainQueryService());
        return javaConvention;
    }

我们看到 JavaBasePlugin 通过添加了一些 Extensions 扩展了 Gradle。或者说对于 Gradle apply 的 Plugin,其实 Gradle 并没有完全定义这些 Plugin 的一些配置行为,但是 Convention 可以完成对于自定义 Plugin 的进行配置,是 Plugin 可以正常工作的基石。

在继续介绍之前我先把先关的两个接口展示一下:

/**
 * Allows adding 'namespaced' DSL extensions to a target object.
 */
public interface ExtensionContainer {
   <T> void add(Class<T> publicType, String name, T extension);
   ...
   void add(String name, Object extension);
   <T> T create(Class<T> publicType, String name, Class<? extends T> instanceType, Object... constructionArguments);
   ...
}

/**
 * <p>A {@code Convention} manages a set of <i>convention objects</i>. When you add a convention object to a {@code
 * Convention}, and the properties and methods of the convention object become available as properties and methods of
 * the object which the convention is associated to. A convention object is simply a POJO or POGO. Usually, a {@code
 * Convention} is used by plugins to extend a {@link org.gradle.api.Project} or a {@link org.gradle.api.Task}.</p>
 */
public interface Convention extends ExtensionContainer {
    
    Map<String, Object> getPlugins();
    
    <T> T getPlugin(Class<T> type) throws IllegalStateException;

    <T> T findPlugin(Class<T> type) throws IllegalStateException;

    DynamicObject getExtensionsAsDynamicObject();
}

从 Convention 接口的注解中可以看出,Convention 管理了一系列的 convention object,当一个 convention object 加入一个 Convention , convention object 的属性和方法调用就可以作用于 Convention 的属性和方法调用。这个 convention object 可以是 POJO 或者是 POGO,其中 POJO(Plain Old Java Object) 指的是普通的Java对象,可以使用 Java 或 JVM 上的其他语言来创建。POGO(Plain Old Groovy Object) 是用 Groovy 编写的对象,扩展了java.lang.Object,同时也实现了groovy.lang.GroovyObject接口。
在上一篇文章中,介绍了 dynamicTarget(ExtensibleDynamicObject),如果有不了解的同学可以转到上一篇文章中查看。我们再展示一下相关的代码:

public class ExtensibleDynamicObject extends MixInClosurePropertiesAsMethodsDynamicObject implements HasConvention {

   public ExtensibleDynamicObject(Object delegate, Class<?> publicType, InstanceGenerator instanceGenerator) {
        this(delegate, createDynamicObject(delegate, publicType), new DefaultConvention(instanceGenerator));
    }

    public ExtensibleDynamicObject(Object delegate, AbstractDynamicObject dynamicDelegate, InstanceGenerator instanceGenerator) {
        this(delegate, dynamicDelegate, new DefaultConvention(instanceGenerator));
    }

    public ExtensibleDynamicObject(Object delegate, AbstractDynamicObject dynamicDelegate, Convention convention) {
        this.dynamicDelegate = dynamicDelegate;
        this.convention = convention;
        this.extraPropertiesDynamicObject = new ExtraPropertiesDynamicObjectAdapter(delegate.getClass(), convention.getExtraProperties());

        updateDelegates();
    }

    private Convention convention;
    ...
    @Override
    public Convention getConvention() {
        return convention;
    }
    ...

其中 Convention 的具体实现是 DefaultConvention,在 ExtensibleDynamicObject 构造方法中被 new 出来。

public class DefaultConvention implements Convention, ExtensionContainerInternal {
    private final ExtensionsStorage extensionsStorage = new ExtensionsStorage();

    ...

     @Override
    public <T> void add(TypeOf<T> publicType, String name, T extension) {
        extensionsStorage.add(publicType, name, extension);
    }

    ...
    private class ExtensionsDynamicObject extends AbstractDynamicObject {
        @Override
        public String getDisplayName() {
            return "extensions";
        }
    }
}

这里一定要注意 ExtensibleDynamicObject 有两个,一个是之前一直提到的,还有一个就是在 DefaultConvention 内部的存在一个内部类 DefaultConvention.ExtensibleDynamicObject。

Plugin 的扩展

这里的扩展是指的对于 apply 了某个 Plugin 之后,Gradle 脚本里的方法查找的扩展。
先以一张图来承上启下:

PluginExtend.png

在第二篇文章中,函数的查找只停留在图中的 ExtensibleDynamicObject,这里我来展开了在 ExtensibleDynamicObject 里的方法查找流,如果在其中某一个环节找到了方法的调用,则停止继续寻找。

其中 ExtensibleDynamicObject 的调用逻辑在其基类 CompositeDynamicObject 中,如下所示:

 @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();
    }

objects 里存的就是我们上图所画的第一列。当遍历到 Convent 时候,会跳转到 DefaultConvention.ExtensibleDynamicObject, 代码如下:

        @Override
        public DynamicInvokeResult tryInvokeMethod(String name, Object... args) {
            if (isConfigureExtensionMethod(name, args)) {
                return DynamicInvokeResult.found(configureExtension(name, args));
            }
            if (plugins == null) {
                return DynamicInvokeResult.notFound();
            }
            for (Object object : plugins.values()) {
                BeanDynamicObject dynamicObject = asDynamicObject(object).withNotImplementsMissing();
                DynamicInvokeResult result = dynamicObject.tryInvokeMethod(name, args);
                if (result.isFound()) {
                    return result;
                }
            }
            return DynamicInvokeResult.notFound();
        }

    private boolean isConfigureExtensionMethod(String name, Object[] args) {
        return args.length == 1 && args[0] instanceof Closure && extensionsStorage.hasExtension(name);
    }

    private Object configureExtension(String name, Object[] args) {
        Closure closure = (Closure) args[0];
        Action<Object> action = ConfigureUtil.configureUsing(closure);
        return extensionsStorage.configureExtension(name, action);
    }

首先会判断 isConfigureExtensionMethod ,我们可以看到 ConfigureExtensionMethod 其实满足有且只有一个 closure 的参数的方法,并且这个方法可以在 extensionsStorage 中存在,也就是在上图的第二列。如果满足上述要求,那么就会用 closure 去配置这个 extension。
这里举个简单的例子方便理解,我们经常会看到这样的代码。

plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 29
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 25
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

在 apply 了 com.android.application plugin 之后,会以"android" 为名字,把一个 BaseExtension 加入 Project 中的 Convention,所以当脚本运行到 android{} 时,其实就是运行了 android(closure),就是用{}里的闭包来配置 BaseExtension。

在上面的代码里,有一段:

        for (Object object : plugins.values()) {
                BeanDynamicObject dynamicObject = asDynamicObject(object).withNotImplementsMissing();
                DynamicInvokeResult result = dynamicObject.tryInvokeMethod(name, args);
                if (result.isFound()) {
                    return result;
                }
        }

这里的 plugins 也是一些扩展,通过 getExtensions() 由对应的 Plugin 来注入到 Gradle 系统中。举个相应的例子:

plugins {
    id 'java'
}

sourceSets {
  main {
    java {
      exclude 'some/unwanted/package/**'
    }
  }
}

和上面所示不同的地方在于 android {} 是 Configure Extension Method,会走一个配置流程(configureExtension(name, args)),我们下一篇会详细讲解,但是 sourceSets 是直接去寻找调用(tryInvokeMethod)。由于篇幅有限,这一章还没有来的及引入 NamedDomainContainer 相关的逻辑,只能放到后面再讲解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容

  • Gradle 入门 第二篇 精诚所至,金石为开。 Gradle 脚本的函数的调用 接着上一篇文章的尾巴,现在需要在...
    杰克熏阅读 745评论 2 7
  • Gradle 起底 第一篇 亦余心之所善兮,虽九死其犹未悔 Gradle 的源代码地址 https://githu...
    杰克熏阅读 904评论 4 10
  • 前言 学习Gradle也有一段时间了,感觉知道了很多,但是还是有些朦朦胧胧,这时候就该写点代码来融会贯通一下, 于...
    AnAppleADie阅读 12,547评论 8 29
  • 本文由玉刚说写作平台提供写作赞助,版权归玉刚说微信公众号所有原作者:ShinyZeng版权声明:未经玉刚说许可,不...
    渡过阅读 10,910评论 3 30
  • 这篇文章讲给大家带来gradle打包系列中的高级用法-自己动手编写gradle插件。我们平常在做安卓开发时,都会在...
    呆萌狗和求疵喵阅读 15,985评论 22 80