前言

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目录中:

  1. // build.gradle
  2. // 为当前Project配置依赖源,mavenCentral是由gradle提供的API,作用是将apache maven仓库配置好
  3. repositories {
  4. mavenCentral()
  5. }
  6. // 任何一个dependencies或者artifacts块中的配置都需要在这里声明注册
  7. // 平时我们用的java或者android plugin都帮我们声明好了,但这里没有引入任何plugin,需要我们自己声明一个configuration
  8. configurations {
  9. depend
  10. }
  11. // 为depend这个configuration配置好dependency,configuration对象实现了FileCollection接口,当把configuration对象
  12. // 当作FileCollection接口对象使用时,gradle会帮我们下载好configuration的dependency,并表现为dependency文件集合。
  13. dependencies {
  14. depend 'junit:junit:4.12'
  15. depend 'commons-io:commons-io:2.6'
  16. }
  17. // 至于下面这种奇特的语法,是gradle利用groovy AST转换能力提供的特殊dsl语法,gradle源码中有一堆代码专门做语法转换,
  18. // 而且没有一个文档解释这些转换具体做了什么工作,就好像他们觉得groovy语法还不够令人困惑一样。
  19. // 如果你不喜欢这种语法,你总是可以仅仅使用api doc中的方法,比如tasks.create。
  20. // 但是,既然别人都在用这种语法,你也不得不接受它。
  21. task copyDepend(type: Copy) {
  22. // from方法接受FileCollection接口对象,而Configuration实现了FileCollection接口
  23. from configurations.depend
  24. into './dependencies/'
  25. }

运行gradle copyDepend,gradle就会执行copyDepend这个task,因为Copy task会尝试将from指定的文件复制到指定位置,会调用FileCollection接口提供的方法,此时gradle就会帮我们将Configuration中配置好的依赖解析并下载,然后这个Configuration对象就表现得好像一个由所有依赖文件组成的FileCollection对象一样,依赖文件得以被复制到指定位置。

声明项目产物,由另一个项目依赖并使用

project_dependency.zip

声明项目产物,并发布到local maven

  1. plugins {
  2. id 'maven-publish'
  3. }
  4. // 默认情况下,maven-publish plugin会取项目的group和version作为maven的group和version,
  5. // 取project名称作为maven的artifactId,因此这里maven id就是:com.example:<projectName>:1.0
  6. group = 'com.example'
  7. version = '1.0'
  8. task zipArchive(type: Zip) {
  9. archiveFileName = 'libUtil.zip'
  10. destinationDirectory = file('./dist/')
  11. from "./src"
  12. }
  13. publishing {
  14. publications {
  15. // 创建一个名为archive的publication对象,并配置
  16. archive(MavenPublication) {
  17. // 将zipArchive task的产出配置为archive publication要上传的产物
  18. artifact zipArchive
  19. }
  20. }
  21. // 如果要发布到真正的maven仓库,需要正确配置这一个block
  22. repositories {
  23. mavenLocal()
  24. }
  25. }

编写自己的Plugin,使用dsl配置自定义参数并根据参数执行Task

  1. // gradle要求每一个NamedDomain Object都要定义一个name属性,并具有单个String参数的构造函数用于配置name属性
  2. class MyPluginConfig {
  3. String name
  4. int versionCode
  5. String versionName
  6. MyPluginConfig(String name) {
  7. this.name = name
  8. }
  9. void versionCode(code) {
  10. versionCode = code
  11. }
  12. void versionName(name) {
  13. versionName = name
  14. }
  15. }
  16. class MyPlugin implements Plugin<Project> {
  17. @Override
  18. void apply(Project project) {
  19. // 使用Project.container方法来创建一个NamedDomainObjectContainer
  20. def configContainer = project.container(MyPluginConfig)
  21. // 将NamedDomainObjectContainer注册为project.extension,从而可以直接使用myConfigs方法来配置它
  22. project.extensions.add('myConfigs', configContainer)
  23. project.task('showVersion') {
  24. doLast {
  25. configContainer.all {
  26. println "config: {name: ${it.name}, versonCode: ${it.versionCode}, versionName: ${it.versionName}}"
  27. }
  28. }
  29. }
  30. }
  31. }
  32. apply plugin: MyPlugin.class
  33. myConfigs {
  34. // 创建一个name属性为config1的NamedDomainObject,然后用下面的闭包来配置它
  35. config1 {
  36. versionCode 1015
  37. versionName "10.15.0"
  38. }
  39. config2 {
  40. versionCode 1016
  41. versionName "10.16.0"
  42. }
  43. }

将上一步编写的Plugin移到独立项目中,并发布到local maven,供其他项目引用

引用standalone plugin的代码如下,standalone plugin的代码目录层级较多:standalonePlugin.zip
然后,使用下面的build.gradle来引用它

  1. buildscript {
  2. repositories {
  3. mavenLocal()
  4. }
  5. dependencies {
  6. classpath 'com.example:myplugin:1.0.3'
  7. }
  8. }
  9. apply plugin: 'com.example.myplugin'
  10. myConfigs {
  11. // 创建一个name属性为config1的NamedDomainObject,然后用下面的闭包来配置它
  12. config1 {
  13. versionCode 1015
  14. versionName "10.15.0"
  15. }
  16. config2 {
  17. versionCode 1016
  18. versionName "10.16.0"
  19. }
  20. }