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 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函數。

@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函數:

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.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

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


這是打印的日誌:

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 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的構造函數看出來:

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}.
     * <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


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-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編譯應用程序過程。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章