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