前言
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,需要我们自己声明一个configurationconfigurations {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.dependinto './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.0group = '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仓库,需要正确配置这一个blockrepositories {mavenLocal()}}
编写自己的Plugin,使用dsl配置自定义参数并根据参数执行Task
// gradle要求每一个NamedDomain Object都要定义一个name属性,并具有单个String参数的构造函数用于配置name属性class MyPluginConfig {String nameint versionCodeString versionNameMyPluginConfig(String name) {this.name = name}void versionCode(code) {versionCode = code}void versionName(name) {versionName = name}}class MyPlugin implements Plugin<Project> {@Overridevoid apply(Project project) {// 使用Project.container方法来创建一个NamedDomainObjectContainerdef 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.classmyConfigs {// 创建一个name属性为config1的NamedDomainObject,然后用下面的闭包来配置它config1 {versionCode 1015versionName "10.15.0"}config2 {versionCode 1016versionName "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 1015versionName "10.15.0"}config2 {versionCode 1016versionName "10.16.0"}}
