Gradle 学习笔记
Gradle 学习笔记
以下内容,理论部分大部分是gradle和groovy的官方文档的解释,实例部分是自己的尝试,如有错误,请不吝指正,谢谢!
0x00 groovy 基本知识
groovy与java
groovy 是一个jvm语言,这意味着除了groovy编译器提供的语法糖和groovy标准库以外,它和java没有区别,我们来看一个Hello World
g2j.groovy
def author = 'chenlong'
def getAuthorName(){
//return author 思考一下为什么这里不能访问到author变量
return 'chenlong'
}
def sayHello(){
println 'Hello '+getAuthorName()
}
sayHello()
看一下编译成jvm字节码后的结果
g2j.class
import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class g2j extends Script
{
public g2j()
{
g2j this;
CallSite[] arrayOfCallSite = $getCallSiteArray();
}
public g2j(Binding context)
{
super(context);
}
public static void main(String[] args)
{
CallSite[] arrayOfCallSite = $getCallSiteArray();
arrayOfCallSite[0].call(InvokerHelper.class, g2j.class, args);
}
public Object run()
{
CallSite[] arrayOfCallSite = $getCallSiteArray();
Object author = "chenlong";//注意这里,author并不是d2j类的成员变量,所以无法在方法内部访问
if ((__$stMC) || (BytecodeInterface8.disabledStandardMetaClass()))
return arrayOfCallSite[1].callCurrent(this);
else
return sayHello();
return null;
}
public Object getAuthorName()
{
CallSite[] arrayOfCallSite = $getCallSiteArray();
return "chenlong";
return null;
}
public Object sayHello() {
CallSite[] arrayOfCallSite = $getCallSiteArray();
if ((__$stMC) || (BytecodeInterface8.disabledStandardMetaClass()))
return arrayOfCallSite[2].callCurrent(
this,
arrayOfCallSite[3].call("Hello ", arrayOfCallSite[4].callCurrent(this)));
else
return arrayOfCallSite[5].callCurrent(
this,
arrayOfCallSite[6].call("Hello ", getAuthorName()));
return null;
}
}
闭包
闭包的概念也许我们稍微陌生一点,但是实际上,我们可以简单把它当做一个匿名类,只是编译器提供了更加简单的语法来实现它的功能。
groovy的闭包是一种表示可执行代码块的方法,闭包也是对象,可以像方法一样传递参数,并且可以在需要的地方执行。一个最简单的闭包形如:
def clos = { params ->
println "Hello ${params}"
}
clos("World")
同时,闭包可以作为参数传递,例如:
def clos = { it ->
return it%2 == 0
}
def list = [1,2,3,4,5,6]
def filter(list,closure){
def res = []
for(i in list){
if(clos(i))
res.add()
}
return res
}
闭包有三个很重要的属性分别是:this
,owner
,delegate
,分别代表以下概念:
this: 表示定义该闭包的类的实例对象(实例闭包)或者类本身(静态闭包)
owner: 它的含义基本上跟this的含义一样,只是除了一种情况,如果该闭包是在其他的闭包中定义的,那么owner是指向定义它的闭包对象
delegate: 它的含义大多数情况下是跟owner的含义一样,除非它被显示的修改(通过Closure.setDelegate()方法进行修改)
DSL
借助闭包的特性,我们可以尝试写一个简单的DSL。下面的代码展示了如何借助groovy的语法特性来实现一个DSL,这些特性我们稍后会在gradle的脚本中看到。
BookDSL.groovy
class Book {
def _name = ''
def _price = 0.0
def shop = []
def static config(config){
Book book = new Book(shop:['A','B'])
config.delegate = book
config()
}
def name(name){
this._name = name
}
def price(price){
this._price = price
}
def getDetail(){
println "name : ${_name}"
println "price : ${_price}"
println "shop : ${shop}"
}
}
Book.config {
name 'test'
price 1.2
detail
}
0x01 Gradle 基本概念
1. 基本元素
Project : 需要完成的工作,这里的工作不一定是构建,可以是任何一件事情,当然我们也可以简单的理解它就是我们需要构建的工程。
Script : gradle的脚本文件,通过脚本,我们可以定义一个Project
Task : Project中的具体执行的原子性工作,以构建一个工程为例,它可以是 编译,执行单元测试,发布 等。
2. Script元素
Init Script
似乎从来没有使用过,但是在每一次构建开始之前,都会执行init script,用来设置一些全局变量,有多个位置可以存放init script如下:
USER_HOME/.gradle/
USER_HOME/.gradle/init.d/
GRADLE_HOME/init.d/
Settings Script
用来在组织多工程的构建,存在于root工程下,settings.gradle
上述script在运行时都会被编译成一个实现了Script接口的class,同时每一个script都有一个委托对象
Build Script -> Project
Init Script -> Gradle
Settings Script -> Settings
任何没有在脚本中定义的方法或者属性,都会被委托给这个脚本的委托对象执行。
Build Script
每一个build.gradle都是一个Build Scrpit,它由两种元素组成。
statement
可以包含方法调用,属性赋值,局部变量定义等
script blocks
block的概念稍微复杂一点,首先我们先要理解一个groovy的元素,闭包。可以参考上面对闭包的解释
有了闭包的概念,那么理解script block就没有障碍了,我们直接看文档中的定义:
A script block is a method call which takes a closure as a parameter. The closure is treated as a configuration closure which configures some delegate object as it executes.
翻译一下就是
一个脚本块是一个接受一个闭包作为参数的方法,这个闭包在执行的时候配置它的委托对象。
文档描述的比较抽象,我们看一个例子:
build.gradle
def buildVersion = '1.2.0'
def author = 'chenlong'
allprojects {
println 'this project is created by ${author}'
setVersion(buildVersion)
}
getAllprojects().each{
println it.getVersion()
}
首先我们定义了两个变量分别是buildVersion
和author
,在执行时,这个两个变量会成为Script Class的属性。然后,我们使用了一个script block,根据定义,这个block对应着一个同名方法allprojects
,可是我们并没有在脚本中定义这样一个方法,那它如何执行呢?回想一下我们刚刚看到的build script的委托对象,没错,这个方法被委托给了Project对象执行,查看文档,我们确实在Project中找到了这个同名方法.
接下来,我们在块中写了两行代码,这就是这个闭包需要执行的代码,首先打印一行文字,其次setVersion()。同样的,我们没有定义setVersion这个方法,这就涉及到闭包的一些概念,我们换一种写法
delegate.setVersion(buildVersion)
setVersion 这个方法实际上是由闭包的委托对象执行的,那委托对象是什么呢?我们查阅一下allprojects这个方法的Api,如下
这个闭包的委托对象是当前的project和它的子project,也就是对于一个包含子工程的工程,这个闭包会执行多次,是不是这样呢,我们实验一下,我创建了一个结构如下的工程
在root工程下执行上述脚本,结果如下:
3. Project 对象
每一个build.gradle文件和一个Project对象一一对应,在执行构建的时候,gradle通过以下方式为每一个工程创建一个Project对象:
- 创建一个Settings对象,
- 根据settings.gradle文件配置它
- 根据Settings对象中定义的工程的父子关系创建Project对象
- 执行每一个工程的build.gradle文件配置上一步中创建的Project对
在build.gradle文件中定义的属性和方法会委托给Project对象执行,每一个project对象在寻找一个属性的时候有5个作用域作为范围,分别是:
属性可见范围
- Project 本身
- Project的extra属性
- 通过plugin添加的extension,每一个extension通过一个和extension同名的只读属性访问
- 通过plugin添加的属性。一个plugin可以通过Project的Convention对象为project添加属性和方法。
- project中的task,一个task对象可以通过project中的同名属性访问,但是它是只读的
- 当前project的父工程的extra属性和convention属性
方法可见范围
- Project对象本身
- build.gradle文件中定义的方法
- 通过plugin添加的extension,每一个extension都作为一个接受一个闭包/Action作为参数的方法被访问
- 通过plugin添加的方法。一个plugin可以通过Project的Convention对象为project添加属性和方法。
- project中的task,每一个task 都会在当前project中存在一个接受一个闭包或者/Action作为参数的方法,这个闭包会在task的configure(closure)方法中调用。
- 当前工程的父工程中的方法
- 当前工程的属性可见范围中所有的闭包属性都可以作为方法访问
4. 构建的生命周期
一次gradle的构建有三个过程
初始化
gradle支持单工程和多工程构建,在初始化的过程中,gradle决定了这次构建包含哪些工程,并且为每一个工程创建一个Project对象。需要注意一点,对于一个多工程的项目,即使通过传入的参数指定了需要构建的子工程,但是,所有在Settings script中包含的工程的build script仍然会执行,因为gradle需要为每一个Project对象配置完整的信息。
配置
在配置的过程中,本次构建包含的所有工程的build script 都会执行一次,同时每个工程的Project对象都会被配置,运行时需要的信息在这个过程中被配置到Projec对象中。最重要的是,在build script中定义的task将在这个过程创建,并被初始化。需要注意的是,在一般情况下,只要在初始化阶段创建的Project对象都会被配置,即使这个工程没有参与本次构建。但是,在1.4之后,一个新的特性被引入,Configuration on demand 如果开启这个选项,那么即使在setting.gradle中包含了工程,但是这个工程不参与本次构建,那么它的Project对象会被创建,但是不会被配置。这也就解释了为什么gradle在多工程构建时运行缓慢了,因为默认情况下,每一个工程的build script都会执行。
执行
gradle通过传入的参数来决定哪些任务会被执行,并生成任务的依赖图(DAG),这意味着,即使在build Script中定义了一个task,但是在构建时这个任务并不会执行,那么这个task对象虽然会被创建,但是不会在任务依赖图中出现。
下面我们通过一个例子来看看gradle的构建生命周期究竟是怎么样的。
工程结构如下:
root Project:gradleStudy
-- lib
build.gradle
-- sub
build.gradle
build.gradle
settings.gradle
root/settings.gradle
println "setting srcipt execute "
rootProject.name = 'gradleStudy'
include ':lib'
include ':sub'
root/build.gradle
println "root build srcipt execute"
allprojects {
afterEvaluate { project ->
println "define in root project : after ${project.name} evaluate"
}
beforeEvaluate { project ->
println "define in root project : before ${project.name} evaluate"
}
}
gradle.settingsEvaluated { settings ->
println "emit by gradle : Setting evaluated"
}
gradle.projectsLoaded { gradle ->
println "emit by gradle : project loaded"
}
gradle.beforeProject { project ->
println "emit by gradle : before ${project.name} evaluate "
}
gradle.afterProject { project ->
println "emit by gradle : after ${project.name} evaluate "
}
gradle.projectsEvaluated { gradle ->
println "emit by gradle : all project evaluated "
}
gradle.taskGraph.whenReady { graph ->
println "task graph is ready"
graph.getAllTasks().each {
println "task ${it.name} will execute"
}
}
gradle.taskGraph.beforeTask { task ->
println "before ${task.name} execute"
}
gradle.taskGraph.afterTask { task ->
println "after ${task.name} execute"
}
gradle.buildFinished { buildResult ->
println "emit by gradle : build finished"
}
println "all project size : ${allprojects.size()}"
lib/build.gradle
println 'lib build script execute'
task libTask0 <<{
println "${name} execute"
}
task libTask1 << {
println "${name} execute"
}
task libTask2(dependsOn:'libTask1')<<{
println "${name} execute"
}
beforeEvaluate { project ->
println "define in lib : before ${project.name} evaluate"
}
afterEvaluate { project ->
println "define in lib : after ${project.name} evaluate"
}
println "lib project has ${tasks.size()} tasks"
sub/build.gradle
println 'sub build script execute'
task subTask1 << {
println "${name} execute"
}
task subTask2(dependsOn:'libTask1')<<{
println "${name} execute"
}
beforeEvaluate { project ->
println "define in sub : ${project.name} evaluate"
}
afterEvaluate { project ->
println "define in sub : ${project.name} evaluate"
}
在根目录下执行 gradle -q :lib:libTask2 --configure-on-demand
在根目录下执行 gradle -q :lib:libTask2
从上述例子中我们验证了以下结果:
- 在configuration demand 模式下执行构建时,虽然sub 工程的Project对象会创建,但是sub/build.gradle并不会执行,并且sub工程的Project不会被配置
- 如果一个task在build.gradle中定义,但是在构建中不会执行,那么它的Task对象会创建,但是不会在任务图中出现。
- 我们可以通过Gradle或者Project对象中定义的方法获取生命周期中每一个过程在执行中的回调。这里注意一下,我们定义的一些回调在实际执行中似乎并没有被触发,例如,
settingsEvaluated
,projectsLoaded
。这个问题,似乎和gradle的版本更新有关,我正在尝试获取官方的解释。
0x02 写一个简单的插件
我跳过了一些比较简单的内容,例如如何在Build Script中定义一个Task,有兴趣的可以参考文档。
首先看一下工程结构
gradleStudy //root工程
lib //子工程
repo //本地maven目录
simplePlugin //plugin工程
build.gradle //root工程的build script
settings.gradle //root工程的setting script
simplePlugin工程的目录结构
simplePlugin
src
main
groovy
com
haizhi
oa
GreetingExtension.groovy //Extension Model
GreetingPlugin.groovy //插件类
SayHelloTask.groovy //任务类
resources
META-INF
gralde-plugins
greeting-plugin.properties //插件名
build.gradle
首先,插件工程可以用任意的jvm语言编写,例如,scala,groovy,java等,最终每一个插件都会打包成一个jar包,其中META-INF文件下中每一个.properties文件代表一个Plugin,里面指明了插件类的全类名,如下:
implementation-class=com.haizhi.oa.GreetingPlugin
GreetingExtension.groovy
package com.haizhi.oa
//定义一个属性类,其中包含message和showAuthorName
class GreetingExtension {
def message = ''
def showAuthorName = false
}
GreetingPlugin.groovy
package com.haizhi.oa
import org.gradle.api.Project
import org.gradle.api.Plugin
class GreetingPlugin implements Plugin<Project>{
@Override
public void apply(Project project){
//创建一个类型为GreetingExtension,名称为greeting的属性
project.extensions.create('greeting',GreetingExtension)
//创建一个类型为SayHelloTask,名称为sayHello的任务
project.task('sayHello',type:SayHelloTask)
}
}
SayHellTask.groovy
package com.haizhi.oa
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class SayHelloTask extends DefaultTask{
//定义一个属性
def author = ''
@TaskAction
public void sayHello() {
//从Project中获取greeting属性
if(project.greeting.showAuthorName){
println "Hello ${author} ${project.greeting.message}"
}else{
println "Hello ${project.greeting.message}"
}
}
}
build.gradle
apply plugin: 'groovy'
apply plugin: 'maven'
//定义发布到maven的版本,group,name
version = '1.0.0'
group = 'com.haizhi.oa'
archivesBaseName='greeting-plugin'
repositories {
mavenCentral()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.4'
compile gradleApi()
compile localGroovy()
}
uploadArchives{
repositories.mavenDeployer{
repository(url : 'file:../repo')//定义本地maven地址
}
}
在simplePlugin的根目录下执行,gralde uploadArchives
,编译插件工程,并发布到../repo
目录。
以上,我们就创建好了一个gradle plugin,那么如何使用它呢?
首先,在root工程下,我们通过buildscript
引入插件
buildscript{
repositories{
mavenCentral()
maven {
url uri('./repo')
}
}
dependencies {
classpath 'com.haizhi.oa:greeting-plugin:1.0.0'
}
}
然后,在lib工程下,我们应用这个插件,
apply plugin : 'greeting-plugin'
greeting.message = 'this is a simple plugin' //为greeting属性赋值
greeting.showAuthorName = true //为greeting属性赋值
sayHello.author = 'chenlong' //为sayHello任务的author属性赋值
似乎和我们通常看到的插件配置方式不太一样,我们换一种配置方式
apply plugin : 'greeting-plugin'
greeting {
message 'this is a simple plugin'
showAuthorName true
}
sayHello {
author 'chenlong'
}
看上去是不是熟悉了很多
下面,在lib或者根目录下执行,gradle sayHello
,执行结果如下:
以上就是一个自定义插件的创建和应用过程,虽然很简单,但是可以帮助我们理解gradle是如何通过plugin完成很多复杂的工作的。
0x03 总结
gradle在定义一个构建工作的时候,主要是围绕着它的两个核心类,Project和Task,在这两个类的基础上,gradle定义了它的规则,例如它的生命周期,例如task的依赖图。在保证了这些核心功能后,所有外围的功能都是通过Plugin来进行的,这一点非常值得我们学习。同时,task的依赖图这个功能也是gradle的一个很强大的功能,它让我们有机会在任务执行之前就能对本次构建有一个全局的理解,同时也提供了接口让我们hook到task的执行过程中,完成一些非常酷炫的功能。