前言
代碼的最后一步是構建成計算機可識別的二進制數據,然后才得以在計算機上運行。如果你曾經寫過有點規模(至少數十個以上獨立的源文件,且需要依賴第三方包)C語言項目,必定對C語言項目的構建過程印象深刻。或者當你曾經在linux系統中使用rpm命令安裝程序 時,系統一遍又一遍不厭其煩地提醒你缺少依賴時,不知那時的心情如何?前一個問題可歸屬於構建系統(Build Systems, Build Automation),后一個問題則屬於構件系統(artifact repository manager or binary repository manager)的范疇。而平常我們所說的“構建系統”往往代指構建系統與構件系統。也可以簡略地認為,構建系統是由程序與配置組成的,而構件系統是由軟件包與軟件元數據組成。
構建系統
go語言的程序構建,基於go build命令,內置於官方發行版中。其天生正統,而不像java有ant、maven、gradle,python有pip。它的構建體系經歷過幾個階段,最早是依賴於GOPATH,其次是vendor(dep),再到后來就是go module(早期也叫vgo)。在具體介紹構建系統之前,按慣例先上示例代碼。
示例代碼是一個mini的java項目結構(非maven、gradle結構),主要演示如果完全依賴於javac,如何做java項目構建。
├── com
| └── eventer
| ├── a
| └── Person.java
| └── Test.java
└── build
| ├── com
| └── eventer
| ├── a
| └── Person.class
| └── Test.class
package com.eventer;
import com.eventer.a.Person;
public class Test {
public static void main(String[] args) {
System.out.println(new Person());
}
}
package com.eventer.a;
public clss Person() {
}
以下是構建過程。
[eventer@localhost]# ls -a
total 0
drwxr-xr-x 4 eventer eventer 128 Mar 26 22:34 .
drwxr-xr-x 38 eventer eventer 1216 Mar 26 22:22 ..
drwxr-xr-x 3 eventer eventer 96 Mar 26 22:34 build
drwxr-xr-x 3 eventer eventer 96 Mar 26 22:23 com
[eventer@localhost]# javac -d build com/rfchina/test/*.java com/rfchina/test/a/*.java
[eventer@localhost]# cd build
[eventer@localhost]# java com/rfchina/test/Test
從上述代碼可以看出,這僅僅是構建一個只有兩個類,且沒有第三方依賴的微型項目,javac命令的參數已經挺長。可想而知,當項目中有成千上百個類,幾十個依賴,再加上編譯選項、注解處理等等,javac的參數長度會有多長。而構建是隨時發生的,這樣的任務毫無疑問應該有一個工具來完成。我相信最早的時候這樣的工具非批處理腳本莫屬。在后來的日子里,隨着越來越多的需求加入到構建的各個階段,如編譯前、編譯時、構建前、構建后、測試階段、打包、發布等,甚至在開發階段也有很多需求產生,如代碼生成。所有這些后來都被構建系統所實現,從最早的ant,到后來一統天下的maven,到現在的gradle,越來越強大。
再比如GNU構建系統經典的configure, make, make install三部曲,已經成為linux系統構建軟件的標准與習慣(即使不是GNU構建系統,很多軟件的構建也使用這三個過程)。
現代的編程語言大多給出了自己專用的構建系統與構件系統,它們兩者已成為軟件開發團隊必不可少的工具。go語言自然也不能例外。
wikipedia對構建的定義
Build automation is the process of automating the creation
of a software build and the associated processes
including: compiling computer source code into binary code,
packaging binary code, and running automated tests.
在《不一樣的go語言-gopher》一文中,“依賴管理”與“構建方式”兩節中已經講述過go語言關於構建的種種,在此不再贅述。也曾提到了當前go的構建系統在國內的種種不便及對策。正所謂合久必分,分久必合,在官方的支持下,go的構建系統日趨成熟,但目前獨獨一個好的構件系統,一個可以在本地搭建的構件系統。關於這一點,將在下一節中詳述。
一個好的構建系統,應該具有以下特征。
- 自動解決依賴關系:包括直接依賴與間接依賴,如A依賴B,B依賴D,C依賴D,構建系統應自動引入A、B、C、D依賴,並去重。
- 增量&並行構建:增量構建是只對變更構建而跳過沒有變更的文件或任務;並行構建指各自獨立的任務可以並行執行。
- 測試:如單元測試、並行測試、性能測試、測試報告等。
- 錯誤提示:友好詳細的構建錯誤提示,豐富的調試或錯誤輸出參數控制。
- 自定義構建目標:比如自定義打包格式, exe, zip, etc。
- 基於配置&約定:約定優於配置,構建輸入輸出易配置。
- 可擴展:可自定義擴展,如maven與gradle的plugin機制
- 跨平台:構建系統本身需要具備跨平台的特性,或者能在交叉構建(在某個目標系統構建另一個目標系統的程序)
- 發布:支持發布程序包至私庫及各類不同協議的第三方倉庫。
- IDE集成:支持與各大主流IDE集成。
顯然go的構建系統還不夠強大,開發人員還無法定制構建過程與結果。只有go build命令而沒有輔助go build命令的工具,在這一方面go依然任得道遠。
常見構建系統列表
語言 | 構建系統 | 說明 |
---|---|---|
c/c++ | gnu make, automake, nmake, cmake, qmake | 各個make對應於不同時期,不同架構體系下的構建工具 |
java | ant, maven, gradle | 目前maven與gradle是主流, ant已經退出歷史舞台 |
python | distribute, setuptools, easy_install, eggs, pip | python的構建系統百家齊放,世界大戰般。不過pip最終贏得王座 |
nodejs | npm | - |
javasript | grunt, gulp, webpack | 誰是王者? |
go | go build | 嫡系 |
rust | cargo | 號稱最強大的構建系統, 超級大殺器。 |
ruby | rubygem | - |
.net | visual studio之nuget | 最牛逼的IDE, 包羅萬有 |
swift&object-c | xcode | 最霸道的IDE |
除了上述列表的構建系統,現在已經有不少雲構建系統存在。比如google的Bazel,滴滴的建木。
上面提到的大殺器cargo,它與其他語言的構建系統不同,其既是構建系統又是構件系統。cargo口號叫得那么響,除了其是官方出品之外,恐怕也只是為了與go一較高下。rust可能為程序 員考慮得更多,極力地想討好程序員。而在構建系統方面,對比一下go module與cargo,只怕go也是借鑒了不少cargo的東西。但與當代成熟的構建系統,比如gradle,並沒有高下立判之感。不同之處在於cargo在配置中使用rust語言本身來做擴展,而gradle則是使用自身的DSL語言來實現。相比這下,go在這一方面還需要快馬加鞭,總比在屁顛屁顛地跟在別人后面追好。
構件系統
go語言的構件系統,官方默認是github。此外也可以是支持vgo下載協議的倉庫(如athens, goproxy.io),然后通過設置GOPROXY環境變量來設置構件系統代理,此舉可以完美解決國內下載依賴不暢的問題。而對於私庫,目前免費開源的只有athens,但是仍不夠完善。除此之外,jfrog的artifactory也支持go私庫,只不過要收費。
私庫對於一個成熟、專業的開發團隊來說,是極其重要的一個工具。團隊內部會產生大量的公共程序包,對於go語言來說,如果沒有一個類似於maven的nexus倉庫,那么項目如何構建?如何持續集成?雖然必然會有各種巧妙地方案來解決或避開這些問題,但無疑是不得已而為之。現代編程體系發展至今天,沒有理由讓歷史倒退,走回批處理或者基礎設施(網絡)的老路來解決問題。有時候我真覺得王垠大俠對go的批判有道理,不吸取前人在編程語言設計上的經驗教訓,而一意孤行到底。盡管我不否認,寫go確實會比java舒服很多,java嚴謹地啰嗦,所以它只適合用來做大項目。
鑒於此,go的官方團隊應該鼓勵社區去達成構件系統這事。而不應該像go module那樣,將社區招之即來,揮之即去,利用完了,就沒社區什么事了。
那么構件系統,應該具有以下特征。
- 構件存儲:包括二進制包、源碼包、文檔
- 版本:支持release、snapshot,
- 元數據:設計良好的元數據的存儲機制
- 緩存:支持本地緩存
- 安全&權限:支持用戶、角色、權限模型;支持常用的用戶體系集成,如LDAP
- 倉庫管理:多語言,多倉庫,支持代理倉庫,遠程倉庫。
常見構件系統列表
構件系統 | 支持語言/系統 | 支持構建系統 | 說明 |
---|---|---|---|
nexus | java | maven, gradle, npm | 歷史悠久,流行的構件系統 |
jfrog | java, go | maven, gradle, go | 后起之秀,有選擇的收費 |
proget | .net | vistual studio | - |
cargo | rust | cargo | - |
Cocoapods | swift&object-c | xcode | - |
Carthage | swift&object-c | xcode | - |
dpkg&apt | Debian, Ubuntu | - | - |
yum | CentOS | - | - |
homebrew | Mac OS | - | - |