千家信息网

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

发表于:2025-02-02 作者:千家信息网编辑
千家信息网最后更新 2025年02月02日,一句话概括Gradle自身源代码编译流程-用gradle来编译Gradle下面我们正式开始分析:因为我们拿到源代码后,首先接触的是gradlew.bat,也就是Gradle源代码自身编译的命令。所以,
千家信息网最后更新 2025年02月02日10. Gradle自身源代码编译流程

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

@if "%DEBUG%" == "" @echo off@rem ##########################################################################@rem@rem  Gradle startup script for Windows@rem@rem ##########################################################################@rem Set local scope for the variables with windows NT shellif "%OS%"=="Windows_NT" setlocalset DIRNAME=%~dp0if "%DIRNAME%" == "" set DIRNAME=.set APP_BASE_NAME=%~n0set 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.exeif defined JAVA_HOME goto findJavaFromJavaHomeset JAVA_EXE=java.exe%JAVA_EXE% -version >NUL 2>&1if "%ERRORLEVEL%" == "0" goto initecho.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 theecho location of your Java installation.goto fail:findJavaFromJavaHomeset JAVA_HOME=%JAVA_HOME:"=%set JAVA_EXE=%JAVA_HOME%/bin/java.exeif exist "%JAVA_EXE%" goto initecho.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 theecho location of your Java installation.goto fail:init@rem Get command-line arguments, handling Windows variantsif not "%OS%" == "Windows_NT" goto win9xME_args:win9xME_args@rem Slurp the command line arguments.set CMD_LINE_ARGS=set _SKIP=2:win9xME_args_slurpif "x%~1" == "x" goto executeset CMD_LINE_ARGS=%*:execute@rem Setup the command lineset 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 shellif "%ERRORLEVEL%"=="0" goto mainEnd:failrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead ofrem the _cmd.exe /c_ return code!if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1exit /b 1:mainEndif "%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函数。

@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这个类之后,比如打印了日志,如果要验证它的结果,需要首先执行几个步骤

  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函数:

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是:

wrapperJar: E:\work_space\gradle-source-from-csdn\gradle-3.1\gradle\wrapper\gradle-wrapper.jargradleUserHome: 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

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

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() {            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 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;            }        });    }


这是打印的日志:

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


也就是:

#Mon Sep 12 15:17:35 CEST 2016distributionBase=GRADLE_USER_HOMEdistributionPath=wrapper/distszipStoreBase=GRADLE_USER_HOMEzipStorePath=wrapper/distsdistributionUrl=https\://services.gradle.org/distributions/gradle-3.1-rc-1-bin.zip


配置文件位置可以在WrapperExecutor的构造函数看出来:

ublic static WrapperExecutor forProjectDirectory(File projectDir) {        return new WrapperExecutor(new File(projectDir, "gradle/wrapper/gradle-wrapper.properties"), new Properties());    }


c. 根据md5计算出zip文件的存放目录

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

/**     * This method computes a hash of the provided {@code string}.     * 

* The algorithm in use by this method is as follows: *

    *
  1. Compute the MD5 value of {@code string}.
  2. *
  3. Truncate leading zeros (i.e., treat the MD5 value as a number).
  4. *
  5. Convert to base 36 (the characters {@code 0-9a-z}).
  6. *
*/ 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


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));    }



打印日志如下:


BootstrapMainStarter gradleHome: D:\gradle_jar_cache\wrapper\dists\gradle-3.1-rc-1-bin\3uhcvxvcic1j9jh0j26e3y151\gradle-3.1-rc-1args[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编译应用程序过程。



0