说明
本文主要从实现原理和代码层面介绍Gradle开发相关知识。关于本文中提到的、Gradle中的基本概念等内容,可参考
Android Gradle配置快速入门 http://www.paincker.com/android-gradle-basics
本文配套示例工程
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上。
1 | 安装groovy(Mac系统) |
在Unix/Linux系统中,同样可以给Groovy脚本第一行加上shebang line
,表示这个文件应该用groovy解释器执行。
1 |
|
GroovyObject,Script
Groovy提供了一个groovyc命令,可将groovy文件转化成class文件。再用JD-GUI或其他工具打开class,就可以看到Java代码。
创建一个groovy脚本如下,第一行打印字符串,第二行声明了一个类。
1 | println "hello groovy!" |
用groovyc对其编译,会生成两个class。
1 | echo 'println "hello groovy!"\nclass Test {}' > hello.groovy |
分别反编译成Java代码如下。
1 | import groovy.lang.GroovyObject; |
1 | import groovy.lang.Binding; |
可以总结一些特点:
- Groovy中的所有类最终都会实现
groovy.lang.GroovyObject
接口。 - 除了显式定义的类,在Groovy文件中的脚本代码,会生成一个继承自
groovy.lang.Script
的Java类,这个类也实现了GroovyObject接口。
动态类型
Groovy定义变量时:可以用Groovy风格的def声明,不指定类型;也可以兼容Java风格,指定变量类型;甚至还可以省略def或类型。
1 | def t1 = 't1' |
Groovy风格定义的变量类型是动态的,编译成class时会自动转换成正确的Java类型。
1 | def var = 'text' |
可用Java实现类似效果如下。
1 | Object o = "text"; |
字符串
Groovy支持灵活的字符串语法,例如:
1 | // 单引号字符串 |
闭包 (Closure)
闭包是一个变量,又是一个函数,类似C语言中的函数指针,或者Java中只有一个方法的接口(Runnable等)。
反编译class文件可以看出,Groovy闭包都会转化为继承groovy.lang.Closure
的类。
闭包方法的参数用箭头定义,如果不特殊指定,则默认有一个it
参数。
闭包方法的返回值可以用return显示指定,如果不指定则使用最后一条语句的值。
1 | def c1 = { |
闭包调用可以用call,也可以直接像Java方法一样加括号调用。
1 | def c = { |
Java实现闭包效果:
1 | abstract class MyClosure { |
方法/闭包的定义与调用
Groovy中定义方法既可以用Groovy闭包风格,也可以用Java风格,参数/返回值类型也是可选的。
1 | def f1 = { text -> |
注意函数定义不能这么写,会被视为函数调用。
1 | f4(text) { |
调用带参数的闭包/函数,通常可以省略括号,如果最后一个参数是闭包,还可以单独写在括号后面,如下。
1 | println('hello') |
1 | def func = { text, Closure closure -> |
delegate,owner,this
查看Closure类的源码,可以发现闭包中有delegate、owner、thisObject三个成员变量,调用闭包没有的属性/方法时,会尝试在这三个变量上调用。一般情况下:
- this指向闭包外部的Object,指定义闭包的类。
- owner指向闭包外部的Object/Closure,指直接包含闭包的类或闭包。
- delegate默认和owner一致,指用于处理闭包属性/方法调用的第三方对象,可以修改。
在闭包构造时this和owner就已经确定并传入,是只读的。如果需要修改,可以用Closure.rehydrate()
方法克隆新的闭包,同时设置其this和owner。
Closure还有一个resolveStrategy属性,有多种值(OWNER_FIRST
、DELEGATE_FIRST
、OWNER_ONLY
、DELEGATE_ONLY
、TO_SELF
),默认为OWNER_FIRST
,表示调用闭包没有定义的属性/方法时,先尝试从owner取,再尝试从delegate取。
Groovy代码示例:
1 | class MyDelegate { |
用Java实现类似效果如下。
1 | static boolean callMethod(Object o, String method, Object... args) { |
属性与Getter、Setter
Groovy中对象的属性(通常即成员变量)可以直接用名字访问,实际上会调用getter和setter
1 | // File没有absolutePath的成员变量,但有getAbsolutePath方法,可以直接当属性访问 |
元编程 (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)如下图。
示例一:propertyMissing和methodMissing
1 | class Cls1 { |
执行结果:
1 | hello name |
示例二:MetaClass
MetaClass的支持是GroovyObject接口定义的,前面已经提到所有Groovy类都会实现这个接口。
MetaClass的作用类似闭包的delegate,当调用对象的属性/方法时,如果原始Class中没定义,会尝试在MetaClass上调用。
1 | class Cls2 { |
执行结果:
1 | invoke method 'func()' |
理解Gradle DSL语法——用Groovy实现自己的DSL
为了较好的理解Gradle DSL语法,本节先给出一段常见的gradle脚本,然后一边对其执行过程进行分析,一边用Groovy自己定义DSL,实现类似的语法效果。
** 注意这里只是简化版的示例,和Gradle的实际实现并不完全相同。**
完整示例工程如下。运行其中的DemoDSL.groovy
即可看到执行结果(如果运行时提示找不到gradle文件,需要在IDEA/Android Studio的RunConfiguration中修改Working directory)。
build.gradle
脚本
在build.gradle
脚本中,可以给工程添加compile
和testCompile
两个Configuration
,然后分别添加若干Dependency
(包括远程模块和本地Project等类型依赖),如下。
1 | // 给Project添加两个Configuration |
build.gradle
脚本的执行
Gradle会在一个Project对象上执行build.gradle
脚本(Run build.gradle against a Project object
),可以理解成build.gradle
的代理对象是Project。
示例代码中:
- 创建Project对象。
- 通过Groovy加装
build.gradle
文件,并将其视为Groovy脚本编译生成Script类,创建出一个GroovyObject实例。 - 利用元编程,设置GroovyObject的代理对象为创建好的Project对象。
- 执行GroovyObject的
run()
方法,即执行build.gradle
脚本。
1 | class Utils { |
Project, configuraitons, dependencies
configurations {}
和dependencies {}
语句其实都是调用Project定义的方法,后面的大括号则是方法的闭包参数。denpendencies.compile
这种写法,这里的dependencies
则是在调用Project定义的属性。project.xxx
,这里的project
也是Project定义的属性,指向其自身。
1 | class Utils { |
ConfigrationContainer
configurations(Closure c)
方法在ConfigurationContainer
对象上执行闭包参数,compile和testCompile都是在调用ConfigurationContainer
的propertyMissing()
。
1 | class ConfigurationContainer { |
DependencyHandler
dependencies(Closure c)
方法在DependencyHandler
上执行闭包参数。
- 闭包中的
compile xxx
、testCompile xxx
等都是在调用DependencyHandler
的methodMissing()
,最后被转到调用add()
方法,从而添加依赖。 - 闭包中的
project(path: ‘xxx‘)
也是DependencyHandler
定义的一个方法,参数为Map,返回一个Dependency
对象。 - 调用
compile xxx {}
时,最后可以传入一个闭包参数,用于配置transitive
属性等操作。当DependencyHandler.add
方法传入了closure,会执行Utils.configureObjectWithClosure(dependency, closure)
,用闭包配置Dependency
,闭包中的transitive=false
会覆盖Dependency
中的对应属性。
1 | class Utils { |
Gradle构建过程
The Build Lifecycle
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支持,有时不一定管用;另外对于动态添加的方法,也不能正常跳转。
查看完整的源码
-
Gradle将整个源码分为若干个模块分别发布到了Maven仓库,可参考 https://maven-repository.com/artifact/org.gradle
-
官网 https://gradle.org/releases/ 可下载Gradle的jar包,分两种(
xx
表示版本号):gradle-xx-bin.zip
,包含所有class的jar包。gradle-xx-all.zip
,包含所有class和源码等内容。
为方便阅读,可用Android Studio或IDEA关联源码(推荐IDEA Ultimate版),具体操作如下。
-
克隆并用IDEA打开下面的工程。打开时Gradle选
Use default gradle wrapper
,同步工程。项目配置了依赖本地gradle库compile gradleApi()
,同步完成后一般会下载并关联gradle的jar包。如果打开工程没有正确选择Gradle,可以在
Preferences
-Build Execution Deployment
-Build Tools
-Gradle
中设置。 -
通过搜索打开任意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/
,如果没有可以自行从官网下载)。
- 如果IDEA已经关联了
常用API
本节介绍一些Gradle开发最常用的API,并通过实例介绍其使用。
官方DSL Reference
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.
示例代码如下。
1 | project.ext.prop1 = "foo" |
Project的属性/方法调用
在build.gradle
中调用属性,或调用Project.property(java.lang.String)
方法时,会按顺序从以下范围查找:
- Project自身定义的属性
- Project的Extra属性
- 插件添加的Extension属性
- 插件添加的Convension属性
- Project中Task的名字
- 从父Project继承的属性,一直递归到RootProject
在build.gradle
中调用方法时,会按顺序从以下范围查找:
- Project自身定义的方法
- build.gradle脚本定义的方法
- 插件添加类型为Action或Closure的Extension
- 插件添加的Convension方法
- Project中Task的名字都会创建一个对应方法
- 从父Project继承的方法,一直递归到RootProject
- Project中为Closure类型的属性可以作为方法调用
常用API
Project
继承了PluginAware
、ExtensionAware
,分别用于支持Plugin和Extension方法。部分常用API如下。
1 | public interface Project extends Comparable<Project>, ExtensionAware, PluginAware { |
常用API示例(以下脚本均写在build.gradle
中):
1 | // 配置Gradle插件,闭包参数会在ScriptHandler上执行 |
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().
1 | public interface Gradle extends PluginAware { |
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如下。
1 | public interface Settings extends PluginAware { |
常用API示例:
-
include()
可以配置包含Project,例如include ':app', ':library'
-
project()
可获取ProjectDescriptor从而做一些配置,例如经常会配置Gradle依赖本地Library工程的路径:1
2include ':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。
1 | taskA.dependsOn(taskB) |
除了mustRunAfter,还有个shouldRunAfter要求宽松一些,大部分情况下两者效果相同,特殊情况下有差异,具体可参考官方文档: https://docs.gradle.org/3.3/userguide/more_about_tasks.html
常用API
Task的部分常用API如下:
1 | public interface Task extends Comparable<Task>, ExtensionAware { |
Task创建
注:Gradle不推荐使用
task hello << { ... }
的方式定义Task,并会在后续版本删除,因此这里不做介绍。
在build.gradle
中创建Task,最常见写法如下。task(xxx)
是Project提供的API,最终调用了TaskContainer的create方法。可接收参数包括:
- Task名称(必选)
Map<String, ?>
类型配置(可选)- 闭包配置(可选)
1 | task hello(dependsOn: clean) { |
也可以直接调用TaskContainer创建Task,Project中的tasks属性即为TaskContainer对象。
1 | tasks.create('hello') |
Task创建后会在Project上添加一个同名方法,调用这个方法可以配置Task。
1 | task hello |
Task的type属性,带参数的Task
还可以用类实现Task,创建Task时指定type为这个class即可,定义Task的类通常继承自DefaultTask。下列示例代码中给Task定义了一个名为name
的参数。
1 | import org.gradle.api.internal.tasks.options.Option |
命令行中执行效果:
1 | ./gradlew hello --name Tom |
org.gradle.api.plugins.PluginAware
前面介绍的Gradle
、Settings
、Project
等接口均继承了PluginAware
接口,PluginAware
主要定义了插件相关API。
1 | public interface PluginAware { |
应用插件
apply plugin: 'java'
,表示应用Java插件。这个语句调用了apply()
方法,后面的plugin: 'java'
是一个Map类型参数。
apply plugin: MyClass
表示应用指定class实现的插件,将在后面的Plugin
中介绍。
执行其他Gradle脚本
当一个gradle脚本(例如build.gradle
)中的代码较多时,可以拆分成多个文件。
- 新写一个gradle文件例如
my_script.gradle
,把拆分出来的代码放在这个文件中。 - 在
build.gradle
中通过apply from: 'my_script.gradle'
或apply from: new File('xxx/my_script.gradle')
,调用当前目录或指定路径的脚本文件。 - 新的
my_script.gradle
在被执行时,其代理对象和调用它的build.gradle
一致,即Project对象。 - 注意,在新的
my_script.gradle
中定义的属性/方法,在build.gradle
中不能访问。因为每个gradle文件最后都会被编译成单独的Groovy Script,这些属性/方法只是Script类中的成员。 - 如果要在不同的脚本文件之间传递数据,可以利用Gradle/Settings/Project对象的ext属性实现。
org.gradle.api.Plugin
Plugin
用于定义插件。Gradle提供了完整的API框架,而很多工作实际是由插件实现的。Gradle内置了Java、Groovy等几种基础插件,也可以自定义插件。
Plugin
接口很简单,只有一个apply方法。
1 | public interface Plugin<T> { |
简易插件开发
下面的示例代码实现了HelloPlugin的简易插件,代码可直接写在build.gradle
中。
插件在apply(Project)
方法里,给Project创建了一个名为hello
的Extension和一个名为welcome
的Task;Task执行时读取Extension并打印字符串。
在build.gradle
执行到apply plugin: HelloPlugin
时,HelloPlugin.apply(Project)
方法被执行,从而Project有了hello
的Extension,于是后面可以调用hello {}
对插件进行配置。
1 | class HelloExtension { |
在命令行中执行结果如下。
1 | ./gradlew welcome |
独立工程开发插件
对于类似前面示例的简单插件,代码可以直接写在工程的gradle脚本中。而对于需要应用到很多工程的插件,或复杂的插件(例如Android插件),在独立的工程中开发是一个更好的选择。
独立工程开发时,可以新建基于Gradle的Groovy工程。
完整代码可参考示例工程中的plugin模块:
插件工程的文件结构如下:
1 | . |
其中build.gradle
内容如下。
1 | apply plugin: 'groovy' |
在src/main/groovy
目录下,可编写插件源码,java、groovy均可。
在src/main/resources/META-INF/gradle-plugins
目录下,可以创建若干properties文件:
-
文件名即为插件名,例如
greeting.properties
,则插件名为greeting
-
文件内容如下,用implementation-class指定插件的实现类
1
implementation-class=com.paincker.gradle.GreetingPlugin
通过gradle的assemble命令将插件打包,默认输出到build/libs/plugin.jar
。
1 | ./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
文件名指定的插件名字。
1 | buildscript { |
org.gradle.api.logging.Logger
Logger用于输出Gradle的Log。在命令行执行gradle任务则Log输出到命令行,在Android Studio中执行则输出到Gradle Console。
Logger提供以下接口。
1 | public interface Logger extends org.slf4j.Logger { |
其中LogLevel表示Log等级,有以下值。可通过StartParameter控制Gradle要输出的Log等级。
1 | public enum LogLevel { |
可通过以下方式获取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()