前言
gradle的官网文档非常详尽,然而可惜的是它们出自于不同的开发者,在不同的时间点写成,最终拼在一起,没有一个清晰的脉络来将各种概念串到一起,至少我在阅读官网文档时有这种感觉。虽然每一篇文档都将各个概念是什么、有什么背景、对应什么接口、有什么属性和方法都说到了,但就是没有一个清晰的示例来展示什么场景下使用、如何使用。
至于Gradle的博客,虽然每一篇博客都对某一个具体需求如何实现给出了介绍和示例,但很遗憾的是介绍基础概念的博客都太简单,没有解答我的全部疑惑。
经过一段时间的阅读和实验后,我感觉我可以整理出来一份资料,在这份资料中,不会涉及如何用gradle管理java或者Android项目,而是介绍如何利用gradle提供的能力,方便地完成自定义的构建任务。不过,这份资料对于理解java和Android Plugin是如何工作的应该会有些帮助。
概念解释
生命周期
gradle执行过程中有三个阶段,分别是:
- 根据setting.gradle进行初始化,此阶段setting.gradle脚本的delegate对象是一个Settings接口对象;
- 根据build.gradle配置Project,此阶段各个build.gradle脚本的delegate对象是对应的Project接口对象;
- 执行本次构建需要执行的Task。
Setting
执行gradle命令时的当前目录就是根项目目录,根项目下通过settings.gradle文件来指导gradle如何执行初始化,初始化过程中gradle需要知道本次构建都有哪些项目,每个子项目都需要在settings.gradle中被引入。
在setting.gradle文件中还可以对project进行更近一步的配置,但一般不需要这么做,可以参考Settings接口API和DSL文档来了解可以如何配置。
Project
Project就是项目,所有Task都定义在各个Project下,Project中可以配置很多内容,除了Task还有:Plugin、Configuration、Dependency、Artifact、Extensions,等等。
当我们在配置Dependency和Artifact时,就是在配置Configuration,具体的概念请参考下面的“延迟的文件”部分。
Task
Task代表一项具体的工作。通常一个task会接受一系列参数,并输出一系列文件,这样的有产物的Task可以被配置为project的Artifact,作为项目的总体产物;也可以被配置为maveb publication的Artifact,作为要发布的产物。
延迟的文件:依赖和产物
在Gradle中,Dependency都可以被当成延迟的文件,虽然它本身不是文件,但它的解析结果是一个文件,把它当作文件来依赖时,当用到文件相关的API时就会解析和下载Dependency,然后正常返回。
前面提到过,当我们在配置项目的Dependencies和Artifacts时,其实就是在配置对应的Configuration,一个Configuration就是Dependency和Artifact的集合,Dependency代表依赖,Artifact代表产物,可以使用同一个Configuration来同时声明项目的依赖和产物,也可以专门用一个Configuration来代表依赖,用另一个Configuration来代表产物。
事实上,我们在dependencies块中配置的每一项依赖都是在对Configuration进行配置,一句”api ‘com.example.test:awesome:1.0.0’”就是在为名为api的Conffiguration增加一项dependency,类似的,在artifacts中的配置也是在为同名Configuration增加一项artifact。
指定Dependency时,常常将Dependency指定为另一个Project、一个maven仓库中的依赖、一个本地文件。当依赖另一个Project时,其实是在依赖另一个项目的某一个Artifact。默认情况下,会依赖另一个项目的default artifact。
实战
即使有上面的名词解释,也不容易真正的理解gradle,但至少已经有了个初步的印象。下面通过几个真实的case,进一步结合概念来进行说明。
声明maven依赖并使用
gradle的依赖管理能力非常强大而且易用,下面这个例子将apache maven仓库中的junit依赖下载到本地的dependencies目录中:
// build.gradle
// 为当前Project配置依赖源,mavenCentral是由gradle提供的API,作用是将apache maven仓库配置好
repositories {
mavenCentral()
}
// 任何一个dependencies或者artifacts块中的配置都需要在这里声明注册
// 平时我们用的java或者android plugin都帮我们声明好了,但这里没有引入任何plugin,需要我们自己声明一个configuration
configurations {
depend
}
// 为depend这个configuration配置好dependency,configuration对象实现了FileCollection接口,当把configuration对象
// 当作FileCollection接口对象使用时,gradle会帮我们下载好configuration的dependency,并表现为dependency文件集合。
dependencies {
depend 'junit:junit:4.12'
depend 'commons-io:commons-io:2.6'
}
// 至于下面这种奇特的语法,是gradle利用groovy AST转换能力提供的特殊dsl语法,gradle源码中有一堆代码专门做语法转换,
// 而且没有一个文档解释这些转换具体做了什么工作,就好像他们觉得groovy语法还不够令人困惑一样。
// 如果你不喜欢这种语法,你总是可以仅仅使用api doc中的方法,比如tasks.create。
// 但是,既然别人都在用这种语法,你也不得不接受它。
task copyDepend(type: Copy) {
// from方法接受FileCollection接口对象,而Configuration实现了FileCollection接口
from configurations.depend
into './dependencies/'
}
运行gradle copyDepend,gradle就会执行copyDepend这个task,因为Copy task会尝试将from指定的文件复制到指定位置,会调用FileCollection接口提供的方法,此时gradle就会帮我们将Configuration中配置好的依赖解析并下载,然后这个Configuration对象就表现得好像一个由所有依赖文件组成的FileCollection对象一样,依赖文件得以被复制到指定位置。
声明项目产物,由另一个项目依赖并使用
声明项目产物,并发布到local maven
plugins {
id 'maven-publish'
}
// 默认情况下,maven-publish plugin会取项目的group和version作为maven的group和version,
// 取project名称作为maven的artifactId,因此这里maven id就是:com.example:<projectName>:1.0
group = 'com.example'
version = '1.0'
task zipArchive(type: Zip) {
archiveFileName = 'libUtil.zip'
destinationDirectory = file('./dist/')
from "./src"
}
publishing {
publications {
// 创建一个名为archive的publication对象,并配置
archive(MavenPublication) {
// 将zipArchive task的产出配置为archive publication要上传的产物
artifact zipArchive
}
}
// 如果要发布到真正的maven仓库,需要正确配置这一个block
repositories {
mavenLocal()
}
}
编写自己的Plugin,使用dsl配置自定义参数并根据参数执行Task
// gradle要求每一个NamedDomain Object都要定义一个name属性,并具有单个String参数的构造函数用于配置name属性
class MyPluginConfig {
String name
int versionCode
String versionName
MyPluginConfig(String name) {
this.name = name
}
void versionCode(code) {
versionCode = code
}
void versionName(name) {
versionName = name
}
}
class MyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
// 使用Project.container方法来创建一个NamedDomainObjectContainer
def configContainer = project.container(MyPluginConfig)
// 将NamedDomainObjectContainer注册为project.extension,从而可以直接使用myConfigs方法来配置它
project.extensions.add('myConfigs', configContainer)
project.task('showVersion') {
doLast {
configContainer.all {
println "config: {name: ${it.name}, versonCode: ${it.versionCode}, versionName: ${it.versionName}}"
}
}
}
}
}
apply plugin: MyPlugin.class
myConfigs {
// 创建一个name属性为config1的NamedDomainObject,然后用下面的闭包来配置它
config1 {
versionCode 1015
versionName "10.15.0"
}
config2 {
versionCode 1016
versionName "10.16.0"
}
}
将上一步编写的Plugin移到独立项目中,并发布到local maven,供其他项目引用
引用standalone plugin的代码如下,standalone plugin的代码目录层级较多:standalonePlugin.zip
然后,使用下面的build.gradle来引用它
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath 'com.example:myplugin:1.0.3'
}
}
apply plugin: 'com.example.myplugin'
myConfigs {
// 创建一个name属性为config1的NamedDomainObject,然后用下面的闭包来配置它
config1 {
versionCode 1015
versionName "10.15.0"
}
config2 {
versionCode 1016
versionName "10.16.0"
}
}