Gradle 学习之 Project

2,241

本文大部分内容来自 Gradle 官方文档,英文 OK 的同学可以直接看官方文档。 Gradle 系列文章的示例代码都放在了 zjxstar 的 GitHub 上了。

前言

本文假设大家都已经掌握了 Gradle 构建项目的目录结构。其中,脚本文件 settings.gradle 和 build.gradle 至关重要。Gradle 中的一系列构建操作都基于这两个文件。

本系列文章想和大家一起学习 Gradle 的基础知识,如:Project、Task 和 Transform 等。本文的主要内容是 Project ,其中会涉及到一些简单的 Task,这里不会详细介绍 Task,大家只需知道有 Task 这个概念即可。文中的示例代码都基于 Android Studio 创建的标准 Android 项目。

Project 的基本概念

在 Gradle 中,最重要的两个概念是 Project(项目) 和 Task(任务)。每一次构建都至少包含一个 Project,每一个 Project 又包含了一个或多个 Task。每个 build.gradle 文件都代表一个 Project,其实 Project 可以理解为在业务范围内,被抽象出来的一个个独立的模块。而 Task 一般被定义在 build.gradle 中,它表示一个操作,比如:复制文件、打个 jar 包、上传文件等。

以 Android 项目举例说明:

Android项目目录结构图

注:Demo 中使用的 Gradle 版本为 3.3.1 。

使用 Android Studio 创建项目后,会自动生成两个模块,一个是以项目名命名的根模块,另一个是 app 模块。两个模块中都有 build.gradle 文件,按照前文的说法,就是两个 Project。其中,项目 GradleSample 被称为 rootProject ,项目 app 被称为 subProject 。在根 build.gradle 文件中默认有一个名为 clean 的 Task,它的职责是删除根模块下 build 目录。

在初始化构建过程中,Gradle 会基于 build 脚本文件来组装 Project 和 Task。

Gradle 的构建生命周期

执行一个 Gradle 构建的最简单形式是执行一个 Task,而一些 Task 可能会依赖于其他 Task。Gradle 为了管理这些 Task 会在任何一个 Task 执行前构建一个 DAG 图(Directed Acyclic Graph,有向无环图)。这意味着所有的 Task 都会被一个接一个地执行,而且只执行一次。那些没有依赖的 Task 通常会被优先执行。

Gradle 的构建生命周期分为三个阶段:

  • 初始化:Gradle 通过 settings.gradle 文件决定哪些项目参与构建,会创建 Project 实例。如果一个项目有多个模块,并且每个模块都有其对应的 build.gradle 文件,那么就会创建多个 Project 实例。
  • 配置:这个阶段,构建脚本 build.gradle 会被执行,并为每个 Project 实例创建和配置 Task。DAG 依赖关系图就在此阶段生成。
  • 执行:该阶段,Gradle 将决定哪个任务会被执行。哪些任务被执行取决于开始这次构建的参数配置以及该 Gradle 文件的所在目录。

Project 的生命周期

在 Gradle 构建的初始化阶段,Gradle 会给每个项目创建一个 Project 对象。那么 Project 又具有怎样的生命周期呢?

  • 为构建创建一个 Settings 实例。
  • 根据 Settings 对象评估 settings.gradle 脚本(如果存在)以进行配置。
  • 使用配置后的 Settings 对象创建 Project 实例的层次结构。
  • 最后,通过对项目执行 build.gradle文件(如果存在)来评估每个 Project。Project 是以广度优先顺序进行评估的,这样就可以使父 Project 在它的子 Project 之前完成评估。

Project 的属性

Gradle 针对 Project 实例执行项目的构建文件以配置项目。build 脚本中的任何属性和方法都会委托给关联的 Project 对象。这意味着,可以直接在脚本中使用 Project 接口上的任何属性和方法。

例如:

defaultTasks('some-task')  // Delegates to Project.defaultTasks()
reportsDir = file('reports') // Delegates to Project.file() and the Java Plugin

你也可以通过 Project 实例使用 Project 属性,比如:

project.name // 获取project的名字,而不是单独使用name属性

你可以在构建脚本中按照名称访问这些属性,也可以通过 Project 的

project.property(java.lang.String) // 例如:project.property('version')

方法来进行访问。

一个项目在搜索属性时会考虑 5 个属性范围:

  • Project 对象自身。这个范围里的属性包含 Project 实现类中定义有 getters 和 setters 方法的所有属性。比如:project.getRootProject() 方法就对应了 rootProject 属性。至于这些属性的可读写性取决于它们是否定义 getters 或者 setters 方法了。
  • Project 的额外属性 ( extra ) 。每个 Project 都会维护一个额外属性的映射,它可以包含任意的名称 -> 值对。定义后,此作用域的属性是可读写的。比如:project.ext.prop1 = 'foo' 。
  • 通过插件被添加到 Project 中的扩展属性 ( extensions ) 。每个扩展名都可以作为只读属性使用,其名称与扩展名相同。比如:project.android.compileSdkVersion 。
  • 通过插件添加到 Project 中的约定属性 ( convention ) 。插件可以通过 Project 的 Convention 对象向 Project 中添加属性和方法。此范围的属性的可读可写性取决于约束对象。
  • Project 中 Tasks 。可以使用 Task 的名称作为属性名称来访问任务。此范围的属性是只读的。

读写属性时,Project 都是按照上述范围的顺序进行查找的,在某个范围找到属性后就会返回该属性。如果没有找到,会抛出异常。

常用的 Project 属性有:

属性名 作用
allprojects 当前项目及其所有子项目的集合
buildDir 当前项目的编译目录(自动生成)
默认值 porjectDir/build
defaultTasks 当前项目的默认任务的名字集
当前构建没有提供任务名时会执行这些默认任务
group 当前项目的组名
logger 当前项目的日志器,可以用来在 build 文件中写日志
name 当前项目的名字
parent 当前项目的父项目
path 当前项目的路径(绝对路径)
project 当前项目的实例
rootProject 当前项目层次结构中的根项目
subprojects 当前项目的子项目集
tasks 当前项目的任务集
version 当前项目的版本号,默认值:unspecified

示例:使用 gradlew build 命令即可运行(基于第一小节图中的 Android 项目)。

// 访问Project的属性
// 在app模块的build.gradle中
println project.group // 打印组名:GradleSample
println version // 打印版本号,默认 unspecified
println project.name // 打印项目名:app
println rootProject.name // 打印根项目的名字:GradleSample
logger.quiet('Test project logger property') // 使用logger
for(String taskName : rootProject.defaultTasks) { // 获取默认任务
    println "defaultTask: $taskName" // 打印:defaultTask: clean
}
println rootProject.ext.hello // 打印额外参数

// 在根build.gradle脚本中
defaultTasks 'clean' // 设置默认Task
ext { // 设置额外参数
    hello = 'Welcome to Gradle'
}

Project 的方法

一个项目在搜索方法时,也会考虑 5 个范围:

  • Project 对象自身。
  • build.gradle 脚本文件。
  • 通过插件添加到 Project 中的扩展 ( extensions ) 。每个扩展都可以当做参数是闭包或 Action 的方法。
  • 插件添加到项目中的约定方法 ( convention ) 。插件可以通过项目的 Convention 对象向项目中添加属性和方法。
  • 项目中的 Tasks 。每个 Task 都会添加一个方法,方法名是任务名,参数是单个闭包或者 Action 。该方法使用提供的闭包为相关任务调用 Task.configure( groovy.lang.Closure ) 方法。

常用的 Project 方法有:

方法名(不列出参数,部分方法有重载) 作用
afterEvaluate 可以添加一个闭包,它会在项目完成评估后立即执行。
当执行属于该项目的构建文件时,会通知此类监听器。
allprojects 配置当前项目以及它的每个子项目
apply 应用插件或脚本
beforeEvaluate 添加一个闭包,她会在项目开始评估前立即执行
configure 通过闭包配置对象集合
copy 拷贝特定文件
file 解析文件
findProperty 找特定属性,返回它的值,如果没有,返回null
hasProperty 判断当前项目有没有指定属性
project 获取指定项目的 project 对象
setProperty 给属性设置值
subprojects 配置当前项目的所有子项目
task 定义一个任务

示例:使用 gradlew -q helloTask 命令运行即可。

// 根build.gradle
allprojects {
    // 给所有项目都添加一个任务:helloTask
    task helloTask {
        doLast { task ->
            println "I'm ${task.project.name}"
        }
    }
}

subprojects {
    helloTask { // 给所有子项目中的helloTask任务增加一个doFirst回调
        doFirst {
            println "I'm the task in sub project, doFirst"
        }
    }

    afterEvaluate { project ->
        println "it's after evaluate..."
        helloTask.configure {
            doLast {
                println "configure task after evaluate"
            }
        }
    }
}

// 给所有的子项目(除了app项目)的helloTask配置doLast
configure(subprojects.findAll{ it.name != 'app' }) {
    helloTask {
        doLast {
            println "i am not app module, i configure doLast"
        }
    }
}

输出结果:

it's after evaluate...
I'm GradleSample
I'm the task in sub project, doFirst
I'm app
configure task after evaluate

总结

本文的内容不多,详细介绍了 Gradle 中 Project 的生命周期、属性和方法。同时,概述了 Gradle 构建项目的三个时期,并提到了 Task 。其实,Gradle 主要还是围绕 Task 来运转的,后续文章我们将学习 Task 的一系列知识。

参考资料

  1. Gradle 官方文档
  2. Android 官网
  3. 《Android Gradle权威指南》
  4. 《Gradle for Android 中文版》