Gradle 生态系统源码分析

Gradle 起底 第一篇

亦余心之所善兮,虽九死其犹未悔

Gradle 的源代码地址 https://github.com/gradle/gradle ,可以看到Gradle的源码里(基于 Gradle 大版本的 version 6)java 占比44% Groovy 占比46%,源码里面大部分的核心代码核心模块都是java 语言编写,test 代码主要是由Groovy语言编写。

language.PNG

目录结构

往往高端的代码都以一种朴素的呈现方式,以gradle-6.3-all 为例,解压之后目录结构如下:


***bin

*****gradle(unix and linux 启动脚本)

*****gradle.bat(windows 启动脚本)

***docs

***init.d(自定义的init.gradle 位置)

***lib(编译好的jar包)

***src(源码)

**LICENSE

**NOTICE

**README

启动Gradle就是从bin 目录下的启动脚本文件触发到lib目录下的jar里的某个java的main函数。以gradle.bat 为例,其中最终调用的位置如下:入口类是org.gradle.launcher.GradleMain。

"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.launcher.GradleMain %CMD_LINE_ARGS%

如果这个时候继续跟着这个GradleMain深入阅读源码的话,很快就会陷入一堆细节,所以在这里就不继续深入了。

Gradle 脚本文件

那我们换个方向来再去观察 Gradle 。".gradle" 文件是一个很好的入手点:


apply plugin: 'java'

...

dependencies {

    compile 'org.codehaus.groovy:groovy-all:2.3.11'

  ...

}

看到这个文件很熟悉但是肯定有很多疑问,比如 apply plugin: 'java' 干什么了,谁来编译它或者解释它给机器呢等等,为了能解答这个问题,需要好好了解一下Groovy。".gradle" 文件其实就是Groovy的的脚本文件。在这里我简单的以java程序员可以理解的方式解释一下apply plugin: 'java',在Gradle代码中有一个java函数它的名字是apply,它的参数是一个闭包:


  public void apply(Closure<?> closure){
    ....
  }

什么是闭包请找代驾:http://groovy-lang.org/closures.html

要强调一点哈,Groovy 是基于JVM,所以呢, 这个脚本文件也是会像java文件一样被编译成.class 文件, 然后被加到jvm虚拟机里运行。

所以就可以从网络上继续获取Groovy 脚本的运行原理, 以及Groovy 作为DSL的支柱: 元对象协议(Meta Object Protocol)简称MOP,基于这个协议就有了运行时和编译时的两种 metaprogramming, 细节和干货都在这个链接里面 http://groovy-lang.org/metaprogramming.html

如果上面的链接你都看完了, 那你的英语应该过了六级。回正题,要了解".gradle"文件的运行。先把脚本文件编译出来的class 文件show 出来。


....

public class build_xxxx extends ProjectScript{

....

     public Object run()

    {

        CallSite acallsite[] = $getCallSiteArray();

        acallsite[0].callCurrent(this,ScriptBytecodeAdapter.createMap(new Object[] {

            "plugin", "java"

        }));

       ...

    }

    private static void $createCallSiteArray_1(String as[])

    {

        as[0] = "apply";

        ...

    }

    private static CallSite[] $getCallSiteArray()

    {

      ...

        callsitearray = $createCallSiteArray_1(s);

      ...

        return callsitearray.array;

    }

}

熟悉Groovy Script 脚本的同学大概就知道,这个脚本的run方法是运行的入口, 通过上边所提到过的MOP,就把脚本的编译和方法的调用分开了(这里用到的是的运行时的metaprogramming),简单描述就是编译的时候脚本文件只要符合Groovy 或者java语法要求,而函数的具体实现在编译期是不需要知道的,只是记录函数的名字和参数,在运行的时候根据特定的查找顺序去寻找方法的调用,没图没真相, 上图:

groovyRTMOP.png

Gradle 脚本的编译与调用时机

在上面的段落里简单的过了一下脚本的一些信息吧, 现在肯定有同学会觉得更加疑问, 这个脚本是谁去编程class的,OK, 首先当然是你的电脑,并且也是你正在运行的Gradle去编译的,Gradle 运行之后不久就会查找settings.gradle,然后通过settings.gradle 里的project的配置去一一查找并且编译那些build.gradle 文件,对于'apply from : "xx.gradle"' 串联的脚本文件, 是在执行到 apply 这个方法的时候去查找并且编译的。这里我就直接先给出Gradle 源码里的相关类:DefaultScriptCompilationHandler.java 以及一个代码片段吧:当然首先要澄清一下这里省略了很多逻辑,比如buildscript{} 代码块里的代码会先于普通代码的编译,以及gradle的编译缓存机制(就是没改变的不会再次编译)。


 private void compileScript(ScriptSource source, ClassLoader classLoader, CompilerConfiguration configuration, File metadataDir,

                               final CompileOperation<?> extractingTransformer, final Action<? super ClassNode> customVerifier) {

      ...

        GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader, configuration, false) ...

        groovyClassLoader.setResourceLoader(NO_OP_GROOVY_RESOURCE_LOADER);

        String scriptText = source.getResource().getText();

        String scriptName = source.getClassName();

        GroovyCodeSource codeSource = new GroovyCodeSource(scriptText == null ? "" : scriptText, scriptName, "/groovy/script");

        try {

            try {

                groovyClassLoader.parseClass(codeSource, false);

           ...

    }

groovyClassLoader.parseClass 这个怎么生成class, 不是本文的范畴。编译好的class文件会放在.gradle\caches\版本号\scripts-remapped 目录下。

关于调用时机,先关的类先放出来:DefaultScriptRunnerFactory.java。


            ...

            T script = getScript();

            script.init(target, scriptServices);

            Thread.currentThread().setContextClassLoader(script.getContextClassloader());

            script.getStandardOutputCapture().start();

            try {

                script.run();

            } catch (Throwable e) {

            ...

边(编)玩(完)就run,果然比较牛逼:如下图。


 ...

            //Pass 2, compile everything except buildscript {}, pluginManagement{}, and plugin requests, then run

            final ScriptTarget scriptTarget = secondPassTarget(target);

            scriptType = scriptTarget.getScriptClass();

            CompileOperation<BuildScriptData> operation = compileOperationFactory.getScriptCompileOperation(scriptSource, scriptTarget);

            final ScriptRunner<? extends BasicScript, BuildScriptData> runner = compiler.compile(scriptType, operation, targetScope, ClosureCreationInterceptingVerifier.INSTANCE);

            if (scriptTarget.getSupportsMethodInheritance() && runner.getHasMethods()) {

                scriptTarget.attachScript(runner.getScript());

            }

            if (!runner.getRunDoesSomething()) {

                return;

            }

            Runnable buildScriptRunner = () -> runner.run(target, services);

Gradle 脚本的函数的调用

在上面的某个地方应该提到过MOP,Groovy 拥有一套查找机制,当然仅仅凭借这一点的灵活性完全无法满足Gradle 那些优(变)雅(态)的需求。所以Gradle源码又创建了DynamicObject所引申出来的一套函数和属性的查找机制。有点复杂会在后面续的文章里讲解。如果你等待不急的话也可以从我基于Gradle思想,所创建的一个log 分析项目的源代码看看:https://github.com/tianxunaicaoke/LogSpin,毕竟我的代码(需)比(要)Gradle(你)的(来)源(点)码(亮)简(星)单(星)多了。

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

推荐阅读更多精彩内容