gradle-wrapper

gradle wrapper 的 wrapper 我认为可以作为“容器”。它封装了所需要的构建工具的版本和配置信息,基于 JVM 跨平台运行。一次发布(distribution, dist for short),不同开发者在不同平台上运行,都能自动初始化相同环境、构建相同的结果,极大地减少了对开发者环境配置的依赖。这与容器设计的思想是相通的。

1. 发布的 gradle wrapper 文件组织结构

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

gradlew / gradlew.bat 分别是 shell 和 windows 批处理运行脚本。命令行运行脚本,会依次检查 java、初始化运行:

  1. 设置工作目录,添加 gradle-wrapper.jar 到 CLASSPATH
  2. 检测 JAVA_HOME,获取 java
  3. 执行下载、安装 gradle,并运行:
    1
    java -classpath ./gradle/wrapper/gradle-wrapper.jar org.gradle.wrapper.GradleWrapperMain

gradle-wrapper.properties 配置了 gradle 发布版本和本地安装目录:

1
2
3
4
5
6
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

distributionBase / zipStoreBase 仅支持2个枚举字符串常量:

  • GRADLE_USER_HOME 使用当前用户的 ~/.gradle/ 作为本地安装目录
  • PROJECT 使用当前工程的 rootDir 作为本地安装目录

同时,开发者也可以在系统环境变量里添加 GRADLE_USER_HOME,自定义本地安装目录。

2. gradlew 如何查找、下载目标版本的 gradle

gradle-wrapper.jar 源码[^1] 很简单,主要流程如下:
gradlew-run-GradleWrapperMain

  1. 查找本地安装根目录
  2. 下载状态检查(markerFile .zip.ok) 和文件锁(.zip.lck)并发控制
  3. 获取zip包下载到、和解压到路径 distZip / distDir
  4. 下载、解压安装,设置运行权限
  5. 启动构建

查找本地安装目录优先级

  1. gradle-wrapper.properties 配置 distributionBase=GRADLE_USER_HOME | PROJECT
  2. 环境变量 $GRADLE_USER_HOME/
  3. 系统变量 ${gradle.user.home}/
  4. 系统变量 ${user.home}/.gradle/

获取zip包下载到、和解压到路径 distZip / distDir

distXXX 路径由下面几段组成:

  1. gradle 本地安装目录 gradleUserHome
  2. 安装路径 path
  3. 版本 hash 路径 rootDir
  4. 安装包名 baseName / 解压路径 distName

以上文 gradle-wrapper.properties 配置为例:

gradleUserHome

1
distributionBase=GRADLE_USER_HOME

base 类型为 GRADLE_USER_HOME,使用默认安装目录:

1
~/.gradle/

path

1
distributionPath=wrapper/dists

配置使用了默认路径 wrapper/dists:

1
~/.gradle/wrapper/dists/

版本 hash 路径 rootDir

1
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
1
2
3
distName = 'gradle-2.14.1-all'
baseName = 'gradle-2.14.1-all.zip'
distHash = base32_encode(hash(distributionUrl)) // '8bnwg5hd3w55iofp58khbp6yv'

相同版本、不同发布方 hash 不同,确保运行路径独立。

最终zip包下载到、和解压到路径 distZip / distDir

1
2
distZip=~/.gradle/wrapper/dists/gradle-2.14.1-all/8bnwg5hd3w55iofp58khbp6yv/gradle-2.14.1-all.zip
distDir=~/.gradle/wrapper/dists/gradle-2.14.1-all/8bnwg5hd3w55iofp58khbp6yv/

另外,下载过程中产生的中间文件:

1
2
3
~/.gradle/wrapper/dists/gradle-2.14.1-all/8bnwg5hd3w55iofp58khbp6yv/gradle-2.14.1-all.zip.lck
~/.gradle/wrapper/dists/gradle-2.14.1-all/8bnwg5hd3w55iofp58khbp6yv/gradle-2.14.1-all.zip.part
~/.gradle/wrapper/dists/gradle-2.14.1-all/8bnwg5hd3w55iofp58khbp6yv/gradle-2.14.1-all.zip.ok

.zip.lck 用于文件锁,控制并发下载。.zip.part 表示下载中,.zip.ok 表示下载成功。

最终解压文件:

1
~/.gradle/wrapper/dists/gradle-2.14.1-all/8bnwg5hd3w55iofp58khbp6yv/gradle-2.14.1

当 markFile .zip.ok 标志下载成功后,distDir 检测解压后子文件夹,有且只有一个。命名不一定和 distName 相同。

最终安装目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.
└── wrapper
└── dists
└── gradle-2.14.1-all
└── 8bnwg5hd3w55iofp58khbp6yv
├── gradle-2.14.1
│ ├── LICENSE
│ ├── NOTICE
│ ├── bin
│ ├── changelog.txt
│ ├── docs
│ ├── getting-started.html
│ ├── init.d
│ ├── lib
│ ├── media
│ ├── samples
│ └── src
├── gradle-2.14.1-all.zip
├── gradle-2.14.1-all.zip.lck
└── gradle-2.14.1-all.zip.ok

gradle 构建,命令行:

1
./wrapper/dists/gradle-2.14.1-all/8bnwg5hd3w55iofp58khbp6yv/gradle-2.14.1/gradle-2.14.1/bin/gradle

或者 gradlew 运行调用 lib:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* java -classpath gradleHome/lib/gradle-launcher-2.14.1.jar org.gradle.launcher.GradleMain <args>
*/
public class BootstrapMainStarter {
public void start(String[] args, File gradleHome) throws Exception {
File gradleJar = findLauncherJar(gradleHome);
URLClassLoader contextClassLoader = new URLClassLoader(new URL[] {
gradleJar.toURI().toURL()
}, ClassLoader.getSystemClassLoader().getParent());
Thread.currentThread().setContextClassLoader(contextClassLoader);
Class < ? > mainClass = contextClassLoader.loadClass("org.gradle.launcher.GradleMain");
Method mainMethod = mainClass.getMethod("main", String[].class);
mainMethod.invoke(null, new Object[] {
args
});
if (contextClassLoader instanceof Closeable) {
((Closeable) contextClassLoader).close();
}
}

See Also

[1] GitHub gradle wrapper main/src, https://github.com/gradle/gradle/blob/master/subprojects/wrapper/src/main/java/org/gradle/wrapper/