Project 接口是 Gradle 构建脚本中用于与 Gradle 交互的主要 API。 通过Project,您可以编程式地访问 Gradle 的所有功能。
生命周期(Lifecycle)
Project和build.gradle(或build.gradle.kts对于Kotlin DSL)文件之间存在一对一的关系。在构建初始化过程中,Gradle为参与构建的每个项目组装一个Project对象,具体过程如下:
1.为构建创建一个Settings实例
2.评估settings.gradle脚本,如果存在,则使用它来配置Settings对象
文件中定义了项目及其子项目的组织方式,比如
include 'moduleA', 'moduleB', 'moduleC'
上述代码告诉了Gradle你的构建应该包含三个子项目:moduleA、moduleB和moduleC。
3.使用配置好的Settings对象创建Project实例的层次结构
首先创建一个代表根项目的Project实例,然后为每个子项目创建一个Project实例。这些子项目的Project实例会成为根项目Project实例的子对象,从而形成一个层次结构。
4.最后,评估每个Project
针对层次结构中的每个Project实例, 评估对应的build.gradle(.kts)文件,如果存在,则执行这个文件来配置和初始化Project。
默认情况下,Gradle会按照广度优先的顺序评估项目。这意味着父项目会先于其子项目被评估。这种顺序确保了父项目的配置可以在子项目中使用。但是,这种顺序可以通过调用Project.evaluationDependsOnChildren()方法或者使用Project.evaluationDependsOn(java.lang.String)方法添加显式的评估依赖关系来覆盖。
任务(Tasks)
一个项目本质上是一组任务对象的集合,每个Task执行一些基本的工作,比如编译类文件、运行单元测试、或者打包WAR文件等。您可以使用TaskContainer上的create()方法向项目添加任务,比如TaskContainer.create(java.lang.String)。也可以使用TaskContainer上的查找方法来定位现有的任务,比如TaskCollection.getByName(java.lang.String)。TaskContainer实例可以通过调用Project对象的getTasks()方法获得。
依赖(Dependencies)
一个项目为了完成自己的工作通常会需要一些依赖项,同样的也会生成一些制品(artifacts),这些制品会被其他项目使用。这些依赖是按配置分组的,并且可以从存储库中检索和上传。可以使用Project对象的getConfigurations方法获取ConfigurationContainer实例,然后使用它来管理这些配置。也可以调用Project.getDependencies()获取DependencyHandler来管理依赖。Project.getArtifacts()返回的ArtifactHandler来管理制品,Project.getRepositories()得到的RepositoryHandler来管理存储库。
多项目构建(Multi-project Builds)
项目通常被组织成一个项目层次结构,每个项目都有一个名称,以及一个完全限定路径,这个全限定路径在项目层次结构中可以唯一地标识项目。
插件(Plugins)
插件可用于模块化和重用项目配置。可以使用PluginAware.apply(java.util.Map)方法或使用PluginDependenciesSpec插件脚本块(plugins {})来应用插件。
这两者之间一个关键的区别是,通过plugins {}块应用的插件在概念上应用于整个构建脚本,而PluginAware.apply(java.util.Map)方法则允许你直接将插件应用到特定的Project对象或其他对象上。然而,随着Gradle的不断发展,可能会有更多的功能和优化被添加到plugins {}块中,使其成为一个更强大和灵活的工具。因此,对于大多数构建脚本,使用plugins {}块来应用插件通常是首选方法。
动态项目属性(Dynamic Project Properties)
Gradle执行项目的build.gradle(.kts)文件来配置Project实例。在构建脚本中使用的任何属性或方法,都被委托给了相应的Project对象。
这意味着,你可以在脚本中直接使用Project接口上的任何方法和属性。
例如:
defaultTasks('some-task') // 委托给 Project.defaultTasks()
reportsDir = file('reports') // 委托给 Project.file()
使用project属性可以访问Project实例,在某些情况下使用Project实例可以使得脚本更清晰,比如使用project.name来访问项目的名称而不是name。
一个项目有五个属性作用域,用于搜索属性。你可以在构建文件中通过名称访问这些属性,或者通过调用项目的Project.property(java.lang.String)方法来访问。
这些作用域包括:
- Project对象本身
这个作用域包含了由Project实现类声明的任何属性的getter和setter方法。例如,Project.getRootProject()可以通过rootProject属性来访问。这个作用域的属性是否可读或可写取决于是否存在相应的getter或setter方法。
- 项目的额外属性
每个项目都维护了一个额外属性的映射Map,可以包含任意的名称->值的键值对。一旦定义,这个作用域的属性就是可读和可写的。更多详情在后面的“额外属性”部分会作进一步介绍。
- 通过插件添加到项目的扩展
每个扩展都作为与扩展同名的只读属性可用。比如我们可以定义一个扩展类:
// MyCustomExtension.groovy
class MyCustomExtension {
// 约定属性 ,插件提供了合理的默认值(即约定)
String customProperty = "default value"
}
在插件中注册这个扩展,并给它指定了一个名称myCustom:
// MyCustomPlugin.groovy
import org.gradle.api.Plugin
import org.gradle.api.Project
class MyCustomPlugin implements Plugin<Project> {
void apply(Project project) {
// 创建一个MyCustomExtension的实例
MyCustomExtension extension = project.objects.newInstance(MyCustomExtension.class)
// 将扩展添加到项目的扩展集合中
project.extensions.add('myCustom', extension)
// 现在可以在构建脚本中通过project.myCustom访问这个扩展了
// 例如,在插件中注册一个任务,使用扩展中的属性
project.task('printCustomProperty') {
doLast {
println "Custom Property: ${extension.customProperty}"
}
}
}
}
之后在项目中应用这个插件,就可以通过project.myCustom或简单的myCustom来访问这个扩展了:
// build.gradle
apply plugin: 'com.example.mycustomplugin' // 假设这是你的插件ID
// 配置插件的扩展
myCustom {
customProperty = 'Hello from MyCustomExtension!'
}
// 在其他地方访问customProperty属性
task printProperty {
doLast {
println "Custom Property: ${myCustom.customProperty}"
}
}
- 通过插件添加到项目的约定属性
插件不仅添加扩展来允许用户自定义其行为,还可能定义所谓的“约定属性”(convention properties)。这些属性提供了插件的默认行为,并且可以被用户覆盖。比如扩展中的customProperty属性,就是一个约定属性。这个作用域的属性是否可读或可写取决于约定对象是否提供了相应的 getter 和 setter 方法。
- 项目任务
任务可以通过其名称作为属性名称来访问。这个作用域的属性是只读的,例如,一个名为compile的任务可以作为compile属性来访问。
- 额外属性和约定属性是从项目的父项目继承的,递归直到根项目。这个作用域的属性是只读的。
读取属性时:项目会按照上述顺序搜索这些作用域,并返回第一个找到属性的作用域中的值。如果未找到,则会抛出异常。更多详情参见Project.property(java.lang.String)。
写入属性时:项目也会按照上述顺序搜索这些作用域,并在它找到属性的第一个作用域中设置该属性。如果未找到,则会抛出异常。更多详情参见Project.setProperty(java.lang.String, java.lang.Object)。
额外属性(Extra Properties)
所有额外的属性都必须通过“ext”命名空间定义。一旦定义了一个额外属性,就可以在拥有它的对象(在下面的例子中分别是Project、Task和子项目)上直接读取和更新它,而无需再次使用ext前缀。即只有定义的时候需要ext前缀。
在 Project 实例上定义:
project.ext.prop1 = "foo"
在 Task 中定义:
虽然直接在 Task 上定义额外属性不常见,但技术上也是可能的:
task doStuff {
ext.prop2 = "bar"
doLast {
println prop2 // 直接访问,无需使用 ext 前缀
}
}
在子项目(Sub-projects)中定义:
在根项目的 settings.gradle 文件中定义包含多个项目的多项目构建时,你可以在每个子项目的 build.gradle 文件中定义额外的属性。
// 在根项目的 settings.gradle
rootProject.name = 'my-multi-project'
include 'subproject1', 'subproject2'
// 在 subproject1 的 build.gradle
ext {
subprojectProperty = 'value for subproject1'
}
// 在 subproject2 的 build.gradle
ext {
subprojectProperty = 'value for subproject2'
}
// 在根项目的 build.gradle 或任何需要使用这些属性的脚本中
subprojects {
task printProperties {
doLast {
println "Project name: $name, subproject property: $subprojectProperty"
}
}
}
subprojects在Gradle配置中的作用是配置该项目的所有子项目。当使用多模块项目时,不同模块之间可能存在相同的配置,这会导致重复配置。为了解决这个问题,可以将相同的配置部分抽取出来,并使用配置注入的技术完成子项目的配置。subprojects方法会遍历根项目的所有子项目,并注入指定的配置。
而allprojects是对所有项目的配置,包括根项目(Root Project)。
在多项目构建中,每个子项目都有自己的 ext 命名空间,因此你可以为每个子项目定义不同的额外属性。
总之,ext 命名空间提供了一种方便的方式来向 Gradle 对象添加额外的属性,并且一旦定义,这些属性就可以直接通过对象来访问和更新。
动态方法(Dynamic Methods)
一个项目有五个方法作用域,用于搜索方法。
- Project对象本身
Gradle的Project类本身包含了许多方法,这些方法提供了与项目交互的功能。例如,project.task(...) 用于创建新任务。
- build.gradle构建文件
项目会搜索定义在构建文件中的匹配的方法。
- 通过插件添加到项目的扩展
扩展通常提供了配置接口(比如前面例子中的myCustom{}配置块),可以接受闭包(在Groovy中)或Action(在Kotlin DSL中)作为参数。myCustom是插件为项目添加的一个扩展属性。这个属性通常是一个对象,该对象提供了配置该扩展的方法或闭包配置块。在闭包内,你可以设置扩展的属性或调用其提供的方法。
- 通过插件添加到项目的约定方法
约定属性和方法通常不是直接作为属性或方法添加到Project对象上,而是作为Convention对象的一部分。
修改动态项目属性中的插件例子,我们给扩展类增加一个名为doSomething的约定方法:
// MyCustomExtension.groovy
class MyCustomExtension {
// 约定属性 ,插件提供了合理的默认值(即约定)
String customProperty = "default value"
//约定方法
doSomething {
}
}
然后在build.gradle配置块中可以调用扩展类中定义的约定方法:
// build.gradle
apply plugin: 'com.example.mycustomplugin' // 假设这是你的插件ID
// 配置插件的扩展
myCustom {
customProperty = 'Hello from MyCustomExtension!'
doSomething {
}
}
- 项目任务
对于项目中的每个任务,Gradle都会动态地添加一个与任务名相同的方法。这个方法接受一个闭包或Action作为参数,并调用任务的configure(groovy.lang.Closure)方法来应用配置。这允许你使用类似于方法调用的简洁语法来配置任务。比如项目有一个名称为compile的任务,那么就会添加一个方法void compile(Closure configureClosure)。
- 父项目的方法,递归直到根项目。
如果一个方法在当前项目中未找到,Gradle会递归地在父项目中查找。这允许你在根项目中定义可以在所有子项目中重用的方法或逻辑。
- 值是闭包的项目属性
如果项目的某个属性的值是一个闭包,那么这个闭包可以像方法一样被调用,并传入相应的参数。Gradle会在上述属性作用域中查找这个属性,如果找到了,就会将其值作为闭包来调用。