Gradle构建的生命周期和其对象的理解

文章主题内容来自Gradle官方文档Understanding the Build Lifecycler章节。通读完该章节,大大加深了我对task对象,project对象,gradle.build脚本和project对象的关系等这3个概念的理解。

官方文档地址:https://docs.gradle.org/current/userguide/build_lifecycle.html#sub:building_the_tree

构建的不同阶段

一个gradle的构建有3个不同的阶段

  1. 初始化(Initialization)

    Gradle支持单和多project的构建。在初始化阶段,gradle决定了哪一个或哪些project将要参与到这次构建,并且为每个project创建一个Project对象。(注意,一个project对应一个build.gradle文件)

  2. 安装(Configuration)

    在这个阶段,Project对象被安装(个人猜测是执行Project对象的构造函数)。所有参与到这次构建的build.gradle脚本文件都会被执行。

  3. 执行(Execution)

    在此阶段,gradle将会决定在安装(Configuration)阶段所创建和装配的tasks的哪些子集tasks要被执行。被执行的那些task是通过gradle命令的参数中的task的名字和当前在哪个目录下来决定的。gradle然后执行每一个被选中的task。

settings.gradle

  1. 除了build.gradle脚本之外,gradle还定义了一个settings.gradle文件。这个文件会在初始化(initialization)阶段被执行。
  2. 一个multi-project的构建必须有一个settings.gradle文件在根目录。因为后者定义了哪些project(也就是build.gradle脚本)会参与到这个构建。当然,单project(单个build.gradle脚本)的情况下,settings.gradle可有可无。

3个不同阶段在构建中的执行顺序

1. 定义了3个task来分别查看他们执行时打印的情况

  1. settings.gradle文件

    println('initialization:settings.gradle被执行')
    
  2. build.gradle文件

    println('configuration:build.gradle')
    task configured{
        println('configuration:task configured')
    }
    task A{
        println('configuration:task A')
        doLast{
            println '这里执行task:A#doLast'
        }
    }
    task B {
        doFirst{
            println '这里执行task:B#doFirst'
        }
        doLast{
            println '这里执行task:B#doLast'
        }
        println 'configuration:task B'
    }
    
  3. $ gradle configured

    william@localhost:~/IdeaProjects/1$ gradle configured
    initialization:settings.gradle被执行
    
    > Configure project : 
    configuration:build.gradle
    configuration:task configured
    configuration:task A
    configuration:task B
    
  4. $ gradle A

    initialization:settings.gradle被执行
    
    > Configure project : 
    configuration:build.gradle
    configuration:task configured
    configuration:task A
    configuration:task B
    
    > Task :A 
    这里执行task:A#doLast
    
  5. $ gradle B

    并被添加到Project对象的一个字段TaskContainer tasks中,initialization:settings.gradle被执行
    
    > Configure project : 
    configuration:build.gradle
    configuration:task configured
    configuration:task A
    configuration:task B
    
    > Task :B 
    这里执行task:B#doFirst
    这里执行task:B#doLast
    
  6. $ gradle A B

    initialization:settings.gradle被执行
    
    > Configure project : 
    configuration:build.gradle
    configuration:task configured
    configuration:task A
    configuration:task B
    
    > Task :A 
    这里执行task:A#doLast
    
    > Task :B 
    这里执行task:B#doFirst
    这里执行task:B#doLast
    

2. 结论

  1. 初始化阶段执行settings.gradle脚本中的内容,

  2. 安装阶段执行build.gradle脚本中除了task.doLast(Closure c)task.doFirst(Closure c)中的所有内容。因为在安装阶段时,该build.gradle文件对应的Project对象已经创建,此时安装阶段对应的就是Project对象执行其构造方法。而上述代码中在build.gradle中定义了3个task,那么在安装阶段,这3个task就会被创建为3个task对象实例,并且被加入到Project对象的TaskContainer容器中,此后,这3个task实例就作为Project对象的属性,可以直接使用了。

  3. 执行阶段,根据输入的task的名称和相关依赖(如果存在),去遍历执行对应的task对象中

    private List<ContextAwareTaskAction> actions=new ArrayList<>();
    

    的所有的方法,而当我们调用doLast或者doFirst都是往那个ArrayList的头尾插入由我们传入的闭包而转化成的Action接口的对象。而Action接口长这样:

    @HasImplicitReceiver
    public interface Action<T> {
        void execute(T t);
    }
    

    那么到执行阶段,这个task的Action的容器actions就会被遍历并调用每个Actionexecute(T t)方法。

响应build.gradle脚本的生命周期

1. Project的安装

build.gradle中的内容依靠Project对象的构造方法来安装Project对象,其中有两个回调暴露给我们,分别是

afterEvalueate(Closure closure)
beforeEvalueate(Closure closure)

它们属于Project接口中就定义了的方法,因此直接用就行

build.gradle:

afterEvaluate {
    if (it.hasProperty('group')) {
        println('has group')
        it.task('B'){
            doLast{
                println 'execute B'
            }
        }
    } else {
        println('do not have group')
    }
}
task A {
    println(' configure A')
}

执行task B

william@localhost:~/IdeaProjects/1$ gradle B

> Configure project : 
configure A
has task A

> Task :B 
execute B


BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

执行结果如上,Project#afterEvaluated(Closure closure)方法会在Project对象全部安装完之后被调用,这也是其构造方法暴露给客户端的回调,那么Project对象的构造方法的伪代码如下:

public ProjectImpl(){
    runClosureBeforeEvalueation();//执行安装前的闭包
    configureCodeInSript();//安装
    runClosureAfterEvalueation();//执行安装后的闭包    
}

2. Task的创建

在一个task对象被添加到一个Project对象后,可以立刻收到一个回调。

tasks.whenTaskAdded(Closure closure)就可以办到

build.gradle:

tasks.whenTaskAdded {
    println(it.name)
}
task A {
    println('configure A')
}
task B {
    println 'configure B'
}

执行task A

william@localhost:~/IdeaProjects/1$ gradle A

> Configure project : 
A
configure A
B
configure B

3.Task的执行

gradle.taskGraph.addTaskExecutionListener(new TaskExecutionListener() {
    @Override
    void beforeExecute(Task task) {
    }
    @Override
    void afterExecute(Task task, TaskState state) {
    }
})

gradle.taskGraph.beforeTask {
}
gradle.taskGraph.afterTask {
}

上面的接口的方法和下面的两个闭包方法的作用都是一样的,我认为他们相比于Task#doFirst或者Task#doLast方法的优点在于,TaskExecutionGraph的这几个方法,是为每一个安装了的task对象都插入一段回调,这段回调在每一个task执行前后被调用。


个人总结:

  1. 在执行build.gradle之前,他所对应的Project对象已然创建,猜测是在执行settings.gradle时已经创建了。
  2. build.gradle中的所有代码,都作为Project对象的构造函数一部分而插入构造函数。
  3. 在build.gradle脚本中创建的所有Task对象都在创建后都被Project对象的TaskContainer这个容器对象引用。
  4. 命令行每执行一次gradle命令,就创建一个程序,从类似java的main方法开始运行。创建Project对象,执行build.gradle脚本来安装(在他的构造方法中),而gradle命令后面跟随的是task的名字,此时装配好Project对象后,根据传入的task的名字来去直接执行这个task。当task执行完毕输出结果后,程序结束,main()方法结束。
  5. 当直接执行gradle命令而不加task,也会依然会执行build.gradle中为Project装配而存在的代码,即此时Project对象存在,但是只是不去execute task了。

以上,为总结和对gradle运行原理的猜想,待考证更多资料后证实之。

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

推荐阅读更多精彩内容

  • 说明 本文主要从实现原理和代码层面介绍Gradle开发相关知识。关于本文中提到的、Gradle中的基本概念等内容,...
    jzj1993阅读 7,900评论 1 33
  • 构建的生命周期 Gradle项目的构建分为三个阶段:初始化、配置、执行。参考官方手册 Build Lifecycl...
    十思叶阅读 3,076评论 1 6
  • 应用拆分 应用拆分原则 应用拆分思考 Dobbo 和 SpringCloud Dobbo : 分布式服务框架,提供...
    Marlon666阅读 453评论 0 0
  • 《班主任》 自从当了班主任 找我干啥的都有 回家收核桃的 看病的 买衣服的 买感冒药的 还有一个 成天在九月的天气...
    莉莉郭阅读 574评论 0 0
  • 清明节来看爷爷奶奶,我爷爷的性格就是那种执着不肯听人劝的那种,身上挺多病的,一只眼睛几乎看不见了,耳朵也怎么听见腿...
    08e32510824f阅读 106评论 0 0