理想情况下,Groovy 构建脚本主要看起来像是配置:设置项目的某些属性、配置依赖项、声明任务等。该配置基于 Groovy 语言结构。本入门指南旨在解释这些结构是什么,以及最重要的——它们与 Gradle API 文档的关系。

Project 对象

由于 Groovy 是一种基于 Java 的面向对象语言,其属性和方法适用于对象。在某些情况下,对象是隐式的——尤其是在构建脚本的顶层,即没有嵌套在 {} 块内。

考虑此构建脚本片段,其中包含一个非限定属性和块

version = '1.0.0.GA'

configurations {
    ...
}

versionconfigurations {} 都是 org.gradle.api.Project 的一部分。

此示例反映了每个 Groovy 构建脚本都由 Project 的隐式实例支持。如果您看到一个非限定元素,并且不知道它在哪里定义,请务必检查 Project API 文档,看看它是否来自那里。

避免在构建脚本中使用 Groovy MetaClass 编程技术。Gradle 提供了自己的 API 来添加动态运行时属性

使用 Groovy 特定的元编程会导致构建在构建之间保留大量内存,最终导致 Gradle 守护程序内存不足。

属性

<obj>.<name>                // Get a property value
<obj>.<name> = <value>      // Set a property to a new value
"$<name>"                   // Embed a property value in a string
"${<obj>.<name>}"           // Same as previous (embedded value)
示例
version = '1.0.1'
myCopyTask.description = 'Copies some files'

file("$projectDir/src")
println "Destination: ${myCopyTask.destinationDir}"

属性表示对象的一些状态。= 符号的存在清楚地表明您正在查看一个属性。否则,一个合格的名称——它以 <obj>. 开头——没有任何其他修饰也是一个属性。

如果名称不合格,则可能是以下之一

  • 具有该名称的任务实例。

  • Project 上的属性。

  • 在项目其他地方定义的额外属性

  • 中隐式对象的属性。

  • 在构建脚本中早先定义的局部变量

请注意,插件可以向 Project 对象添加自己的属性。API 文档列出了核心插件添加的所有属性。如果您难以找到属性的来源,请查看构建使用的插件的文档。

当在构建脚本中引用由非核心插件添加的项目属性时,请考虑在其前面加上 project. ——这样就清楚地表明该属性属于项目对象。

API 文档中的属性

Groovy DSL 参考显示了在构建脚本中使用的属性,但 Javadoc 只显示方法。那是因为属性是在幕后作为方法实现的

  • 如果有一个名为 get<PropertyName> 的方法,零参数,返回与属性相同的类型,则可以读取属性。

  • 如果有一个名为 set<PropertyName> 的方法,一个参数,与属性具有相同的类型,并且返回类型为 void,则可以修改属性。

请注意,属性名称通常以小写字母开头,但该字母在方法名称中为大写。因此 getter 方法 getProjectVersion() 对应于属性 projectVersion。当名称以至少两个大写字母开头时,此约定不适用,在这种情况下,大小写不会改变。例如,getRAM() 对应于属性 RAM

示例
project.getVersion()
project.version

project.setVersion('1.0.1')
project.version = '1.0.1'

方法

<obj>.<name>()              // Method call with no arguments
<obj>.<name>(<arg>, <arg>)  // Method call with multiple arguments
<obj>.<name> <arg>, <arg>   // Method call with multiple args (no parentheses)
示例
myCopyTask.include '**/*.xml', '**/*.properties'

ext.resourceSpec = copySpec()   // `copySpec()` comes from `Project`

file('src/main/java')
println 'Hello, World!'

方法表示对象的一些行为,尽管 Gradle 也经常使用方法来配置对象的状态。方法可以通过其参数或空括号来识别。请注意,有时需要括号,例如当方法没有参数时,因此您可能会发现始终使用括号最简单。

Gradle 有一个约定,即如果一个方法与基于集合的属性同名,则该方法会将其值追加到该集合中。

代码块

代码块也是方法,只是最后一个参数的类型特定。

<obj>.<name> {
     ...
}

<obj>.<name>(<arg>, <arg>) {
     ...
}
示例
plugins {
    id 'java-library'
}

configurations {
    assets
}

sourceSets {
    main {
        java {
            srcDirs = ['src']
        }
    }
}

dependencies {
    implementation project(':util')
}

代码块是一种一次性配置构建元素多个方面​​的机制。它们还提供了一种嵌套配置的方法,从而形成一种结构化数据。

您应该了解代码块的两个重要方面

  1. 它们作为具有特定签名的方法实现。

  2. 它们可以更改非限定方法和属性的目标(“委托”)。

两者都基于 Groovy 语言特性,我们将在以下章节中解释它们。

代码块方法签名

您可以根据方法的签名,或者更具体地说,其参数类型,轻松地将方法识别为代码块背后的实现。如果方法对应于代码块

例如,Project.copy(Action) 符合这些要求,因此您可以使用以下语法

copy {
    into layout.buildDirectory.dir("tmp")
    from 'custom-resources'
}

这就引出了 into()from() 如何工作的问题。它们显然是方法,但是您会在 API 文档中哪里找到它们呢?答案来自于理解对象委托

委托

属性部分列出了可以找到非限定属性的地方。一个常见的地方是 Project 对象上。但是,在代码块内部的这些非限定属性和方法还有一个替代来源:代码块的委托对象

为了帮助解释这个概念,考虑上一节的最后一个例子

copy {
    into layout.buildDirectory.dir("tmp")
    from 'custom-resources'
}

此示例中的所有方法和属性都是非限定的。您可以在Project API 文档中轻松找到 copy()layout,但是 into()from() 呢?它们是针对 copy {} 块的委托解析的。该委托的类型是什么?您需要检查该 API 文档

根据代码块方法的签名,有两种方法可以确定委托类型

  • 对于 Action 参数,查看类型的参数。

    在上面的示例中,方法签名为 copy(Action<? super CopySpec>),括号中的部分告诉您委托类型——在本例中是 CopySpec

  • 对于 Closure 参数,文档将明确说明正在配置的类型或委托的类型(不同术语表示相同的事物)。

因此,您可以在 CopySpec 上找到 into()from()。您甚至可能会注意到这两种方法都有一个将 Action 作为最后一个参数的变体,这意味着您可以将代码块语法与它们一起使用。

所有新的 Gradle API 都声明 Action 参数类型而不是 Closure,这使得选择委托类型变得非常容易。即使是较旧的 API,除了旧的 Closure 之外,还有 Action 变体。

局部变量

def <name> = <value>        // Untyped variable
<type> <name> = <value>     // Typed variable
示例
def i = 1
String errorMsg = 'Failed, because reasons'

局部变量是一种 Groovy 构造——与额外属性不同——可用于在构建脚本中共享值。

避免在项目的根部使用局部变量,即作为伪项目属性。它们不能在构建脚本之外读取,并且 Gradle 对它们一无所知。

在更狭窄的上下文(例如配置任务)中,局部变量有时会有所帮助。