編程語(yǔ)言的支撐體系:構(gòu)建系統(tǒng)、IDE 和依賴管理
年關(guān)(annual review)將近,這一段時(shí)間,我在梳理 2020 年做的一些事情,并試著制定下一年的計(jì)劃。過(guò)程中,我發(fā)現(xiàn)我做的一些事情,或是工作相關(guān),或是興趣上的探索,還都可以繼續(xù)總結(jié)出一些文章。在工作上,很多一部分做的事情就是編程語(yǔ)言的支撐體系。外加業(yè)余時(shí)間里,和同事一起花了一些時(shí)間在研究編程語(yǔ)言。在這幾部分的結(jié)合之下,我對(duì)于整個(gè)體系的端到端實(shí)現(xiàn)有一個(gè)整體的認(rèn)識(shí)。
作為一個(gè)職業(yè)的程序員,在我們的職業(yè)生涯里,不可避免地要學(xué)習(xí)一個(gè)又一個(gè)的編程語(yǔ)言。雖然多數(shù)情況下,我們對(duì)于使用什么語(yǔ)言并沒(méi)有太多的選擇權(quán)。但是,當(dāng)我們選擇一門語(yǔ)言時(shí),都要考慮一系列的要素,比如:
- 構(gòu)建系統(tǒng)
- IDE/Editor 支撐
- 依賴管理
- ……
PS:當(dāng)然了,對(duì)于那些使用 C/C++ 的人來(lái)說(shuō),這些可能都是例外:他/她覺(jué)得自己不需要這些工具,需要的時(shí)候可以自己創(chuàng)造一個(gè)。所以,這些語(yǔ)言在很長(zhǎng)的一段時(shí)間里,都缺乏良好的依賴管理工具。
故事開(kāi)始之前,讓我們讓 Android 使用的開(kāi)發(fā)和構(gòu)建來(lái)講述這個(gè)過(guò)程。
從 Android 應(yīng)用的開(kāi)發(fā)與構(gòu)建說(shuō)起
在移動(dòng)端開(kāi)發(fā)上,雖比不上這個(gè)行業(yè)的諸多大佬,但我也算是頗有經(jīng)驗(yàn)的。而恰好一年中有一半的時(shí)間,都在相關(guān)的項(xiàng)目上。所以,我從宏觀上了解了整體的體系。
當(dāng)我們開(kāi)始一個(gè)新的移動(dòng)應(yīng)用時(shí),會(huì)從 IDE 里通過(guò)模板創(chuàng)造一個(gè)嶄新的應(yīng)用,又或者是從某個(gè)地方(如 GitHub)尋找合適的模板。而后,為驗(yàn)證模板的有效性,我們通過(guò)執(zhí)行 Gradle 的相關(guān)命令,完成一個(gè)應(yīng)用的過(guò)程,運(yùn)行這個(gè) Demo。(PS:這一點(diǎn)與我們使用 Java 開(kāi)發(fā)應(yīng)用時(shí),并沒(méi)有太大區(qū)別)。
這個(gè)過(guò)程中,發(fā)生了這么一些事情:
- IDE 通過(guò)某種通訊機(jī)制,與 Gradle 進(jìn)行通訊,以執(zhí)行對(duì)應(yīng)的命令,如 build。
- Gradle 接收到 IDE 的指令后,解析 build.gradle 相關(guān)的內(nèi)容,尋找是否存在對(duì)應(yīng)的 Task,如這里的 build。
- 執(zhí)行 build 時(shí),首先要去解決依賴關(guān)系,如從對(duì)應(yīng)的 Maven 倉(cāng)庫(kù)中下載依賴。
- 隨后,真正地執(zhí)行對(duì)應(yīng)的構(gòu)建任務(wù),如調(diào)用 javac。
這個(gè)過(guò)程看上去非常簡(jiǎn)單,但是背后還藏著諸多的細(xì)節(jié)問(wèn)題。
構(gòu)建與依賴管理
當(dāng)我用 CLOC 工具統(tǒng)計(jì)了一下 Gradle 工具的源碼時(shí),我才發(fā)現(xiàn)這個(gè)工具并不簡(jiǎn)單。而進(jìn)一步地,在半深入源碼之后,我發(fā)現(xiàn)構(gòu)建系統(tǒng)還是頗為復(fù)雜的。一個(gè)簡(jiǎn)單的 Java 應(yīng)用就分為這么一些步驟:
- :compileJava UP-TO-DATE
- :processResources UP-TO-DATE
- :classes UP-TO-DATE
- :run
而當(dāng)我們有依賴的時(shí)候,需要添加上 classpath,即將依賴添加到編譯的路徑中。而對(duì)于一些非 .jar 類型的依賴而言,如 .war,構(gòu)建工具還要支持對(duì)他們的解析。因此,整體的過(guò)程就是:
- 判斷是否存在本地的依賴,如果沒(méi)有的話,從遠(yuǎn)程獲取。如果有依賴沖突的話,解決這些沖突,或者報(bào)錯(cuò)。
- 獲取依賴后,根據(jù)需要對(duì)依賴進(jìn)行處理。如 Android 中的 aar 包的解壓等。
- 結(jié)合依賴,對(duì)源碼進(jìn)行編譯
- 將所需要的 Java Resources 從依賴的 Jar 拷貝到指定目錄
- 打包構(gòu)建后的產(chǎn)物到一個(gè)新的 jar 包中
這些只是表面上的一些工作。而為了更好地表述這個(gè)過(guò)程,需要抽象出一個(gè) task 的概念,在這個(gè)概念里,一個(gè) task 有輸入和輸出。如
- 解析依賴?yán)?。它的輸出?build.gradle 文件,輸出是處理完的依賴路徑。
- 編譯任務(wù)里。它的輸入是源碼,輸出是 .class 文件。
- 打包任務(wù)里。它的輸入是一堆文件夾或者文件,輸出是一個(gè) .jar 包。
- ……
于是,在有了這些基礎(chǔ)之后,為了加快構(gòu)建,還需要緩存的機(jī)制。它對(duì)輸入和輸出進(jìn)行計(jì)算,當(dāng)兩者發(fā)生變化的時(shí)候,再進(jìn)行編譯。否則就跳過(guò)這個(gè)任務(wù)。
而這些只是核心功能,在非核心的功能區(qū)里,還有諸如于 SDK 版本、多輸入多輸出的變體等等。
IDE 與構(gòu)建系統(tǒng)
在那篇《編程語(yǔ)言的 IDE 支持》中,我們已經(jīng)介紹了編程語(yǔ)言所需要的 IDE 功能,諸如于:
- 語(yǔ)法高亮
- 子系統(tǒng)關(guān)聯(lián)與集成
- 跳轉(zhuǎn)與引用分析
- 智能感知
- 重構(gòu)
- 快速修復(fù)
- 結(jié)構(gòu)化視圖
- ……
在這篇文章中,大概再回顧一下它與構(gòu)建系統(tǒng)之間的關(guān)系。IDE 與構(gòu)建系統(tǒng)一般會(huì)存在這種關(guān)聯(lián):
- 解析構(gòu)建系統(tǒng)中的任務(wù)。如 Gradle 提供的 task,又或者是 package.json 中的 scripts,并將它們顯式地展示出來(lái),如 IDEA 中的 line marker,又或者是獨(dú)立的 Gradle pannel。
- 執(zhí)行構(gòu)建任務(wù)。即在 IDE 中的 UI 與構(gòu)建命令相綁定,典型的如 IDEA 中的 Android 應(yīng)用的構(gòu)建。
- 動(dòng)態(tài)修改構(gòu)建系統(tǒng)(可選)。如 IDEA 中的更新依賴版本,它依賴于解析構(gòu)建系統(tǒng)的 DSL,并更新對(duì)應(yīng)的 DSL。
對(duì)應(yīng)的有兩種機(jī)制可以與構(gòu)建系統(tǒng)通訊:
- 由構(gòu)建系統(tǒng)提供構(gòu)建 API。如 Gradle Tooling API,在那篇《Gradle IDEA 的項(xiàng)目模型》中,我們實(shí)際上介紹了由構(gòu)建系統(tǒng)主動(dòng)向 IDE 提供模型的方式。
- 由 IDE 構(gòu)造一遍構(gòu)建系統(tǒng)。如 IDEA 對(duì)于 Node.js 的處理方式。
簡(jiǎn)單來(lái)說(shuō),就是復(fù)雜的系統(tǒng)應(yīng)該由構(gòu)建系統(tǒng)提供機(jī)制,而簡(jiǎn)單的構(gòu)建系統(tǒng)則就不會(huì)有這樣的問(wèn)題。
依賴管理的基礎(chǔ)設(shè)施
不同語(yǔ)言對(duì)于依賴的管理機(jī)制都有所不同,但是它們的原理都是相似的:
- 源碼包。即將源碼打包,并以特定的格式發(fā)布,適用于腳本語(yǔ)言
- 倉(cāng)庫(kù)源。方式類似于源碼包,唯一不同的地方是借助于版本管理工具,如 Golang。
- 類二進(jìn)制包。典型的是 Java
- 其它包。如 Maven 可以支持其它自制的包
最有意思的是Maven 的機(jī)制,我可以自制依賴,并上傳上去。而整個(gè)倉(cāng)庫(kù)并不關(guān)心這個(gè)包的內(nèi)容,我們只需要依賴于它定義的格式即可。如果我們考慮圍繞語(yǔ)言來(lái)設(shè)計(jì)依賴管理體系,那么可以考慮的是類似的方式,并借助于 Git 這樣的版本工具。這樣一來(lái),我們就可以去中心化。
本文轉(zhuǎn)載自微信公眾號(hào)「 phodal」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 phodal公眾號(hào)。