一句话概括Gradle自身源代码编译流程-用gradle来编译Gradle
下面我们正式开始分析:
因为我们拿到源代码后,首先接触的是gradlew.bat,也就是Gradle源代码自身编译的命令。所以,我们还是从这个脚本开始分析。
一. Eclipse打开源代码
为了方便修改代码,我选择用Eclipse来打开这个工程。步骤是:
File->New->Java Project->Use default location去掉勾选->Browse选择Gradle源代码目录->finish
二. gradlew.bat脚本
1. 还是从我们编译Gradle源代码的命令入手
gradlew.bat assemble
那首先来看下gradlw.bat :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=-Xmx1024m -Dfile.encoding=UTF-8
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
|
执行gradlew.bat assemble时,首先来看看这个脚本里面的各个变量值:
CLASSPATH:gradle-3.1\\gradle\wrapper\gradle-wrapper.jar
表示的是gradle源代码里面gralde\wrapper\目录下gradle-wrapper.jar,这个jar也是待会要执行的编译操作要运行的jar。
DEFAULT_JVM_OPTS:-Xmx1024m -Dfile.encoding=UTF-8 表示的是Java虚拟机配置
JAVA_OPTS:空
GRADLE_OPTS:空
CMD_LINE_ARGS:assemble 表示要执行的gradle task名字
然后接下来,就会执行重要的一句,启动gradle-wrapper.jar里面的GradleWrapperMain.main函数。
1
2
|
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
那这里可能有个疑问,就是此时Gradle源代码还没有编译出来,哪来的gradle-wrapper.jar。这个问题就像鸡和蛋的问题,先有鸡还是先有蛋。
Gradle的做法是先有鸡后有蛋,那第一只鸡哪来的呢? Gradle是自己给它造了一只鸡。
请看gradle\wrapper\gradle-wrapper.jar
所以这里有个小的细节要提醒下大家,大家修改完GradleWrapperMain这个类之后,比如打印了日志,如果要验证它的结果,需要首先执行几个步骤
gradlew.bat assemble
进行编译把编译出来的gradle-wrapper.jar覆盖到gradle\wrapper\目录下。
再执行一个gradlew.bat assemble就可以在命令行里面验证。
三. GradleWrapperMain
文件路径:
gradle-3.1\subprojects\wrapper\src\main\java\org\gradle\wrapper\GradleWrapperMain.java
GradleWrapperMain位置subprojects里面,Gradle源代码把各个工程拆分成各个模块,类似于插件的方式。
也就是说每个功能都拆分成一个插件,然后使用的时候进行配置,比如某个插件需要依赖于哪几个插件,那就直接配置上就可以。
配置的路径在每个插件的jar包里面,名称叫做xxx-classpath.properties,里面有个projects属性,配置了这个插件依赖的插件(也可以叫项目或者模块)。
这种设计思想可以让整个项目层次清晰,同时便于多个团队间合作开发。
看下GradleWrapperMain的main函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public
class
GradleUserHomeLookup {
public
static
final
String DEFAULT_GRADLE_USER_HOME = System.getProperty(
"user.home"
) +
"/.gradle"
;
public
static
final
String GRADLE_USER_HOME_PROPERTY_KEY =
"gradle.user.home"
;
public
static
final
String GRADLE_USER_HOME_ENV_KEY =
"GRADLE_USER_HOME"
;
public
static
File gradleUserHome() {
String gradleUserHome;
if
((gradleUserHome = System.getProperty(GRADLE_USER_HOME_PROPERTY_KEY)) !=
null
) {
return
new
File(gradleUserHome);
}
if
((gradleUserHome = System.getenv(GRADLE_USER_HOME_ENV_KEY)) !=
null
) {
return
new
File(gradleUserHome);
}
return
new
File(DEFAULT_GRADLE_USER_HOME);
}
}
public
static
void
main(String[] args)
throws
Exception {
File wrapperJar = wrapperJar();
System.out.println(
"wrapperJar: "
+ wrapperJar);
File propertiesFile = wrapperProperties(wrapperJar);
File rootDir = rootDir(wrapperJar);
...
File gradleUserHome = gradleUserHome(options);
System.out.println(
"gradleUserHome: "
+ gradleUserHome +
" rootDir: "
+ rootDir
+
" options: "
+ options+
"propertiesFile: "
+ propertiesFile);
addSystemProperties(gradleUserHome, rootDir);
Logger logger = logger(options);
WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile);
wrapperExecutor.execute(
args,
new
Install(logger,
new
Download(logger,
"gradlew"
, wrapperVersion()),
new
PathAssembler(gradleUserHome)),
new
BootstrapMainStarter());
}
|
打印的log是:
1
2
|
wrapperJar: E:\work_space\gradle-source-from-csdn\gradle-3.1\gradle\wrapper\gradle-wrapper.jar
gradleUserHome: D:\gradle_jar_cache rootDir: E:\work_space\gradle-source-from-csdn\gradle-3.1 options: options: , extraArguments: 'assemble', removedOptions: propertiesFile: E:\work_space\gradle-source-from-csdn\gradle-3.1\gradle\wrapper\gradle-wrapper.properties
|
这个日志已经很清楚的说明了各个变量的值。
需要说明的一点是gradleUserHome,这个是Gradle下载其他Jar包的存放地址,默认是c盘的user/xxx/.gradle/目录。
但是这个目录是可以配置的,配置GRADLE_USER_HOME环境变量即可。
这点从上面代码getUserHome可以清楚的看到。
另外,程序的最后面Gradle执行WrapperExecutor.execute(xxxx)方法,这个比较关键。
四. WrapperExecutor.execute
1. execute
1
2
3
4
|
public
void
execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter)
throws
Exception {
File gradleHome = install.createDist(config);
bootstrapMainStarter.start(args, gradleHome);
}
|
可以看到execute里面没有什么东西,调用的是传入的install.createDist和bootstrapMainStarter.start方法,所以,需要分析下这两个方法。
2. Install.createDist
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
new
Install(logger,
new
Download(logger,
"gradlew"
, wrapperVersion()),
new
PathAssembler(gradleUserHome))
public
File createDist(
final
WrapperConfiguration configuration)
throws
Exception {
final
URI distributionUrl = configuration.getDistribution();
final
String distributionSha256Sum = configuration.getDistributionSha256Sum();
final
PathAssembler.LocalDistribution localDistribution = pathAssembler.getDistribution(configuration);
final
File distDir = localDistribution.getDistributionDir();
final
File localZipFile = localDistribution.getZipFile();
System.out.println(
"distributionUrl: "
+ distributionUrl +
" distributionSha256Sum: "
+ distributionSha256Sum
+
" localDistribution: "
+ localDistribution
+
" distDir: "
+ distDir
+
" localZipFile: "
+ localZipFile);
return
exclusiveFileAccessManager.access(localZipFile,
new
Callable<File>() {
public
File call()
throws
Exception {
final
File markerFile =
new
File(localZipFile.getParentFile(), localZipFile.getName() +
".ok"
);
if
(distDir.isDirectory() && markerFile.isFile()) {
return
getAndVerifyDistributionRoot(distDir, distDir.getAbsolutePath());
}
boolean
needsDownload = !localZipFile.isFile();
if
(needsDownload) {
File tmpZipFile =
new
File(localZipFile.getParentFile(), localZipFile.getName() +
".part"
);
tmpZipFile.delete();
logger.log(
"Downloading "
+ distributionUrl);
download.download(distributionUrl, tmpZipFile);
tmpZipFile.renameTo(localZipFile);
}
List<File> topLevelDirs = listDirs(distDir);
for
(File dir : topLevelDirs) {
logger.log(
"Deleting directory "
+ dir.getAbsolutePath());
deleteDir(dir);
}
verifyDownloadChecksum(configuration.getDistribution().toString(), localZipFile, distributionSha256Sum);
logger.log(
"Unzipping "
+ localZipFile.getAbsolutePath() +
" to "
+ distDir.getAbsolutePath());
unzip(localZipFile, distDir);
File root = getAndVerifyDistributionRoot(distDir, distributionUrl.toString());
setExecutablePermissions(root);
markerFile.createNewFile();
return
root;
}
});
}
|
这是打印的日志:
1
|
distributionUrl: https://services.gradle.org/distributions/gradle-3.1-rc-1-bin.zip distributionSha256Sum: null localDistribution: org.gradle.wrapper.PathAssembler$LocalDistribution@4a574795 distDir: D:\gradle_jar_cache\wrapper\dists\gradle-3.1-rc-1-bin\3uhcvxvcic1j9jh0j26e3y151 localZipFile: D:\gradle_jar_cache\wrapper\dists\gradle-3.1-rc-1-bin\3uhcvxvcic1j9jh0j26e3y151\gradle-3.1-rc-1-bin.zip
|
这里有几个问题:
a. 下载zip包
createDist其实就是去下载distributionUrl描述的zip包。
其实这还是个鸡和蛋的问题,Gradle源代码是用Gradle来编译的,现在我们只有源代码,那怎么编译呢?
所以就只能先从服务器上把Gradle zip包下载下来。
b. distributionUrl定义位置
gradle源代码根目录/gradle/wrapper/gradle-wrapper.properties
也就是:
1
2
3
4
5
6
|
#Mon Sep 12 15:17:35 CEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-rc-1-bin.zip
|
配置文件位置可以在WrapperExecutor的构造函数看出来:
1
2
3
|
ublic
static
WrapperExecutor forProjectDirectory(File projectDir) {
return
new
WrapperExecutor(
new
File(projectDir,
"gradle/wrapper/gradle-wrapper.properties"
),
new
Properties());
}
|
c. 根据md5计算出zip文件的存放目录
1
|
distDir: D:\gradle_jar_cache\wrapper\dists\gradle-3.1-rc-1-bin\3uhcvxvcic1j9jh0j26e3y151
|
distDir目录,也就是下载下来的zip文件存放目录有一串字符串,这是根据md5算出来的,保证唯一性,代码如下:
文件路径:
subprojects\wrapper\src\main\java\org\gradle\wrapper\PathAssembler.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* This method computes a hash of the provided {@code string}.
* <p>
* The algorithm in use by this method is as follows:
* <ol>
* <li>Compute the MD5 value of {@code string}.</li>
* <li>Truncate leading zeros (i.e., treat the MD5 value as a number).</li>
* <li>Convert to base 36 (the characters {@code 0-9a-z}).</li>
* </ol>
*/
private
String getHash(String string) {
try
{
MessageDigest messageDigest = MessageDigest.getInstance(
"MD5"
);
byte
[] bytes = string.getBytes();
messageDigest.update(bytes);
return
new
BigInteger(
1
, messageDigest.digest()).toString(
36
);
}
catch
(Exception e) {
throw
new
RuntimeException(
"Could not hash input string."
, e);
}
}
|
d. 执行时机
在第一次执行gradlew.bat assemble的时候会去下载,然后解压。
所以第一次执行gradlew.bat assemble会出现这样日志:
Downloading xxxx.......
Unzipping.....
3. bootstrapMainStarter.start
文件路径:
subprojects\wrapper\src\main\java\org\gradle\wrapper\BootstrapMainStarter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public
void
start(String[] args, File gradleHome)
throws
Exception {
System.out.println(
"BootstrapMainStarter gradleHome: "
+ gradleHome);
if
(args !=
null
) {
for
(
int
i =
0
; i< args.length; i++) {
System.out.println(
"args["
+ i+
"]= "
+ args[i]);
}
}
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();
}
}
private
File findLauncherJar(File gradleHome) {
for
(File file :
new
File(gradleHome,
"lib"
).listFiles()) {
if
(file.getName().matches(
"gradle-launcher-.*\\.jar"
)) {
return
file;
}
}
throw
new
RuntimeException(String.format(
"Could not locate the Gradle launcher JAR in Gradle distribution '%s'."
, gradleHome));
}
|
打印日志如下:
1
2
|
BootstrapMainStarter gradleHome: D:\gradle_jar_cache\wrapper\dists\gradle-3.1-rc-1-bin\3uhcvxvcic1j9jh0j26e3y151\gradle-3.1-rc-1
args[0]= assemble
|
那么现在来解释下上面这段代码是在干什么:
a. 从gradle bin下载解压目录的lib文件夹找到gradle-launcher-.*\\.jar。
b. 然后执行launcher-xxx.jar包里面的org.gradle.launcher.GradleMain.main函数。
c. 同时,把我们输入的参数assemble传入进去。
那么接下去执行的就是我们下载的gradle里面的launcher-xxx.jar的函数了,不是我们源代码里面的,这点要特别区分清楚。
执行下载的gradle里面的代码来编译Gradle源代码的过程,就像gradle编译其他程序一样。
所以才说Gradle源代码的编译过程就是用gradle来编译Gradle.
这个地方就是之前我们说过的gradlew.bat和gradle.bat的区别。
d. gradlew.bat和gradle.bat的区别
gradlew.bat是Gradle源代码自身编译时候的bat脚本
加载的是当前目录下 gralde/wrapper/gradle-wrapper.jar包,执行的是GradleWrapperMain.main方法。
然后去下载gradle bin,再解压。然后执行gradle lib里面的gradle-launcher-xxx.jar里面的GradleMain.main函数。
gradle.bat是gradle编译其他程序的bat脚本
加载的是gradle lib里面gradle-launcher-xxx.jar里面的GradleMain.main函数。
e. 关于gradlew.bat命名的思考
我在思考为什么编译Gradle自身源代码的脚本要叫gradlew.bat呢?和编译其他程序的脚本gradle.bat只有一字之差?"w"代表的意思是什么呢?
gradlew.bat做的事情是编译Gradle源代码自身,那么叫叫gradle_compile_self.bat比较直观点?
也许,我们可以理解"w"是wrapper的缩写,因为两个脚本加载的Jar就是这样的区别;而且wapper做的事情真的就是包装的工作。比如它只是去下载gradle bin,然后就直接调用gradle bin的grale-launcher-xxx.jar了。
所以,也许这个就是Gradle团队对于"w"的理解吧。。
接下来的话,Gradle自身源代码的编译过程就和一般程序的编译过程一样了;我们再分析gradle编译应用程序过程。
本文转自rongwei84n 51CTO博客,原文链接:http://blog.51cto.com/483181/1927683,如需转载请自行联系原作者