执行构建的推荐方式是使用包装器。

20240402164524

包装器脚本会调用脚本中声明的指定版本的 Gradle,如有必要会预先下载。

20240403161642

包装器通过gradlewgradlew.bat文件的形式体现;使用包装器有以下益处:

  • 在指定版本的 Gradle 上标准化项目。
  • 为不同用户配置相同的 Gradle 版本
  • 为不同的执行环境(IDE、CI 服务器等)配置 Gradle 版本。

有三种使用包装器的方式:

  1. 创建新项目的同时添加包装器在该项目中。
  2. 在已经包含包装器的项目中使用包装器。
  3. 将包装器升级为新版本的 Gradle。

添加

生成包装器相关的文件需要本机上已经安装好 Gradle;庆幸的是,生成包装器相关的文件是一个一次性的过程

每个普通的构建都附带一个名为wrapper的内置任务;在使用列出任务命令(gradle tasks)时,该任务将列在 Build Setup tasks 组下,如下所示:

1
2
3
4
Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

执行gradle wrapper任务将会在项目目录下生成必须的包装器相关文件。

如果要使包装器对其他协同开发人员和执行环境可用,需要将相应的文件纳入到版本控制中;下面列出的文件均需要纳入版本控制:

1
2
3
4
5
6
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat

生成的 Wrapper 属性文件gradle/wrapper/gradle-wrapper.properties存储了关于发行版本的相关信息:

  • 托管发行版本的服务器。
  • 发行版本的类型;默认情况下,-bin类型的发行版只包含运行时,不包含示例代码和文档。
  • 用于执行构建的发行版本;默认情况下,gradle wrapper任务采用与执行该命令相同版本的 Gradle 来生成包装器的相关文件。
  • 下载发行版本时的超时设置(单位为毫秒),该设置是可选的
  • 验证发行版本的布尔值参数设置,该设置是可选的

以下是在gradle/wrapper/gradle-wrapper.properties中生成的发行版本 URL 的示例:

1
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip

上述所有的信息均可通过下面的gradle wrapper命令行选项进行配置:

  • --gradle-version:指定版本号,参数如下
  • --distribution-type:指定发行版本的类型,参数如下
    • all:包含二进制运行时及源码。
    • bin:仅包含二进制运行时,该参数为该选项默认值
  • --gradle-distribution-url,指定发行版本的下载地址;使用该选项会导致--gradle-version--distribution-type失效,可以使用该选项来干啥大陆环境由于网络原因下载发行版本下载速度缓慢的问题。
  • --gradle-distribution-sha256-sum:SHA-256 checksum,用于验证发行版本的合法性。
  • --network-timeout:设置下载发行版本的超时时间,默认值为1000毫秒
  • --no-validate-url:禁用对已配置的发行版本 URL 的验证。
  • --validate-url:启用对已配置的发行版本 URL的验证,默认启用该选项

如果发行版本的 URL 配置了--gradle-version--gradle-distribution-url,URL 为https协议时发送HEAD请求,为file协议则通过检查文件是否存在来验证 distribution URL。

举个栗子,使用如下命令:

1
gradle wrapper --gradle-distribution-url=https://services.gradle.org/distributions/gradle-8.7-all.zip

上述命令将使用8.7版本生成包装器,并下载源代码以便于 IDE 能正确定位到 Gradle 的源码。

生成的gradle/wrapper/gradle-wrapper.properties文件内容如下:

1
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip

生成的项目结构布局如下(以父子聚合项目为例):

1
2
3
4
5
6
7
8
9
10
.
├── a-subproject
│ └── build.gradle.kts
├── settings.gradle.kts
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat

通常为每个子项目生成一个settings.gradle(.kts)文件和一个build.gradle(.kts)文件;包装器相关的文件位于项目的gradle目录和根目录中。

  • gradle-wrapper.jar:包含下载发行版本代码的包装器jar文件。
  • gradle-wrapper.properties:包装器的属性定义文件,负责配置包装器的运行时行为,如:与此版本兼容的发行版本。需要注意的是,通用的设置(如,包装器的代理相关的配置)需要放到不同的文件中。
  • gradlew, gradlew.bat:用于使用包装器执行构建的 Shell 脚本和 Windows 批处理脚本;使用这两个脚本可以在不安装 Gradle 运行时的情况下使用包装器执行构建,但如果没有这两个文件又需要通过安装 Gradle 运行时来生成他们,或者从别的现成的安全可靠的项目中拷贝它们。

使用

始终建议使用 Wrapper 执行构建,以确保构建的可靠、受控和标准化执行。

根据操作系统的不同,可以运行gradlewgradlew.bat而不是gradle命令。

典型的 Gradle 调用:

1
gradle build

在 Linux 或 OSX 上调用包装器:

1
./gradlew build

在 Windows 上调用包装器:

1
.\gradlew.bat build

该命令在包装器所在的同一目录中运行;如果想在不同的目录下运行命令,使用包装器的相对路径即可。

如果包装器属性配置文件中的发行版在本机不存在,则包装器下载它并将其存储在本地文件系统中。

只要包装器属性配置文件中的发型版本 URL 不变,任何后续的构建调用都重用本地已经下载好的发行版本。

更新

  • 方式1:直接更新包装器的属性配置文件gradle-wrapper.properties中的发行版本 URL。
  • 方式2(推荐):使用包装器的脚本来执行wrapper任务,使用包装器任务可确保对包装器脚本的任何优化都应用于项目上;使用这种方式需要注意以下几点
    • 通过包装器脚本来更新包装器会修改包装器相关的文件,因此,执行此操作要确保包装器的相关文件纳入版本控制中

    • 执行包装器任务只会更新gradle-wrapper.properties,但不会更改gradle-wrapper.jar,这通即使使用旧的包装器文件,也可以运行最新的发型版本。

    • 如果想要把包装器的相关文件全部更新到最新版本,需要再次执行wrapper任务。

    • 使用栗子:

      1. 更新至最新版本

        1
        ./gradlew wrapper --gradle-version latest
      2. 更新至指定版本

        1
        ./gradlew wrapper --gradle-version 8.7
      3. 一旦更新了包装器之后,使用包装器脚本确认版本

        1
        ./gradlew --version

      (可选)再次运行包装器任务来下载发行版本的二进制文件并更新gradlewgradlew.bat文件。

定制

包装器的的默认运行时行为适用于大多数用户。但项目规范、安全限制或个人喜好则需要进行定制。

大部分的配置选项都通过Wrapper类暴露出来。

举个栗子,现有build.gradle.kts配置如下:

1
2
3
tasks.wrapper {
distributionType = Wrapper.DistributionType.ALL
}

上述配置在执行./gradlew wrapper --gradle-version 8.7将会在包装器属性配置文件中生成带-all后缀的发行版本 URL:

1
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip

更多的配置参考 Wrapper Task API 文档。

配置下载认证

包装器可以通过 HTTP Basic 身份认证从受保护的私有服务器上下载自行托管的发行版本。

有两种方式可以指定 HTTP Basic 认证的用户名和密码:

  1. 在系统属性配置用户名和密码(系统属性可在当前系统用户的家目录下的.gradle/gradle.properties文件中或其他方式配置),如下:

    1
    2
    systemProp.gradle.wrapperUser=username
    systemProp.gradle.wrapperPassword=password
  2. 直接将用户名和密码嵌入到gradle/wrapper/gradle-wrapper.properties文件的发行版本 URL 中(该文件需要提交到版本控制)

    1
    distributionUrl=https://username:password@somehost/path/to/gradle-distribution.zip

    这种共享用户名和密码的方式应仅在可控的环境中采用,有一定的安全隐患!

方式1优先于方式2
由于使用 HTTP Basic 身份验证,用户凭据以明文形式发送,应当采用HTTPS而不是HTTP

检查发行版本

通过 SHA-256 checksum比较来验证下载的发行版本可以有效的防止中间人攻击者篡改下载的发行版本来提高针对目标攻击的安全性。

若要启用此功能,需要下载与想要验证的发行版本关联的.sha256文件。

下载检查文件

可以从以下几个地方下载发行版本的.sha256验证文件。

.sha256验证文件的格式是单行文本,内容为对应的zip文件的 SHA-256 散列哈希值。

配置检查属性

两种方式:

  1. gradle-wrapper.properties文件中配置distributionSha256Sum属性:

    1
    distributionSha256Sum=371cb9fbebbe9880d147f59bab36d61eee122854ef8c9ee1ecf12b82368bcf10
  2. 使用命令行参数:--gradle-distribution-sha256-sum

如果配置的 SHA-256 哈希值和与托管发行版本的服务器上的 SHA-256 哈希值不匹配,构建将会失败。
只会在下载本机上不存在的发行版本时才会执行这个校验。

验证

包装器 Jar 是一个二进制文件,将在开发人员和构建服务器的计算机上执行;因此与所有此类文件一样,应当在执行这类文件确保其可靠性

由于包装器相关的文件会被纳入到项目的版本控制系统中,因此恶意攻击者可能通过提交仅升级发行版本的拉取请求,用修改后的恶意文件替换原始文件。

为了验证包装器 Jar 的完整性,官方创建了一个GitHub Action,可以根据已知良好的校验和列表自动检查拉取请求中的包装器 Jar

官方开发团队还会发布所有版本的checksum和(3.34.0.2版本除外,这几个版本不会生成 Jar),因此可以通过checksum手动验证包装器 Jar的完整性。

自动验证

如果项目托管在 Github 上,可以将官方提供的Github Action应用到被托管项目中自动验证包装器 Jar 文件的完整性。

手动验证

不同操作系统手动验证包装器 Jar 完整性的操作命令如下:

  • Linux:

    1
    2
    3
    4
    cd gradle/wrapper
    curl --location --output gradle-wrapper.jar.sha256 https://services.gradle.org/distributions/gradle-{gradleVersion}-wrapper.jar.sha256
    echo " gradle-wrapper.jar" >> gradle-wrapper.jar.sha256
    sha256sum --check gradle-wrapper.jar.sha256

    校验通过输出:

    1
    gradle-wrapper.jar: OK
  • Mac OS:

    1
    2
    3
    4
    cd gradle/wrapper
    curl --location --output gradle-wrapper.jar.sha256 https://services.gradle.org/distributions/gradle-{gradleVersion}-wrapper.jar.sha256
    echo " gradle-wrapper.jar" >> gradle-wrapper.jar.sha256
    shasum --check gradle-wrapper.jar.sha256

    校验通过输出:

    1
    gradle-wrapper.jar: OK
  • Windows:

    1
    2
    3
    $expected = Invoke-RestMethod -Uri https://services.gradle.org/distributions/gradle-8.7-wrapper.jar.sha256
    $actual = (Get-FileHash gradle\wrapper\gradle-wrapper.jar -Algorithm SHA256).Hash.ToLower()
    @{$true = 'OK: Checksum match'; $false = "ERROR: Checksum mismatch!`nExpected: $expected`nActual: $actual"}[$actual -eq $expected]

    校验通过输出:

    1
    OK: Checksum match

问题诊断

如果checksum和预期的不匹配,则可能是包装器任务未与要升级至的发行版本一起执行

此时应首先检查实际的checksum和是否与其他的发型版本匹配。

以下是可以在主流操作系统上生成包装器 Jar 实际的checksum命令:

  • Linux:

    1
    sha256sum gradle/wrapper/gradle-wrapper.jar

    输出:

    1
    d81e0f23ade952b35e55333dd5f1821585e887c6d24305aeea2fbc8dad564b95 gradle/wrapper/gradle-wrapper.jar
  • Mac OS:

    1
    shasum --algorithm=256 gradle/wrapper/gradle-wrapper.jar

    输出:

    1
    d81e0f23ade952b35e55333dd5f1821585e887c6d24305aeea2fbc8dad564b95 gradle/wrapper/gradle-wrapper.jar
  • Windows:

    1
    (Get-FileHash gradle\wrapper\gradle-wrapper.jar -Algorithm SHA256).Hash.ToLower()

    输出:

    1
    d81e0f23ade952b35e55333dd5f1821585e887c6d24305aeea2fbc8dad564b95

然后将生成的checksumhttps://gradle.org/release-checksums/页面上列出的进行比对,如果生成的checksum能在该页面找到,那么当前使用的包装器 Jar 是合法的!

此时如果这个合法的包装器 Jargradle/wrapper/gradle-wrapper.properties定义的发行版本不一致,再次执行任务命令wrapper来更新包装器 Jar 是没有什么安全隐患的。

如果未在https://gradle.org/release-checksums/页面上找到与当前生成的相匹配的checksum,虽然这个包装器 Jar可能是日更、候选发行版本,但在确认它的有效性之前,务必把它认为是不可靠的发行版本的包装器 Jar