您好,登錄后才能下訂單哦!
一句話概括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
所以這里有個小的細節要提醒下大家,大家修改完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函數:
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編譯應用程序過程。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。