本文來自于CSDN博客歇盼,作者:晏博精盅,已獲授權(quán),版權(quán)歸原作者所有横缔,未經(jīng)作者同意镜遣,請(qǐng)勿轉(zhuǎn)載己肮。
背景
陸陸續(xù)續(xù)一年多,總是有人問 Gradle 構(gòu)建悲关,總是發(fā)現(xiàn)很多人用 Gradle 是迷糊狀態(tài)的谎僻,于是最近準(zhǔn)備來一個(gè)“Gradle 庖丁解牛”系列寓辱,一方面作為自己的總結(jié)艘绍,一方面希望真的能達(dá)到標(biāo)題所示效果,同時(shí)希望通過該系列達(dá)到珍惜彼此時(shí)間的目的秫筏,因?yàn)槟壳笆忻嫔详P(guān)于 Gradle 的教程都是在教怎么配置和怎么編寫插件诱鞠,很少有說明 Gradle 自己到底是個(gè)啥玩意的,還有是如何工作的这敬,本系列以官方 release 3.4 版本為基礎(chǔ)航夺。
廢話不多說,標(biāo)題也表明了本篇所總結(jié)的內(nèi)容 —— 構(gòu)建源頭源碼淺析崔涂,不懂啥叫 Gradle 和 Groovy 的請(qǐng)先移步我以前文章《Groovy 腳本基礎(chǔ)全攻略》和《Gradle 腳本基礎(chǔ)全攻略》阳掐,免得這一系列也看不明白咋回事。不過還是要先打個(gè)預(yù)防針,竟然還有人覺得 Gradle 只能構(gòu)建 Android App缭保,這 TM 就尷尬了汛闸,我想說 Gradle 就是一套腳手架,你想干啥玩意都可以艺骂,就看你想不想干诸老,他就是個(gè)自動(dòng)化構(gòu)建框架,你整明白它的核心以后钳恕,他的插件你只用依據(jù)規(guī)則使用即可别伏,譬如用來構(gòu)建 Android 的 apply plugin: ‘com.android.application’ 只是它的一個(gè)小插件而已,實(shí)質(zhì)就看你想搞啥了苞尝,Java畸肆、Web、JS宙址、C 等等都可以用它構(gòu)建轴脐;而整明白它原理核心之一就是你得先整明白他的規(guī)則,也就是本文所總結(jié)討論的內(nèi)容抡砂。
基礎(chǔ)技能叨叨敘
所謂基礎(chǔ)技能就是一些鋪墊大咱,既然要說 Gradle 構(gòu)建系列了,我們有必要知道 Gradle 相關(guān)的一些基本命令注益,方便下面演示時(shí)使用(注意:如下命令是針對(duì)安裝 gradle 并配置環(huán)境變量的情況下執(zhí)行碴巾,你如果做 Android 開發(fā)的話,Ubuntu 直接將如下所有 gradle 替換為 ./gradlew 即可丑搔,這樣就可以用包裝特性了厦瓢,至于包裝是咋回事后面會(huì)說),關(guān)于其他命令請(qǐng)呼叫 help 或者查看官方 User Guide 的 Using the Gradle Command-Line啤月。
看完和本篇相關(guān)的命令總結(jié)后來看看前面提到的 gradle 替換 gradlew 執(zhí)行是咋回事煮仇,其實(shí)實(shí)質(zhì)可以查看官方 User Guide 的 The Gradle Wrapper,這里給出總結(jié)如下:
其實(shí)上面腳本的作用就是對(duì) Gradle 的一個(gè)包裝谎仲,保證任何機(jī)器都可以運(yùn)行這個(gè)構(gòu)建浙垫,即使這個(gè)機(jī)器沒有安裝 Gradle 或者安裝的 Gradle 版本不兼容匹配,如果沒裝或者不兼容就會(huì)根據(jù) gradle-wrapper.properties 里的配置下載特定版本郑诺,下載的包放在 $USER_HOME/.gradle/wrapper/dists 目錄下夹姥,所以我們有時(shí)候第一次通過 gradlew 包裝腳本執(zhí)行構(gòu)建時(shí)總會(huì)看到正在下載一個(gè) zip 文件,實(shí)質(zhì)就是在下 gradle-wrapper.properties 中指定的 zip 包辙诞。
gradlew 有一個(gè)非常人性化和牛逼的特點(diǎn)辙售,解決了幾種坑爹的問題,譬如團(tuán)隊(duì)開發(fā)保證 Gradle 構(gòu)建版本一致性倘要、gradle-wrapper.properties 下載地址方便切換到公司內(nèi)部服務(wù)器等(甚至可以先通過 gradle.properties 文件配置公司服務(wù)器的帳號(hào)密碼或者驗(yàn)證信息圾亏,然后在 gradle-wrapper.properties 中配置distributionUrl=https://username:password@somehost/path/to/gradle-distribution.zip十拣,這樣就可以達(dá)到公司內(nèi)網(wǎng)加認(rèn)證下載的雙重安全了)封拧。
gradlew 由來的最終原理其實(shí)是通過 Gradle 預(yù)置的 Wrapper (繼承自 DefaultTask )來實(shí)現(xiàn)的(AS 內(nèi)部默認(rèn)實(shí)現(xiàn)了自動(dòng)生成而已)志鹃,譬如:
運(yùn)行 gradle createWrapper 就能生成上面描述的幾個(gè)文件。而關(guān)于 Gradle 的 Wrapper 可以參考 DSL Reference 的 Wrapper 和 JavaDoc 的 Wrapper API泽西,里面提供了一堆屬性設(shè)置下載地址曹铃、解壓路徑、 gradleVersion 等捧杉,你也可以在 distributionUrl 中通過 ${gradleVersion} 來使用你設(shè)置的 DSL 變量陕见,這里暫時(shí)不再展開。
說完命令我們接著說說 Groovy味抖、DSL评甜、Gradle 之間的關(guān)系,首先一定要明確仔涩,Groovy 不是 DSL忍坷,而是通用的編程語言,類似 Java熔脂、C++ 等佩研,就是一種語言;但 Groovy 對(duì)編寫 DSL 提供了很牛逼的支持霞揉,這些支持都源自 Groovy 自己語法的特性旬薯,比如閉包特性、省略分號(hào)特性适秩、有參方法調(diào)用省略括弧特性绊序、屬性默認(rèn)實(shí)現(xiàn) getter、setter 方法特性等秽荞,當(dāng)然骤公,作為 Android 開發(fā)來說,Gradle 構(gòu)建 Android 應(yīng)用實(shí)質(zhì)也是基于 Groovy 編寫的 DSL蚂会,DSL 存在的意義就在于簡(jiǎn)化編寫和閱讀淋样。
而 Gradle 的實(shí)質(zhì)就是一個(gè)基于 Groovy 的框架了,也就是說我們得按照他的約束來玩了胁住,和我們平時(shí) Android 開發(fā)使用框架類似趁猴,一旦引入框架,我們就得按照框架的寫法來規(guī)規(guī)矩矩的編寫彪见。Gradle 這個(gè)框架只負(fù)責(zé)流程約定儡司,處理細(xì)節(jié)是我們自己的事,就像我們編譯 Android App 時(shí)基于 Gradle 框架流程引入 apply plugin: ‘com.android.application’ 構(gòu)建插件一樣余指,具體做事是我們插件再約束的捕犬,插件又對(duì)我們簡(jiǎn)化了配置跷坝,我們只用基于 Gradle 框架和相關(guān)插件進(jìn)行構(gòu)建配置。
了所以簡(jiǎn)單粗暴的解釋就是碉碉, Groovy 是一門語言柴钻,DSL 就是一種特定領(lǐng)域的配置文件,Gradle 就是基于 Groovy 的一種框架垢粮,就像我們以前做 Android 開發(fā)使用 Ant 構(gòu)建一樣贴届,build.xml 就可以粗略的理解為 Ant 的 DSL 配置,所以我們編寫 build.xml 時(shí)會(huì)相對(duì)覺得挺輕松(和后來的 Gradle 還是沒法比的)蜡吧。
搞清了他們之間的關(guān)系后我們就要深入看看為啥是這樣了毫蚓,因?yàn)殛P(guān)于 Gradle 構(gòu)建現(xiàn)在中文網(wǎng)上的教程要么是教你如何配置 DSL 屬性,要么就是教你如何使用 Groovy 去拓展自己的特殊 task昔善,或者是教你怎么編寫插件元潘,卻幾乎沒人提到 Gradle 這個(gè)構(gòu)建框架的本質(zhì),所以使用起來總是心里發(fā)慌君仆,有種不受控制的感覺翩概。
Gradle 源碼源頭分析
還記得我們前一小節(jié)講的 gradlew 么,追蹤溯源就從它開始袖订,以前我也是出于好奇就去看了下它氮帐,發(fā)現(xiàn)這個(gè) shell 腳本前面其實(shí)就是干了一堆沒事干的事,大招就在它的最后一句洛姑,如下:
對(duì)的上沐,大招就是 GradleWrapperMain,這貨還支持通過 “$@” 傳遞參數(shù)楞艾,想想我們平時(shí)執(zhí)行的 gradlew 命令参咙,這下明白了吧,既然這樣我們就拿他開涮硫眯,去看看他的源碼蕴侧,如下:
上面 WrapperExecutor.forWrapperPropertiesFile 方法實(shí)質(zhì)就是通過 java Properties 去解析 gradle/wrapper/gradle-wrapper.properties 文件里的配置,譬如 distributionUrl 等两入;接著執(zhí)行 wrapperExecutor.execute 方法净宵,args 參數(shù)就是我們執(zhí)行 gradlew 腳本時(shí)傳遞的參數(shù),Install 實(shí)例就是管理本地本項(xiàng)目是否有 wrapper 指定版本的 gradle裹纳,木有就去下載解壓等等择葡,BootstrapMainStarter 實(shí)例就是 wrapper 執(zhí)行 gradle 真實(shí)入口的地方,所以我們接著看看 execute 這個(gè)方法剃氧,如下:
到這里如果本地沒有 wrapper 包裝的 Gradle敏储,就會(huì)下載解壓等,然后準(zhǔn)備一堆貨朋鞍,貨備足后就調(diào)用了前面說的 BootstrapMainStarter 的 start 方法已添,如下:
不解釋妥箕,快上車,真的 Gradle 要現(xiàn)身了更舞,Wrapper 的使命即將終結(jié)畦幢,我們把重點(diǎn)轉(zhuǎn)到 org.gradle.launcher.GradleMain 的 main 方法,如下:
GG了疏哗,莫慌呛讲,我們的重點(diǎn)不是看懂 Gradle 的每一句代碼禾怠,我們需要撿自己需要的重點(diǎn)返奉,這貨設(shè)置各種 ClassLoader 后最終還是調(diào)用了 org.gradle.launcher.Main 的 run 方法,實(shí)質(zhì)就是 EntryPoint 類的 run 方法吗氏,因?yàn)?Main 類是 EntryPoint 類的實(shí)現(xiàn)類芽偏,而 EntryPoint 的 run 方法最主要做的事情就是創(chuàng)建了一個(gè)回調(diào)監(jiān)聽接口,然后調(diào)用了 Main 重寫的 doAction 方法弦讽,所以我們?nèi)サ?Main 的 doAction 看看污尉,如下:
這貨實(shí)質(zhì)調(diào)用了 CommandLineActionFactory 實(shí)例的 convert 方法得到 Action 實(shí)例,然后調(diào)用了 Action 的 execute 方法往产,我去被碗,真特么繞的深,這彎溜的仿村,我們會(huì)發(fā)現(xiàn) CommandLineActionFactory 里的 convert 方法實(shí)質(zhì)除過 log 記錄準(zhǔn)備外干的惟一一件事就是創(chuàng)建其內(nèi)部類 WithLogging 的對(duì)象锐朴,這時(shí)候我們可以發(fā)現(xiàn) Action 的 execute 方法實(shí)質(zhì)就是調(diào)用了 WithLogging 的 execute 實(shí)現(xiàn),如下:
這不蔼囊,最終還是執(zhí)行了 new ExceptionReportingAction(
new JavaRuntimeValidationAction(
new ParseAndBuildAction(loggingServices, args)),
new BuildExceptionReporter(loggingServices.get(StyledTextOutputFactory.class), loggingConfiguration, clientMetaData()))); 對(duì)象的 execute 方法(上面的 action 就是這個(gè)對(duì)象)焚志,關(guān)于這個(gè)對(duì)象的創(chuàng)建我們只用關(guān)注 new JavaRuntimeValidationAction(
new ParseAndBuildAction(loggingServices, args)) 這個(gè)參數(shù)即可,這也是一個(gè) Action畏鼓,實(shí)例化后在 ExceptionReportingAction 的 execute 調(diào)用了他的 execute酱酬,而 ParseAndBuildAction 的 execute 又被 JavaRuntimeValidationAction 的 execute 觸發(fā),有點(diǎn)包裝模式的感覺云矫,所以我們直接關(guān)注 ParseAndBuildAction 的實(shí)例化和 execute 方法膳沽,因?yàn)槠渌皇俏覀兊闹攸c(diǎn),如下:
既然我們是追主線分析(關(guān)于執(zhí)行命令中帶 help让禀、version挑社、gui 的情況我們就不分析了,也比較簡(jiǎn)單堆缘,當(dāng)我們執(zhí)行 gradle –help 或者 gradle –gui 時(shí)打印的 help 或者彈出的 GUI 是 BuiltInActions 或者 GuiActionsFactory 滔灶,比較簡(jiǎn)單,不作分析)吼肥,我們看核心主線 BuildActionsFactory 的 createAction 方法和通過 createAction 方法生成的 Runnable 的 run 方法即可(所謂的主線就是我們執(zhí)行 gradle taskName 命令走的流程录平,譬如 gradle asseambleDebug 等)麻车,如下是 BuildActionsFactory 的 createAction 方法:
既然上面都判斷最后調(diào)用都是類同的,那我們就假設(shè)調(diào)用了 runBuildInProcess 方法吧斗这,如下:
此刻您可憋住了动猬,別小看這么簡(jiǎn)單的一個(gè)方法,這玩意麻雀雖小五臟俱全啊表箭,在我第一次看這部分源碼時(shí)是懵逼的赁咙,好在看見了相關(guān)類的注釋才恍然大悟,至于為啥我們現(xiàn)在來分析下免钻。先說說 globalServices 對(duì)象的構(gòu)建吧彼水,其實(shí)和 createGlobalClientServices() 這個(gè)方法類似,隨意咯极舔,我們就隨便看個(gè)凤覆,如下:
看起來就是構(gòu)造了一個(gè) clientSharedServices 對(duì)象,然后交給下面的 runBuildAndCloseServices 方法使用拆魏,對(duì)的盯桦,就是這樣的,只是這個(gè) createGlobalClientServices() 方法真的很懵逼渤刃,ServiceRegistryBuilder 的 build() 方法實(shí)質(zhì)是實(shí)例化了一個(gè) DefaultServiceRegistry 對(duì)象拥峦,然后通過構(gòu)造方法傳遞了 parent(NativeServices.getInstance()) 實(shí)例,通過 addProvider(provider) 方法傳遞了 provider(new GlobalScopeServices(false)) 和 provider(new DaemonClientGlobalServices()) 實(shí)例卖子,巧妙的地方就在 DefaultServiceRegistry 類的注釋上面略号,大家一定要先看注釋,ServiceRegistryBuilder 的 build() 最后調(diào)用的是 DefaultServiceRegistry 對(duì)象的 addProvider 方法揪胃,實(shí)質(zhì)調(diào)用的是 DefaultServiceRegistry 的 findProviderMethods(provider) 方法璃哟,如下:
這時(shí)咱們先回過頭看看前面說的 createGlobalClientServices() 方法,我們發(fā)現(xiàn)其中的 NativeServices喊递、FileSystemServices随闪、DaemonClientGlobalServices 都沒有 config 方法,但是有一堆 createXXX 的方法骚勘,這些都會(huì)被加入 ownServices 列表铐伴,但是 GlobalScopeServices 類卻有 config 方法,如下:
到這里你可以暫時(shí)松口氣了俏讹,上面 config 等等一堆都是在做準(zhǔn)備当宴,說白了就是各種列表注冊(cè)都放好,然后 DefaultServiceRegistry 的 get(…) 系列方法實(shí)質(zhì)都是通過 doGet(Type serviceType) 的 invok 方法調(diào)用等泽疆。接著讓我們把目光移到上面運(yùn)行 gradle taskName 命令時(shí)分析的 BuildActionsFactory 的 runBuildInProcess(…) 方法户矢,我們?cè)谠摲椒ㄖ姓{(diào)用 runBuildAndCloseServices(…) 方法時(shí)第三個(gè)參數(shù)傳遞的是 globalServices.get(BuildExecuter.class), 也就是調(diào)用了 DefaultServiceRegistry 的 get(BuildExecuter.class) 方法殉疼,剛剛說了 DefaultServiceRegistry 的 get(…) 實(shí)質(zhì)就是 invoke 一個(gè)方法梯浪,在這里 serviceType 參數(shù)是 BuildExecuter.class捌年,所以調(diào)用的就是 ToolingGlobalScopeServices 的 createBuildExecuter(…) 方法,ToolingGlobalScopeServices 是 LauncherServices 的 registerGlobalServices(…) 方法實(shí)例化的挂洛,而 LauncherServices implements PluginServiceRegistry 又是剛剛上面分析 GlobalScopeServices 的 configure(…) 方法里被實(shí)例化添加的礼预,LauncherServices 的 registerGlobalServices(…) 方法也是在那調(diào)用的。
繞了一圈真是折騰虏劲,突然發(fā)現(xiàn)累覺不愛托酸,覺得 Gradle 框架中 DefaultServiceRegistry 類的設(shè)計(jì)真的是挺顛覆我認(rèn)知的,雖然沒啥黑科技柒巫,但是這個(gè)設(shè)計(jì)思路真的是坑爹励堡,很容易繞懵逼,好在后來整明白了吻育,真想說句 ZNMKD念秧。好了,牢騷也發(fā)完了布疼,下面該正題了,我們接著上面說的去看看 LauncherServices 內(nèi)部 ToolingGlobalScopeServices 的 createBuildExecuter(…) 方法币狠,一貫做法游两,關(guān)注主線,咱們會(huì)發(fā)現(xiàn)這方法最主要的就是層層簡(jiǎn)單代理模式包裝實(shí)例化 BuildActionExecuter 對(duì)象漩绵,最關(guān)鍵的就是實(shí)例化了 InProcessBuildActionExecuter 對(duì)象贱案,這個(gè)對(duì)象就一個(gè) execute 方法(這個(gè) execute 方法被調(diào)用的地方就在上面分析的 BuildActionsFactory 的 runBuildInProcess 方法返回的 RunBuildAction 對(duì)象的 run 方法中,RunBuildAction 對(duì)象的 run 方法又是前面分析的源頭觸發(fā)的)止吐,如下:
贊宝踪,到此真想說句真是不容易啊0印4裨铩!總算看見光明了不同,我 Gradle 的大 GradleLauncher厉膀,為啥這么稱呼呢,因?yàn)槟憧戳?GradleLauncher 實(shí)現(xiàn)你會(huì)有種柳暗花明又一村的感覺二拐,真的服鹅,你會(huì)覺得前面這些復(fù)雜的初始化就是為了等到這個(gè)實(shí)例的到來,因?yàn)?GradleLauncher 實(shí)現(xiàn)里就真真切切的告訴你了 Gradle 構(gòu)建的三大生命周期——-初始化百新、配置企软、執(zhí)行,不信你看:
到此構(gòu)建源頭就分析完了饭望,下一篇會(huì)接著這里分析 DefaultGradleLauncher 及后續(xù)真正開始構(gòu)建的流程仗哨,所以這時(shí)候你回過頭會(huì)發(fā)現(xiàn) Gradle 其實(shí)也就那么回事聚蝶,也是代碼寫的(哈哈,找打T逯巍)碘勉,只是這一篇我們只分析了 Gradle 框架自身初始化(非構(gòu)建生命周期的初始化,要區(qū)分)的核心流程桩卵。
總結(jié)
為了 Gradle 庖丁解牛系列做鋪墊验靡,本篇主要進(jìn)行了 Gradle 基礎(chǔ)鋪墊說明總結(jié),后面對(duì) Gradle 框架自身初始化(非構(gòu)建生命周期的初始化雏节,要區(qū)分)的核心流程進(jìn)行了分析胜嗓,通過本文我們至少應(yīng)該知道如下:
Gradle 只是一個(gè)構(gòu)建框架,而且大多數(shù)代碼是 Java 和 Groovy 編寫钩乍。
gradlew 是 gradle 的一個(gè)兼容包裝工具辞州。
執(zhí)行 gradle 或者 gradlew 命令開始進(jìn)行構(gòu)建生命周期前做的第一步是對(duì) Gradle 框架自身的初始化(本文淺析內(nèi)容)。
有了這一篇的鋪墊寥粹,下面幾篇我們會(huì)以此分析繼續(xù)变过,不過主線都是基于執(zhí)行一個(gè) gradle taskName 命令,所以如果想看懂這個(gè)系列文章涝涤,建議先補(bǔ)習(xí)下 Gradle 構(gòu)建基礎(chǔ)媚狰。