10. Gradle自身源代码编译流程

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

一句话概括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

wKiom1kg-16h2kVhAAAeE-eu6Hg942.png-wh_50



所以这里有个小的细节要提醒下大家,大家修改完GradleWrapperMain这个类之后,比如打印了日志,如果要验证它的结果,需要首先执行几个步骤

  1. gradlew.bat assemble

  2. 进行编译把编译出来的gradle-wrapper.jar覆盖到gradle\wrapper\目录下。

  3. 再执行一个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,如需转载请自行联系原作者






相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
Android开发
IDEA编译gradle提示This version of the Android Support plugin for IntelliJ IDEA (or Android Studio) cannot open this project, please retry with version 2020.3.1 or newer.
IDEA编译gradle提示This version of the Android Support plugin for IntelliJ IDEA (or Android Studio) cannot open this project, please retry with version 2020.3.1 or newer.
907 1
|
Java 开发工具 Android开发
【错误记录】Android Studio 编译报错 ( Invalid Gradle JDK configuration found )
【错误记录】Android Studio 编译报错 ( Invalid Gradle JDK configuration found )
991 0
【错误记录】Android Studio 编译报错 ( Invalid Gradle JDK configuration found )
|
4月前
|
Android开发
解决Android、Flutter编译时Gradle报错:javax.net.ssl.SSLException: Connection reset
解决Android、Flutter编译时Gradle报错:javax.net.ssl.SSLException: Connection reset
478 0
|
6月前
|
C# Android开发 开发者
Android gradle编译时字节码处理
Android gradle编译时字节码处理
94 1
|
7月前
|
Java Apache Maven
在STS里使用Gradle编译Apache POI5.0.0
在STS里使用Gradle编译Apache POI5.0.0
66 0
|
7月前
|
Java Linux 开发工具
Azkaban【部署 01】Linux环境 CentOS Linux release 7.5.1804安装配置azkaban-3.70.0编译阶段(附安装包及gradle-4.6资源)
【2月更文挑战第4天】Linux环境 CentOS Linux release 7.5.1804安装配置azkaban-3.70.0编译阶段(附安装包及gradle-4.6资源)
148 1
|
7月前
|
开发工具 Android开发 开发者
Android 项目编译 Gradle 配置说明
Android 项目编译 Gradle 配置说明
357 0
|
Java 数据库连接 API
Gradle依赖管理:编译时和运行时依赖的区别
Gradle依赖管理:编译时和运行时依赖的区别
119 0
|
Java 开发工具 Maven
Android 编译 gradle 内存 OOM 解决之路(二)
Android 编译 gradle 内存 OOM 解决之路
|
Java Android开发
Android 编译 gradle 内存 OOM 解决之路(一)
Android 编译 gradle 内存 OOM 解决之路