本文來自于CSDN博客,作者:晏博,已獲授權,版權歸原作者所有,未經作者同意,請勿轉載。
背景
陸陸續續一年多,總是有人問 Gradle 構建,總是發現很多人用 Gradle 是迷糊狀態的,于是最近準備來一個“Gradle 庖丁解牛”系列,一方面作為自己的總結,一方面希望真的能達到標題所示效果,同時希望通過該系列達到珍惜彼此時間的目的,因為目前市面上關于 Gradle 的教程都是在教怎么配置和怎么編寫插件,很少有說明 Gradle 自己到底是個啥玩意的,還有是如何工作的,本系列以官方 release 3.4 版本為基礎。
廢話不多說,標題也表明了本篇所總結的內容 —— 構建源頭源碼淺析,不懂啥叫 Gradle 和 Groovy 的請先移步我以前文章《Groovy 腳本基礎全攻略》和《Gradle 腳本基礎全攻略》,免得這一系列也看不明白咋回事。不過還是要先打個預防針,竟然還有人覺得 Gradle 只能構建 Android App,這 TM 就尷尬了,我想說 Gradle 就是一套腳手架,你想干啥玩意都可以,就看你想不想干,他就是個自動化構建框架,你整明白它的核心以后,他的插件你只用依據規則使用即可,譬如用來構建 Android 的 apply plugin: ‘com.android.application’ 只是它的一個小插件而已,實質就看你想搞啥了,Java、Web、JS、C 等等都可以用它構建;而整明白它原理核心之一就是你得先整明白他的規則,也就是本文所總結討論的內容。
基礎技能叨叨敘
所謂基礎技能就是一些鋪墊,既然要說 Gradle 構建系列了,我們有必要知道 Gradle 相關的一些基本命令,方便下面演示時使用(注意:如下命令是針對安裝 gradle 并配置環境變量的情況下執行,你如果做 Android 開發的話,Ubuntu 直接將如下所有 gradle 替換為 ./gradlew 即可,這樣就可以用包裝特性了,至于包裝是咋回事后面會說),關于其他命令請呼叫 help 或者查看官方 User Guide 的 Using the Gradle Command-Line。
看完和本篇相關的命令總結后來看看前面提到的 gradle 替換 gradlew 執行是咋回事,其實實質可以查看官方 User Guide 的 The Gradle Wrapper,這里給出總結如下:
其實上面腳本的作用就是對 Gradle 的一個包裝,保證任何機器都可以運行這個構建,即使這個機器沒有安裝 Gradle 或者安裝的 Gradle 版本不兼容匹配,如果沒裝或者不兼容就會根據 gradle-wrapper.properties 里的配置下載特定版本,下載的包放在 $USER_HOME/.gradle/wrapper/dists 目錄下,所以我們有時候第一次通過 gradlew 包裝腳本執行構建時總會看到正在下載一個 zip 文件,實質就是在下 gradle-wrapper.properties 中指定的 zip 包。
gradlew 有一個非常人性化和牛逼的特點,解決了幾種坑爹的問題,譬如團隊開發保證 Gradle 構建版本一致性、gradle-wrapper.properties 下載地址方便切換到公司內部服務器等(甚至可以先通過 gradle.properties 文件配置公司服務器的帳號密碼或者驗證信息,然后在 gradle-wrapper.properties 中配置distributionUrl=https://username:password@somehost/path/to/gradle-distribution.zip,這樣就可以達到公司內網加認證下載的雙重安全了)。
gradlew 由來的最終原理其實是通過 Gradle 預置的 Wrapper (繼承自 DefaultTask )來實現的(AS 內部默認實現了自動生成而已),譬如:
運行 gradle createWrapper 就能生成上面描述的幾個文件。而關于 Gradle 的 Wrapper 可以參考 DSL Reference 的 Wrapper 和 JavaDoc 的 Wrapper API,里面提供了一堆屬性設置下載地址、解壓路徑、 gradleVersion 等,你也可以在 distributionUrl 中通過 ${gradleVersion} 來使用你設置的 DSL 變量,這里暫時不再展開。
說完命令我們接著說說 Groovy、DSL、Gradle 之間的關系,首先一定要明確,Groovy 不是 DSL,而是通用的編程語言,類似 Java、C++ 等,就是一種語言;但 Groovy 對編寫 DSL 提供了很牛逼的支持,這些支持都源自 Groovy 自己語法的特性,比如閉包特性、省略分號特性、有參方法調用省略括弧特性、屬性默認實現 getter、setter 方法特性等,當然,作為 Android 開發來說,Gradle 構建 Android 應用實質也是基于 Groovy 編寫的 DSL,DSL 存在的意義就在于簡化編寫和閱讀。
而 Gradle 的實質就是一個基于 Groovy 的框架了,也就是說我們得按照他的約束來玩了,和我們平時 Android 開發使用框架類似,一旦引入框架,我們就得按照框架的寫法來規規矩矩的編寫。Gradle 這個框架只負責流程約定,處理細節是我們自己的事,就像我們編譯 Android App 時基于 Gradle 框架流程引入 apply plugin: ‘com.android.application’ 構建插件一樣,具體做事是我們插件再約束的,插件又對我們簡化了配置,我們只用基于 Gradle 框架和相關插件進行構建配置。
了所以簡單粗暴的解釋就是, Groovy 是一門語言,DSL 就是一種特定領域的配置文件,Gradle 就是基于 Groovy 的一種框架,就像我們以前做 Android 開發使用 Ant 構建一樣,build.xml 就可以粗略的理解為 Ant 的 DSL 配置,所以我們編寫 build.xml 時會相對覺得挺輕松(和后來的 Gradle 還是沒法比的)。
搞清了他們之間的關系后我們就要深入看看為啥是這樣了,因為關于 Gradle 構建現在中文網上的教程要么是教你如何配置 DSL 屬性,要么就是教你如何使用 Groovy 去拓展自己的特殊 task,或者是教你怎么編寫插件,卻幾乎沒人提到 Gradle 這個構建框架的本質,所以使用起來總是心里發慌,有種不受控制的感覺。
Gradle 源碼源頭分析
還記得我們前一小節講的 gradlew 么,追蹤溯源就從它開始,以前我也是出于好奇就去看了下它,發現這個 shell 腳本前面其實就是干了一堆沒事干的事,大招就在它的最后一句,如下:
對的,大招就是 GradleWrapperMain,這貨還支持通過 “$@” 傳遞參數,想想我們平時執行的 gradlew 命令,這下明白了吧,既然這樣我們就拿他開涮,去看看他的源碼,如下:
上面 WrapperExecutor.forWrapperPropertiesFile 方法實質就是通過 java Properties 去解析 gradle/wrapper/gradle-wrapper.properties 文件里的配置,譬如 distributionUrl 等;接著執行 wrapperExecutor.execute 方法,args 參數就是我們執行 gradlew 腳本時傳遞的參數,Install 實例就是管理本地本項目是否有 wrapper 指定版本的 gradle,木有就去下載解壓等等,BootstrapMainStarter 實例就是 wrapper 執行 gradle 真實入口的地方,所以我們接著看看 execute 這個方法,如下:
到這里如果本地沒有 wrapper 包裝的 Gradle,就會下載解壓等,然后準備一堆貨,貨備足后就調用了前面說的 BootstrapMainStarter 的 start 方法,如下:
不解釋,快上車,真的 Gradle 要現身了,Wrapper 的使命即將終結,我們把重點轉到 org.gradle.launcher.GradleMain 的 main 方法,如下:
GG了,莫慌,我們的重點不是看懂 Gradle 的每一句代碼,我們需要撿自己需要的重點,這貨設置各種 ClassLoader 后最終還是調用了 org.gradle.launcher.Main 的 run 方法,實質就是 EntryPoint 類的 run 方法,因為 Main 類是 EntryPoint 類的實現類,而 EntryPoint 的 run 方法最主要做的事情就是創建了一個回調監聽接口,然后調用了 Main 重寫的 doAction 方法,所以我們去到 Main 的 doAction 看看,如下:
這貨實質調用了 CommandLineActionFactory 實例的 convert 方法得到 Action 實例,然后調用了 Action 的 execute 方法,我去,真特么繞的深,這彎溜的,我們會發現 CommandLineActionFactory 里的 convert 方法實質除過 log 記錄準備外干的惟一一件事就是創建其內部類 WithLogging 的對象,這時候我們可以發現 Action 的 execute 方法實質就是調用了 WithLogging 的 execute 實現,如下:
這不,最終還是執行了 new ExceptionReportingAction(
new JavaRuntimeValidationAction(
new ParseAndBuildAction(loggingServices, args)),
new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), loggingConfiguration, clientMetaData()))); 對象的 execute 方法(上面的 action 就是這個對象),關于這個對象的創建我們只用關注 new JavaRuntimeValidationAction(
new ParseAndBuildAction(loggingServices, args)) 這個參數即可,這也是一個 Action,實例化后在 ExceptionReportingAction 的 execute 調用了他的 execute,而 ParseAndBuildAction 的 execute 又被 JavaRuntimeValidationAction 的 execute 觸發,有點包裝模式的感覺,所以我們直接關注 ParseAndBuildAction 的實例化和 execute 方法,因為其他不是我們的重點,如下:
既然我們是追主線分析(關于執行命令中帶 help、version、gui 的情況我們就不分析了,也比較簡單,當我們執行 gradle –help 或者 gradle –gui 時打印的 help 或者彈出的 GUI 是 BuiltInActions 或者 GuiActionsFactory ,比較簡單,不作分析),我們看核心主線 BuildActionsFactory 的 createAction 方法和通過 createAction 方法生成的 Runnable 的 run 方法即可(所謂的主線就是我們執行 gradle taskName 命令走的流程,譬如 gradle asseambleDebug 等),如下是 BuildActionsFactory 的 createAction 方法:
既然上面都判斷最后調用都是類同的,那我們就假設調用了 runBuildInProcess 方法吧,如下:
此刻您可憋住了,別小看這么簡單的一個方法,這玩意麻雀雖小五臟俱全啊,在我第一次看這部分源碼時是懵逼的,好在看見了相關類的注釋才恍然大悟,至于為啥我們現在來分析下。先說說 globalServices 對象的構建吧,其實和 createGlobalClientServices() 這個方法類似,隨意咯,我們就隨便看個,如下:
看起來就是構造了一個 clientSharedServices 對象,然后交給下面的 runBuildAndCloseServices 方法使用,對的,就是這樣的,只是這個 createGlobalClientServices() 方法真的很懵逼,ServiceRegistryBuilder 的 build() 方法實質是實例化了一個 DefaultServiceRegistry 對象,然后通過構造方法傳遞了 parent(NativeServices.getInstance()) 實例,通過 addProvider(provider) 方法傳遞了 provider(new GlobalScopeServices(false)) 和 provider(new DaemonClientGlobalServices()) 實例,巧妙的地方就在 DefaultServiceRegistry 類的注釋上面,大家一定要先看注釋,ServiceRegistryBuilder 的 build() 最后調用的是 DefaultServiceRegistry 對象的 addProvider 方法,實質調用的是 DefaultServiceRegistry 的 findProviderMethods(provider) 方法,如下:
這時咱們先回過頭看看前面說的 createGlobalClientServices() 方法,我們發現其中的 NativeServices、FileSystemServices、DaemonClientGlobalServices 都沒有 config 方法,但是有一堆 createXXX 的方法,這些都會被加入 ownServices 列表,但是 GlobalScopeServices 類卻有 config 方法,如下:
到這里你可以暫時松口氣了,上面 config 等等一堆都是在做準備,說白了就是各種列表注冊都放好,然后 DefaultServiceRegistry 的 get(…) 系列方法實質都是通過 doGet(Type serviceType) 的 invok 方法調用等。接著讓我們把目光移到上面運行 gradle taskName 命令時分析的 BuildActionsFactory 的 runBuildInProcess(…) 方法,我們在該方法中調用 runBuildAndCloseServices(…) 方法時第三個參數傳遞的是 globalServices.get(BuildExecuter.class), 也就是調用了 DefaultServiceRegistry 的 get(BuildExecuter.class) 方法,剛剛說了 DefaultServiceRegistry 的 get(…) 實質就是 invoke 一個方法,在這里 serviceType 參數是 BuildExecuter.class,所以調用的就是 ToolingGlobalScopeServices 的 createBuildExecuter(…) 方法,ToolingGlobalScopeServices 是 LauncherServices 的 registerGlobalServices(…) 方法實例化的,而 LauncherServices implements PluginServiceRegistry 又是剛剛上面分析 GlobalScopeServices 的 configure(…) 方法里被實例化添加的,LauncherServices 的 registerGlobalServices(…) 方法也是在那調用的。
繞了一圈真是折騰,突然發現累覺不愛,覺得 Gradle 框架中 DefaultServiceRegistry 類的設計真的是挺顛覆我認知的,雖然沒啥黑科技,但是這個設計思路真的是坑爹,很容易繞懵逼,好在后來整明白了,真想說句 ZNMKD。好了,牢騷也發完了,下面該正題了,我們接著上面說的去看看 LauncherServices 內部 ToolingGlobalScopeServices 的 createBuildExecuter(…) 方法,一貫做法,關注主線,咱們會發現這方法最主要的就是層層簡單代理模式包裝實例化 BuildActionExecuter 對象,最關鍵的就是實例化了 InProcessBuildActionExecuter 對象,這個對象就一個 execute 方法(這個 execute 方法被調用的地方就在上面分析的 BuildActionsFactory 的 runBuildInProcess 方法返回的 RunBuildAction 對象的 run 方法中,RunBuildAction 對象的 run 方法又是前面分析的源頭觸發的),如下:
贊,到此真想說句真是不容易?。。?!總算看見光明了,我 Gradle 的大 GradleLauncher,為啥這么稱呼呢,因為你看了 GradleLauncher 實現你會有種柳暗花明又一村的感覺,真的,你會覺得前面這些復雜的初始化就是為了等到這個實例的到來,因為 GradleLauncher 實現里就真真切切的告訴你了 Gradle 構建的三大生命周期——-初始化、配置、執行,不信你看:
到此構建源頭就分析完了,下一篇會接著這里分析 DefaultGradleLauncher 及后續真正開始構建的流程,所以這時候你回過頭會發現 Gradle 其實也就那么回事,也是代碼寫的(哈哈,找打?。皇沁@一篇我們只分析了 Gradle 框架自身初始化(非構建生命周期的初始化,要區分)的核心流程。
總結
為了 Gradle 庖丁解牛系列做鋪墊,本篇主要進行了 Gradle 基礎鋪墊說明總結,后面對 Gradle 框架自身初始化(非構建生命周期的初始化,要區分)的核心流程進行了分析,通過本文我們至少應該知道如下:
Gradle 只是一個構建框架,而且大多數代碼是 Java 和 Groovy 編寫。
gradlew 是 gradle 的一個兼容包裝工具。
執行 gradle 或者 gradlew 命令開始進行構建生命周期前做的第一步是對 Gradle 框架自身的初始化(本文淺析內容)。
有了這一篇的鋪墊,下面幾篇我們會以此分析繼續,不過主線都是基于執行一個 gradle taskName 命令,所以如果想看懂這個系列文章,建議先補習下 Gradle 構建基礎。