Gradle开发快速入门——DSL语法原理与常用API介绍

说明

本文主要从实现原理和代码层面介绍Gradle开发相关知识。关于本文中提到的、Gradle中的基本概念等内容,可参考

Android Gradle配置快速入门
http://www.paincker.com/android-gradle-basics

本文配套示例工程

https://github.com/jzj1993/GradleStudy

Groovy语言简介

Groovy语言的特性很多很复杂,这里先介绍一些Gradle脚本中常用到的Groovy语言基础。

Groovy是一种开源的脚本语言,在Java基础上进行了扩展,支持闭包、动态类型、元编程等特性,几乎兼容所有Java语法。因此很容易用Groovy实现领域特定语言(DSL, Domain-Specific Language)。

Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities, for the Java platform aimed at improving developer productivity thanks to a concise, familiar and easy to learn syntax. It integrates smoothly with any Java program, and immediately delivers to your application powerful features, including scripting capabilities, Domain-Specific Language authoring, runtime and compile-time meta-programming and functional programming.

官方网站、文档、源码

http://groovy-lang.org/
http://groovy-lang.org/documentation.html
https://github.com/apache/groovy

HelloWorld

和所有解释性语言一样,Groovy可以从源文件直接运行。而实际执行过程,也是先转换成class文件,再运行在JVM上。

# 安装groovy(Mac系统)
$ brew install groovy

# 创建Groovy脚本。Groovy语句末尾分号可省略。
$ echo 'println "hello groovy!"' > hello.groovy

# 运行Groovy脚本
$ groovy hello.groovy
hello groovy!

在Unix/Linux系统中,同样可以给Groovy脚本第一行加上shebang line,表示这个文件应该用groovy解释器执行。

#!/usr/bin/env groovy
println "hello groovy!"

GroovyObject,Script

Groovy提供了一个groovyc命令,可将groovy文件转化成class文件。再用JD-GUI或其他工具打开class,就可以看到Java代码。

创建一个groovy脚本如下,第一行打印字符串,第二行声明了一个类。

println "hello groovy!"
class Test {}

用groovyc对其编译,会生成两个class。

$ echo 'println "hello groovy!"\nclass Test {}' > hello.groovy
$ groovyc hello.groovy
$ ls
Test.class   hello.class  hello.groovy

分别反编译成Java代码如下。

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class Test implements GroovyObject {
    public Test() {
        Test this;
        CallSite[] arrayOfCallSite = $getCallSiteArray();
        MetaClass localMetaClass = $getStaticMetaClass();
        this.metaClass = localMetaClass;
    }
}
import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class hello extends Script {

    public hello() {
    }

    public hello(Binding context) {
        super(context);
    }

    public static void main(String... args) {
        CallSite[] arrayOfCallSite = $getCallSiteArray();
        arrayOfCallSite[0].call(InvokerHelper.class, hello.class, args);
    }

    public Object run() {
        CallSite[] arrayOfCallSite = $getCallSiteArray();
        return arrayOfCallSite[1].callCurrent(this, "hello groovy!");
        return null;
    }
}

可以总结一些特点:

  1. Groovy中的所有类最终都会实现groovy.lang.GroovyObject接口。
  2. 除了显式定义的类,在Groovy文件中的脚本代码,会生成一个继承自groovy.lang.Script的Java类,这个类也实现了GroovyObject接口。

动态类型

Groovy定义变量时:可以用Groovy风格的def声明,不指定类型;也可以兼容Java风格,指定变量类型;甚至还可以省略def或类型。

def t1 = 't1'
String t2 = 't2'
t3 = 't3'

Groovy风格定义的变量类型是动态的,编译成class时会自动转换成正确的Java类型。

def var = 'text'
println var
var = 5
println var + 1

可用Java实现类似效果如下。

Object o = "text";
System.out.println(String.valueOf(o));
o = 5;
System.out.println(String.valueOf(Integer.valueOf(o) + 1));

字符串

Groovy支持灵活的字符串语法,例如:

// 单引号字符串
def a = 'hello "world"'

// 双引号字符串
def b = "What's the weather like?"

// 用加号连接字符串,用等号对比字符串
assert 'ab' == 'a' + 'b'

// 三个单引号字符串,支持直接换行
def aMultilineString = '''line one
line two
line three'''

// 斜线字符串中,反斜线不需要转义,常用于正则表达式
def fooPattern = /.*foo.*/

// 双引号字符串支持用$嵌入变量
def name = 'Tom'
def greeting = "Hello ${name}"

// 如需函数调用,则$后表达式要加大括号
def pi = 3.14
def piString = "Pi = ${pi.toString()}"

闭包 (Closure)

闭包是一个变量,又是一个函数,类似C语言中的函数指针,或者Java中只有一个方法的接口(Runnable等)。

反编译class文件可以看出,Groovy闭包都会转化为继承groovy.lang.Closure的类。

闭包方法的参数用箭头定义,如果不特殊指定,则默认有一个it参数。

闭包方法的返回值可以用return显示指定,如果不指定则使用最后一条语句的值。

def c1 = {
    println 'hello'
}
def c2 = { a, b ->
    println a
    println b
}
def c3 = { int a, String b ->
    println a
    println b
}
def c4 = { ->
    println 'hello'
}
def c5 = {
    println it
}
def c6 = {
    return it + 1
}
def c7 = {
    it + 1
}

闭包调用可以用call,也可以直接像Java方法一样加括号调用。

def c = {
    println it
}
c.call('text1')
c('text2')

Java实现闭包效果:

abstract class MyClosure {
    abstract void call(Object o);
}
MyClosure c = new MyClosure() {
    @Override
    void call(Object o) {
        System.out.println(String.valueOf(o));
    }
};
c.call("text");

方法/闭包的定义与调用

Groovy中定义方法既可以用Groovy闭包风格,也可以用Java风格,参数/返回值类型也是可选的。

def f1 = { text ->
    println text
}
def f2(text) {
    println text
}
void f3(String text) {
    println text
}

注意函数定义不能这么写,会被视为函数调用。

f4(text) {
    println text
}

调用带参数的闭包/函数,通常可以省略括号,如果最后一个参数是闭包,还可以单独写在括号后面,如下。

println('hello')
println 'hello'
def func = { text, Closure closure ->
    println text
    closure.call()
}

func('1', {
    println '2'
})
func '3', {
    println '4'
}
func('5') {
    println '6'
}

delegate,owner,this

查看Closure类的源码,可以发现闭包中有delegate、owner、thisObject三个成员变量,调用闭包没有的属性/方法时,会尝试在这三个变量上调用。一般情况下:

  • this指向闭包外部的Object,指定义闭包的类。
  • owner指向闭包外部的Object/Closure,指直接包含闭包的类或闭包。
  • delegate默认和owner一致,指用于处理闭包属性/方法调用的第三方对象,可以修改。

在闭包构造时this和owner就已经确定并传入,是只读的。如果需要修改,可以用Closure.rehydrate()方法克隆新的闭包,同时设置其this和owner。

Closure还有一个resolveStrategy属性,有多种值(OWNER_FIRSTDELEGATE_FIRSTOWNER_ONLYDELEGATE_ONLYTO_SELF),默认为OWNER_FIRST,表示调用闭包没有定义的属性/方法时,先尝试从owner取,再尝试从delegate取。

Groovy代码示例:

class MyDelegate {
    def func = {
        println('hello')
    }
}
def c = {
    func()
}
c.delegate = new MyDelegate()
c.call()

用Java实现类似效果如下。

static boolean callMethod(Object o, String method, Object... args) {
    try {
        Method func = o.getClass().getDeclaredMethod(method);
        if (func != null) {
            func.invoke(o, args);
            return true;
        }
    } catch (Exception ignored) {
    }
    return false;
}
class MyDelegate {
    void func() {
        System.out.println("func");
    }
}
abstract class MyClosure {
    Object delegate;
    abstract void call();
}
MyClosure c = new MyClosure() {
    @Override
    void call() {
        if (!callMethod(this, "func")) {
            callMethod(delegate, "func");
        }
    }
};
c.delegate = new MyDelegate();
c.call();

属性与Getter、Setter

Groovy中对象的属性(通常即成员变量)可以直接用名字访问,实际上会调用getter和setter

// File没有absolutePath的成员变量,但有getAbsolutePath方法,可以直接当属性访问
println new File('text').absolutePath

// File没有setAbsolutePath方法,这句会报ReadOnlyPropertyException
new File('text').absolutePath = '1'

元编程 (MetaProgramming)

Groovy支持两类元编程:运行时和编译时。前者在代码运行阶段可以修改类的成员变量、方法,后者则只是在编译时进行(类似Java的注解生成代码)。这里只介绍运行时元编程。

http://groovy-lang.org/metaprogramming.html

The Groovy language supports two flavors of metaprogramming: runtime and compile-time. The first allows altering the class model and the behavior of a program at runtime while the second only occurs at compile-time.

Groovy拦截机制(Groovy interception mechanism)如下图。

image

示例一:propertyMissing和methodMissing

class Cls1 {
    def name = 'hello name'
    // 处理未定义的属性调用
    Object propertyMissing(String name) {
        return "missing property '$name'"
    }
    // 处理未定义的方法调用
    void methodMissing(String name, Object args) {
        println "call missing method '$name()'"
    }
}

def var1 = new Cls1()
println var1.name
println var1.myName
var1.missingFunc()

执行结果:

hello name
missing property 'myName'
call missing method 'missingFunc()'

示例二:MetaClass

MetaClass的支持是GroovyObject接口定义的,前面已经提到所有Groovy类都会实现这个接口。

MetaClass的作用类似闭包的delegate,当调用对象的属性/方法时,如果原始Class中没定义,会尝试在MetaClass上调用。

class Cls2 {
}

def var2 = new Cls2()
// 新增一个func方法
var2.metaClass.func = {
    println 'hello'
}
// 拦截所有方法调用,先输出一个log,再调用原始方法
var2.metaClass.invokeMethod = { String name, Object[] args ->
    println "invoke method '$name()'"
    def originMethod = var2.metaClass.getMetaMethod(name, args)
    if (originMethod != null) originMethod.invoke(var2, args)
}
var2.func()

执行结果:

invoke method 'func()'
hello

理解Gradle DSL语法——用Groovy实现自己的DSL

为了较好的理解Gradle DSL语法,本节先给出一段常见的gradle脚本,然后一边对其执行过程进行分析,一边用Groovy自己定义DSL,实现类似的语法效果。

** 注意这里只是简化版的示例,和Gradle的实际实现并不完全相同。**

完整示例工程如下。运行其中的DemoDSL.groovy即可看到执行结果(如果运行时提示找不到gradle文件,需要在IDEA/Android Studio的RunConfiguration中修改Working directory)。

https://github.com/jzj1993/GradleStudy

build.gradle脚本

build.gradle脚本中,可以给工程添加compiletestCompile两个Configuration,然后分别添加若干Dependency(包括远程模块和本地Project等类型依赖),如下。

// 给Project添加两个Configuration
configurations {
    compile
    testCompile
}

// 在不同的Configuration中添加依赖项
dependencies {
    add('compile', 'com.demo:module0')
    compile 'com.demo:module1' // 外部Module依赖
    testCompile project(path: ":library") // Project依赖
    compile('com.demo:module2') { // 支持Closure配置
        transitive = false
    }
    debugCompile 'com.demo:module3' // 没有这个Configuration,会报错
}

// 其他变式写法
dependencies.compile 'com.demo:module4'
project.dependencies.compile 'com.demo:module5'
project.dependencies {
    compile 'com.demo:module6'
}

build.gradle脚本的执行

Gradle会在一个Project对象上执行build.gradle脚本(Run build.gradle against a Project object),可以理解成build.gradle的代理对象是Project。

示例代码中:

  1. 创建Project对象。

  2. 通过Groovy加装build.gradle文件,并将其视为Groovy脚本编译生成Script类,创建出一个GroovyObject实例。

  3. 利用元编程,设置GroovyObject的代理对象为创建好的Project对象。

  4. 执行GroovyObject的run()方法,即执行build.gradle脚本。

class Utils {
    static GroovyObject loadAndCreateGroovyObject(File sourceFile) {
        Class groovyClass = new GroovyClassLoader().parseClass(sourceFile);
        return (GroovyObject) groovyClass.newInstance();
    }

    static void setDelegateForGroovyObject(GroovyObject obj, Object delegate) {
        obj.metaClass.getProperty = { String name ->
            def metaProperty = obj.metaClass.getMetaProperty(name)
            metaProperty != null ? metaProperty : delegate.getProperty(name)
        }
        obj.metaClass.invokeMethod = { String name, Object[] args ->
            def metaMethod = obj.metaClass.getMetaMethod(name, args)
            metaMethod != null ? metaMethod.invoke(obj, args) : delegate.invokeMethod(name, args)
        }
    }
}

class Project {
    // ...
}

// 创建Project对象
def project = new Project()
// 加载并实例化Groovy对象
def groovyObject = Utils.loadAndCreateGroovyObject(new File('./build.gradle'))
// 给groovyObject设置代理对象
Utils.setDelegateForGroovyObject(groovyObject, project)
// 执行脚本(Run "build.gradle" against the Project object)
groovyObject.invokeMethod("run", null)

Project, configuraitons, dependencies

  • configurations {}dependencies {}语句其实都是调用Project定义的方法,后面的大括号则是方法的闭包参数。

  • denpendencies.compile这种写法,这里的dependencies则是在调用Project定义的属性。

  • project.xxx,这里的project也是Project定义的属性,指向其自身。

class Utils {
    /**
     * 指定代理对象,运行闭包
     */
    static void runClosureAgainstObject(Closure closure, Object delegate) {
        Closure c = (Closure) closure.clone()
        c.delegate = delegate
        c.call()
    }
}
class Project {

    ConfigurationContainer configurations = new ConfigurationContainer()
    DependencyHandler dependencies = new DependencyHandler(this)
    Project project = this

    void configurations(Closure closure) {
        Utils.runClosureAgainstObject(closure, configurations)
    }

    void dependencies(Closure closure) {
        Utils.runClosureAgainstObject(closure, dependencies)
    }
}

ConfigrationContainer

configurations(Closure c)方法在ConfigurationContainer对象上执行闭包参数,compile和testCompile都是在调用ConfigurationContainerpropertyMissing()

class ConfigurationContainer {
    Map<String, Configuration> configurations = new HashMap<>()

    Object propertyMissing(String name) {
        println "add configuration '$name'"
        configurations.put(name, new Configuration())
    }
}

DependencyHandler

dependencies(Closure c)方法在DependencyHandler上执行闭包参数。

  • 闭包中的compile xxxtestCompile xxx等都是在调用DependencyHandlermethodMissing(),最后被转到调用add()方法,从而添加依赖。

  • 闭包中的project(path: ‘xxx‘)也是DependencyHandler定义的一个方法,参数为Map,返回一个Dependency对象。

  • 调用compile xxx {}时,最后可以传入一个闭包参数,用于配置transitive属性等操作。当DependencyHandler.add方法传入了closure,会执行Utils.configureObjectWithClosure(dependency, closure),用闭包配置Dependency,闭包中的transitive=false会覆盖Dependency中的对应属性。

class Utils {
    static void configureObjectWithClosure(Object object, Closure closure) {
        Closure c = (Closure) closure.clone()
        c.resolveStrategy = Closure.DELEGATE_FIRST;
        c.delegate = object
        c.call()
    }
}
class DependencyHandler {

    Project project;

    DependencyHandler(Project project) {
        this.project = project;
    }

    void add(String configuration, String dependencyNotation) {
        add(configuration, new Dependency(dependencyNotation), null)
    }

    void add(String configuration, String dependencyNotation, Closure closure) {
        add(configuration, new Dependency(dependencyNotation), closure)
    }

    void add(String configuration, Dependency dependency) {
        add(configuration, dependency, null)
    }

    void add(String configuration, Dependency dependency, Closure closure) {
        Configuration cfg = this.project.configurations.configurations.get(configuration)
        if (cfg != null) {
            if (closure != null) {
                Utils.configureObjectWithClosure(dependency, closure)
            }
            cfg.dependencies.add(dependency)
            println "add dependency '${dependency}' to '${configuration}'"
        } else {
            println "configuration '${configuration}' not found, dependency is '${dependency}'"
        }
    }

    Dependency project(Map<String, ?> notation) {
        return new Dependency("project(${notation.get("path")})")
    }

    Object methodMissing(String name, Object args) {
        Object[] arr = (Object[]) args;
        if (arr.length >= 1 && (arr[0] instanceof String || arr[0] instanceof Dependency)
                && this.project.configurations.configurations.get(name) != null) {
            Dependency dependency = arr[0] instanceof String ? new Dependency((String) arr[0]) : (Dependency) arr[0];
            if (arr.length == 1) {
                add(name, dependency)
            } else if (arr.length == 2 && arr[1] instanceof Closure) {
                add(name, dependency, (Closure) arr[1])
            }
        } else {
            println "method '${name}' with args '${args}' not found!"
        }
        return null
    }
}

Gradle构建过程

The Build Lifecycle

https://docs.gradle.org/3.3/userguide/build_lifecycle.html

Gradle构建过程通常分为三步。

A Gradle build has three distinct phases.

1、初始化阶段 (Initialization)

Gradle支持单个和多个工程的编译。在初始化阶段,Gradle判断需要参与编译的工程,为每个工程创建一个Project对象。

在这个阶段,Gradle会创建Settings对象,并在其上执行settings.gradle脚本,建立工程之间的层次关系。

Gradle supports single and multi-project builds. During the initialization phase, Gradle determines which projects are going to take part in the build, and creates a Project instance for each of these projects.

2、配置阶段 (Configuration)

在这个阶段,Gradle会分别在每个Project对象上执行对应的build.gradle脚本,对Project进行配置。

During this phase the project objects are configured. The build scripts of all projects which are part of the build are executed. Gradle 1.4 introduced an incubating opt-in feature called configuration on demand. In this mode, Gradle configures only relevant projects (see the section called “Configuration on demand”).

3、执行阶段 (Execution)

在执行阶段,Gradle会判断配置阶段创建的哪些Task需要被执行,然后执行选中的每个Task。

Gradle determines the subset of the tasks, created and configured during the configuration phase, to be executed. The subset is determined by the task name arguments passed to the gradle command and the current directory. Gradle then executes each of the selected tasks.

Gradle源码查看

Gradle是开源的,学习Gradle最好的资料就是Gradle官方文档和Gradle源码。这里介绍查看Gradle源码比较好的方法。

查看gradle脚本调用的API

在Android Studio/IDEA的Gradle工程中,可以光标选中gradle脚本语句,用Navigate - Declaration菜单或快捷键,跳转到其调用的Gradle API方法。

例如选中build.gradle中的dependencies,可跳转到Project.dependencies(Closure)方法。

这个操作需要IDE支持,有时不一定管用;另外对于动态添加的方法,也不能正常跳转。

查看完整的源码

为方便阅读,可用Android Studio或IDEA关联源码(推荐IDEA Ultimate版),具体操作如下。

  1. 克隆并用IDEA打开下面的工程。打开时Gradle选Use default gradle wrapper,同步工程。项目配置了依赖本地gradle库compile gradleApi(),同步完成后一般会下载并关联gradle的jar包。

    https://github.com/jzj1993/GradleStudy

    如果打开工程没有正确选择Gradle,可以在Preferences - Build Execution Deployment - Build Tools - Gradle中设置。

  2. 通过搜索打开任意Gradle类,例如org.gradle.api.Project(或在左侧Project窗口中展开External Libraries - gradle-api-xx.jar打开)。

    • 如果IDEA已经关联了gradle-xx-all.zip,此时就能看到源码。
    • 如果关联的是gradle-xx-bin.zip,此时只能看到class反编译的结果,点击提示栏的Choose Source,选择gradle-3.3-all.zip关联源码即可(Mac系统中默认存放在~/.gradle/wrapper/dists/,如果没有可以自行从官网下载)。

常用API

本节介绍一些Gradle开发最常用的API,并通过实例介绍其使用。

官方DSL Reference

https://docs.gradle.org/current/dsl/index.html

org.gradle.api.Project

Project对象是Gradle中最核心的API,通过Project对象可以访问所有Gradle特性。

This interface is the main API you use to interact with Gradle from your build file. From a Project, you have programmatic access to all of Gradle's features.

Project与build.gradle

Project对象和build.gradle文件一一对应。在Gradle构建时,会先创建Settings实例并在其上执行settings.gradle;再通过Settings对象定义的Project层级,创建若干个Project实例,并分别在其上执行对应的build.gradle

Lifecycle
There is a one-to-one relationship between a Project and a build.gradle file. During build initialisation, Gradle assembles a Project object for each project which is to participate in the build, as follows:

  • Create a org.gradle.api.initialization.Settings instance for the build.
  • Evaluate the settings.gradle script, if present, against the org.gradle.api.initialization.Settings object to configure it.
  • Use the configured org.gradle.api.initialization.Settings object to create the hierarchy of Project instances.
  • Finally, evaluate each Project by executing its build.gradle file, if present, against the project. The projects are evaluated in breadth-wise order, such that a project is evaluated before its child projects. This order can be overridden by calling evaluationDependsOnChildren() or by adding an explicit evaluation dependency using evaluationDependsOn(String).

Extra属性

Project有一个Extra属性,可通过ext前缀在其中定义属性,定义好后可以不加ext前缀直接访问。

All extra properties must be defined through the "ext" namespace. Once an extra property has been defined, it is available directly on the owning object (in the below case the Project, Task, and sub-projects respectively) and can be read and updated. Only the initial declaration that needs to be done via the namespace.

示例代码如下。

project.ext.prop1 = "foo"
task doStuff {
    ext.prop2 = "bar"
}

ext.isSnapshot = version.endsWith("-SNAPSHOT")
if (isSnapshot) {
    // do snapshot stuff
}

Project的属性/方法调用

build.gradle中调用属性,或调用Project.property(java.lang.String)方法时,会按顺序从以下范围查找:

  1. Project自身定义的属性
  2. Project的Extra属性
  3. 插件添加的Extension属性
  4. 插件添加的Convension属性
  5. Project中Task的名字
  6. 从父Project继承的属性,一直递归到RootProject

build.gradle中调用方法时,会按顺序从以下范围查找:

  1. Project自身定义的方法
  2. build.gradle脚本定义的方法
  3. 插件添加类型为Action或Closure的Extension
  4. 插件添加的Convension方法
  5. Project中Task的名字都会创建一个对应方法
  6. 从父Project继承的方法,一直递归到RootProject
  7. Project中为Closure类型的属性可以作为方法调用

常用API

Project继承了PluginAwareExtensionAware,分别用于支持Plugin和Extension方法。部分常用API如下。

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {

    Project getRootProject();

    File getRootDir();

    File getBuildDir();

    void allprojects(Closure configureClosure);

    ScriptHandler getBuildscript();

    void buildscript(Closure configureClosure);

    RepositoryHandler getRepositories();

    void repositories(Closure configureClosure);

    ConfigurationContainer getConfigurations();

    void configurations(Closure configureClosure);

    DependencyHandler getDependencies();

    void dependencies(Closure configureClosure);

    ConfigurableFileCollection files(Object... paths);

    ConfigurableFileTree fileTree(Object baseDir);

    Convention getConvention();

    ExtensionContainer getExtensions();

    Task task(String name) throws InvalidUserDataException;

    Task task(String name, Closure configureClosure);

    void afterEvaluate(Closure closure);

    // ...
}

常用API示例(以下脚本均写在build.gradle中):

// 配置Gradle插件,闭包参数会在ScriptHandler上执行
buildscript {
    // ...
}

// 配置所有工程,闭包参数会分别在每个Project上执行
allprojects {
    // ...
}

// 配置使用的仓库,闭包参数会在RepositoryHandler上执行
repositories {
    // ...
}

// 配置依赖项,闭包参数会在DependencyHandler上执行。
// files和fileTree也是Project提供的API,
// 而project则是DependencyHandler提供的API。
dependencies {
    compile files('hibernate.jar', 'libs/spring.jar')
    compile fileTree('libs')
    compile project(path: ':library')
    // ...
}

// 在当前Project配置完成后,闭包会被执行
afterEvaluate {
    println "Project '$name' has been evaluated!"
}

// 在RootProject配置完成后,闭包会被执行
rootProject.afterEvaluate {
    println "RootProject '$name' has been evaluated!"
}

org.gradle.api.invocation.Gradle

Gradle对象表示一次Gradle调用,通过Project.getGradle()可以获取这个对象。在一次构建过程中只有一个Gradle对象,可在其上保存一些全局配置参数,包括StartParameter等。

Represents an invocation of Gradle.
You can obtain a Gradle instance by calling Project.getGradle().

public interface Gradle extends PluginAware {

    String getGradleVersion();

    File getGradleUserHomeDir();

    File getGradleHomeDir();

    Gradle getParent();

    Project getRootProject() throws IllegalStateException;

    void rootProject(Action< ? super Project> action);

    void allprojects(Action< ? super Project> action);

    TaskExecutionGraph getTaskGraph();

    StartParameter getStartParameter();

    ProjectEvaluationListener addProjectEvaluationListener(ProjectEvaluationListener listener);

    void removeProjectEvaluationListener(ProjectEvaluationListener listener);

    void beforeProject(Closure closure);

    void afterProject(Closure closure);

    void buildStarted(Closure closure);

    void settingsEvaluated(Closure closure);

    void projectsLoaded(Closure closure);

    void projectsEvaluated(Closure closure);

    void buildFinished(Closure closure);

    void addBuildListener(BuildListener buildListener);

    public void addListener(Object listener);

    public void removeListener(Object listener);

    public void useLogger(Object logger);

    Gradle getGradle();
}

org.gradle.api.initialization.Settings

Settings对象主要用于配置Project的层级结构。

Settings对象和settings.gradle文件一一对应。Gradle构建的第一步,就是创建Settings对象并其上执行settings.gradle脚本。

Declares the configuration required to instantiate and configure the hierarchy of org.gradle.api.Project instances which are to participate in a build.

There is a one-to-one correspondence between a Settings instance and a settings.gradle settings file. Before Gradle assembles the projects for a build, it creates a Settings instance and executes the settings file against it.

Settings的部分API如下。

public interface Settings extends PluginAware {

    String DEFAULT_SETTINGS_FILE = "settings.gradle";

    void include(String[] projectPaths);

    void includeFlat(String[] projectNames);

    Settings getSettings();

    File getSettingsDir();

    File getRootDir();

    ProjectDescriptor getRootProject();

    ProjectDescriptor project(String path) throws UnknownProjectException;

    ProjectDescriptor findProject(String path);

    ProjectDescriptor project(File projectDir) throws UnknownProjectException;

    ProjectDescriptor findProject(File projectDir);

    StartParameter getStartParameter();

    Gradle getGradle();
}

常用API示例:

  1. include()可以配置包含Project,例如include ':app', ':library'

  2. project()可获取ProjectDescriptor从而做一些配置,例如经常会配置Gradle依赖本地Library工程的路径:

    include ':img:library'
    project(':img:library').projectDir = new File('../../img/library')
    

org.gradle.api.Task

Task

Task也是Gradle中很重要的API。Task代表构建过程中的一个原子操作,例如编译classes文件或生成JavaDoc。

每个Task属于一个Project。每个Task都有一个名字。所属Project名+Task名可组成唯一的完整名(fully qualified path),例如:app:assemble

A Task represents a single atomic piece of work for a build, such as compiling classes or generating javadoc.

Each task belongs to a Project.

Each task has a name, which can be used to refer to the task within its owning project, and a fully qualified path, which is unique across all tasks in all projects. The path is the concatenation of the owning project's path and the task's name. Path elements are separated using the : character.

Action

每个Task包含一个Action序列,并在Task执行时按先后顺序执行。通过Task的doFirst/doLast方法可以往Action序列的头部/末尾添加Action,支持Action或闭包(闭包会被转换成Action对象)。

A Task is made up of a sequence of Action objects. When the task is executed, each of the actions is executed in turn, by calling Action.execute. You can add actions to a task by calling doFirst(Action) or doLast(Action).

Groovy closures can also be used to provide a task action. When the action is executed, the closure is called with the task as parameter. You can add action closures to a task by calling doFirst(Closure) or doLast(Closure).

Task依赖和排序

每个Task可以依赖其他Task,执行Task时会先执行其依赖的Task,通过dependsOn可设置依赖。每个Task还可以设置在其他Task之前、之后执行,一般可通过mustRunAfter设置。

A task may have dependencies on other tasks or might be scheduled to always run after another task. Gradle ensures that all task dependencies and ordering rules are honored when executing tasks, so that the task is executed after all of its dependencies and any "must run after" tasks have been executed.

例如下面的配置,执行A时一定会先执行B;执行A不一定会执行C;当A、C都要执行时一定先执行C。

taskA.dependsOn(taskB)
taskA.mustRunAfter(taskC)

除了mustRunAfter,还有个shouldRunAfter要求宽松一些,大部分情况下两者效果相同,特殊情况下有差异,具体可参考官方文档:
https://docs.gradle.org/3.3/userguide/more_about_tasks.html

常用API

Task的部分常用API如下:

public interface Task extends Comparable<Task>, ExtensionAware {

    String getName();

    Project getProject();

    TaskDependency getTaskDependencies();

    Task dependsOn(Object... paths);

    String getPath();

    Task doFirst(Action< ? super Task> action);

    Task doFirst(Closure action);

    Task doLast(Action< ? super Task> action);

    Task doLast(Closure action);

    Task configure(Closure configureClosure);

    Task mustRunAfter(Object... paths);

    TaskDependency shouldRunAfter(Object... paths);

    // ...
}

Task创建

注:Gradle不推荐使用task hello << { ... }的方式定义Task,并会在后续版本删除,因此这里不做介绍。

build.gradle中创建Task,最常见写法如下。task(xxx)是Project提供的API,最终调用了TaskContainer的create方法。可接收参数包括:

  • Task名称(必选)
  • Map<String, ?>类型配置(可选)
  • 闭包配置(可选)
task hello(dependsOn: clean) {
    doLast {
        println 'hello'
    }
}

也可以直接调用TaskContainer创建Task,Project中的tasks属性即为TaskContainer对象。

tasks.create('hello')

Task创建后会在Project上添加一个同名方法,调用这个方法可以配置Task。

task hello

hello {
    doLast {
        println 'hello'
    }
}

Task的type属性,带参数的Task

还可以用类实现Task,创建Task时指定type为这个class即可,定义Task的类通常继承自DefaultTask。下列示例代码中给Task定义了一个名为name的参数。

import org.gradle.api.internal.tasks.options.Option

class HelloTask extends DefaultTask {

    String personName = '';

    HelloTask() {
        doLast {
            println "Hello " + personName
        }
    }

    @Option(description = "set person name", option = "name")
    def setMessage(String name) {
        this.personName = name;
    }
}

task hello(type: HelloTask)

命令行中执行效果:

$ ./gradlew hello --name Tom
:hello
Hello Tom

BUILD SUCCESSFUL

Total time: 0.889 secs

org.gradle.api.plugins.PluginAware

前面介绍的GradleSettingsProject等接口均继承了PluginAware接口,PluginAware主要定义了插件相关API。

public interface PluginAware {

    PluginContainer getPlugins();

    void apply(Closure closure);

    void apply(Action< ? super ObjectConfigurationAction> action);

    void apply(Map<String, ?> options);

    PluginManager getPluginManager();
}

应用插件

apply plugin: 'java',表示应用Java插件。这个语句调用了apply()方法,后面的plugin: 'java'是一个Map类型参数。

apply plugin: MyClass表示应用指定class实现的插件,将在后面的Plugin中介绍。

执行其他Gradle脚本

当一个gradle脚本(例如build.gradle)中的代码较多时,可以拆分成多个文件。

  1. 新写一个gradle文件例如my_script.gradle,把拆分出来的代码放在这个文件中。

  2. build.gradle中通过apply from: 'my_script.gradle'apply from: new File('xxx/my_script.gradle'),调用当前目录或指定路径的脚本文件。

  3. 新的my_script.gradle在被执行时,其代理对象和调用它的build.gradle一致,即Project对象。

  4. 注意,在新的my_script.gradle中定义的属性/方法,在build.gradle中不能访问。因为每个gradle文件最后都会被编译成单独的Groovy Script,这些属性/方法只是Script类中的成员。

  5. 如果要在不同的脚本文件之间传递数据,可以利用Gradle/Settings/Project对象的ext属性实现。

org.gradle.api.Plugin

Plugin用于定义插件。Gradle提供了完整的API框架,而很多工作实际是由插件实现的。Gradle内置了Java、Groovy等几种基础插件,也可以自定义插件。

Plugin接口很简单,只有一个apply方法。

public interface Plugin<T> {
    /**
     * Apply this plugin to the given target object.
     *
     * @param target The target object
     */
    void apply(T target);
}

简易插件开发

下面的示例代码实现了HelloPlugin的简易插件,代码可直接写在build.gradle中。

插件在apply(Project)方法里,给Project创建了一个名为hello的Extension和一个名为welcome的Task;Task执行时读取Extension并打印字符串。

build.gradle执行到apply plugin: HelloPlugin时,HelloPlugin.apply(Project)方法被执行,从而Project有了hello的Extension,于是后面可以调用hello {}对插件进行配置。

class HelloExtension {
    Boolean enable = true
    String text = ''
}

class HelloPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        project.extensions.create('hello', HelloExtension)
        project.task('welcome') {
            doLast {
                HelloExtension ext = project.extensions.hello;
                println ext.enable ? "Hello ${ext.text}!" : 'HelloPlugin is disabled.'
            }
        }
    }
}

apply plugin: HelloPlugin

hello {
    enable = true
    text = 'Gradle'
}

在命令行中执行结果如下。

$ ./gradlew welcome
:welcome
Hello Gradle!

BUILD SUCCESSFUL

Total time: 0.917 secs

独立工程开发插件

对于类似前面示例的简单插件,代码可以直接写在工程的gradle脚本中。而对于需要应用到很多工程的插件,或复杂的插件(例如Android插件),在独立的工程中开发是一个更好的选择。

独立工程开发时,可以新建基于Gradle的Groovy工程。

完整代码可参考示例工程中的plugin模块:

https://github.com/jzj1993/GradleStudy

插件工程的文件结构如下:

.
├── build.gradle
└── src
    └── main
        ├── groovy
        │   └── com
        │       └── paincker
        │           └── gradle
        │               ├── GreetingExtension.groovy
        │               └── GreetingPlugin.groovy
        └── resources
            └── META-INF
                └── gradle-plugins
                    └── greeting.properties

其中build.gradle内容如下。

apply plugin: 'groovy'

repositories {
    mavenCentral()
}

dependencies {
    compile localGroovy() // Groovy支持(本地)
    compile gradleApi() // GradleAPI支持
}

src/main/groovy目录下,可编写插件源码,java、groovy均可。

src/main/resources/META-INF/gradle-plugins目录下,可以创建若干properties文件:

  • 文件名即为插件名,例如greeting.properties,则插件名为greeting

  • 文件内容如下,用implementation-class指定插件的实现类

    implementation-class=com.paincker.gradle.GreetingPlugin
    

通过gradle的assemble命令将插件打包,默认输出到build/libs/plugin.jar

$ ./gradlew clean :plugin:assemble

独立Gradle插件的使用

在要使用插件的Gradle工程的buildscript.dependencies {}中,可引入Gradle插件包。

  • 可将前面生成的plugin.jar直接复制到工程根目录,通过classpath files('plugin.jar')引入。

  • 也可以将插件jar包发布到Maven仓库,通过classpath 'com.xxx:greeting:1.0'的形式引入。

引入插件包后,用apply plugin: 'greeting'应用插件,greeting即为前面通过properties文件名指定的插件名字。

buildscript {
    dependencies {
        classpath files('plugin.jar')
    }
}

apply plugin: 'greeting'

greet {
    enable = true
    text = 'Plugin'
}

org.gradle.api.logging.Logger

Logger用于输出Gradle的Log。在命令行执行gradle任务则Log输出到命令行,在Android Studio中执行则输出到Gradle Console。

Logger提供以下接口。

public interface Logger extends org.slf4j.Logger {

    boolean isLifecycleEnabled();

    void debug(String message, Object... objects);

    void lifecycle(String message);

    void lifecycle(String message, Object... objects);

    void lifecycle(String message, Throwable throwable);

    boolean isQuietEnabled();

    void quiet(String message);

    void quiet(String message, Object... objects);

    void info(String message, Object... objects);

    void quiet(String message, Throwable throwable);

    boolean isEnabled(LogLevel level);

    void log(LogLevel level, String message);

    void log(LogLevel level, String message, Object... objects);

    void log(LogLevel level, String message, Throwable throwable);
}

其中LogLevel表示Log等级,有以下值。可通过StartParameter控制Gradle要输出的Log等级。

public enum LogLevel {
    DEBUG,
    INFO,
    LIFECYCLE,
    WARN,
    QUIET,
    ERROR
}

可通过以下方式获取Logger对象:

  • org.gradle.api.logging.Logging.getLogger(Class)
  • org.gradle.api.logging.Logging.getLogger(String)
  • org.gradle.api.Project.getLogger()
  • org.gradle.api.Task.getLogger()
  • org.gradle.api.Script.getLogger()

本文首发自我的个人博客 http://www.paincker.com/gradle-develop-basics

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

推荐阅读更多精彩内容

  • 说明 本文主要介绍和Gradle关系密切、相对不容易理解的配置,偏重概念介绍。部分内容是Android特有的(例如...
    jzj1993阅读 15,573评论 1 62
  • Android Studio作为Android应用开发的官方IDE,默认使用Gradle作为构建工具,所以对于An...
    feil0n9wan9阅读 1,655评论 1 6
  • Gradle简介 Gradle是一个构建工具,同时它也是一个编程框架。 当你把Gradle当构建工具看的时候,我们...
    Rangethan阅读 2,308评论 1 8
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 人或老亦,初心尚存!普天之下,又有几人。
    蜀途之个人专属阅读 282评论 0 1