這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文在此《How Go uses Go to build itself》,作者為 Dave Cheney。
————翻譯分隔線————
Go 是如何用 go 編譯自己的
這篇文章基於 2013 年四月中旬我為雪梨 Go 使用者組做的一次關於 Go 構建過程的演講而成。
在郵件清單或 IRC 的頻道裡經常有人尋求關於 Go 編譯器、運行時和內部原理的細節文檔。當前,關於 Go 的內部原理的權威文檔的來源就是我鼓勵每個人都去閱讀的原始碼。雖然這樣說,但是從 Go 1.0 發布以來 Go 的構建過程就已經穩定了,因此將其記錄在這裡或許也會有其相應的價值。
這篇文章大致介紹了 Go 編譯過程的九個步驟,從原始碼開始,結束於經過充分測試的安裝好的 Go 。為了簡明扼要,所有路徑都相對與原始碼檢出的根路徑,$GOROOT/src。
你應當通過閱讀在 golang.org 網站上的從原始碼安裝 Go 來瞭解更多背景知識。
第一步. all.bash
% cd $GOROOT/src% ./all.bash
第一步有一點點虎頭蛇尾,all.bash 只是調用了另外兩個 shell 指令碼:make.bash 和 run.bash。若使用 Windows 或 Plan 9,其過程也基本類似,只是指令碼分別以 .bat 或 .rc 結尾。在文章的其他部分,請用適當的作業系統對應的擴充來補全命令。
第二步. make.bash
. ./make.bash --no-banner
make.bash 作為 all.bash 內容的一部分,如果它退出也會中斷構建過程。make.bash 有三個主要的任務,第一個任務是驗證將編譯 Go 的環境是健全的。健全檢查已經開發了幾年了,是為了能通用的識別出會導致問題的工具,或指出環境中什麼地方導致構建失敗。
第三步. cmd/dist
gcc -O2 -Wall -Werror -ggdb -o cmd/dist/dist -Icmd/dist cmd/dist/*.c
當健全檢查完成後,make.bash 開始編譯 cmd/dist。cmd/dist 替換了在 Go 1 之前的基於 Makefile 的系統,並且管理了在 pkg/runtime 中的小部分代碼的產生。cmd/dist 是一個 C 程式,為了處理大多數平台的已知問題,它會對系統的 C 編譯器和標頭檔施加影響。cmd/dist 會檢測主機的作業系統和架構,$GOHOSTOS 和 $GOHOSTARCH。這可能與為了交叉編譯設定的 $GOOS 和 $GOARCH 的值不同。事實上,Go 的構建過程就是在構建一個交叉編譯器,不過在大多數情況下主機和目標平台是一樣的。接下來,make.bash 使用初始參數調用 cmd/dist 來編譯用於支撐編譯器套件的庫:lib9、libbio 和 libmach,然後是編譯器本身。這些工具也是用 C 編寫的,並且由系統的 C 編譯器編譯。
echo "# Building compilers and Go bootstrap tool for host, $GOHOSTOS/$GOHOSTARCH."buildall="-a"if [ "$1" = "--no-clean" ]; then buildall=""fi./cmd/dist/dist bootstrap $buildall -v # builds go_bootstrap
使用編譯器套件,cmd/dist 會編譯 go 工具:go_bootstrap。go_bootstrap 不是完整的 go 工具,例如為了避免依賴 cgo 因此 pkg/net 被廢除了。包含有包和庫的目錄列表被編譯,而其依賴關係是在 cmd/dist 工具裡編碼的,因此避免在編譯 cmd/go 引入新的依賴就極為重要。
第四步. go_bootstrap
現在 go_bootstrap 已經構建完成,make.bash 的最後一步是使用 go_bootstrap 編譯完整的 Go 標準庫,包括一個完整的 go 工具用以替換。
echo "# Building packages and commands for $GOOS/$GOARCH.""$GOTOOLDIR"/go_bootstrap install -gcflags "$GO_GCFLAGS" \ -ldflags "$GO_LDFLAGS" -v std
第五步. run.bash
現在 make.bash 已經完成,回到 all.bash 的執行,這會調用 run.bash。run.bash 的任務是編譯和測試標準庫、運行時以及語言測試集。
bash run.bash --no-rebuild
由於 make.bash 和 run.bash 都會調用 go install -a std,因此需要使用 –no-rebuild 標誌來避免重複前面的步驟,–no-rebuild 跳過了第二個 go install。
# allow all.bash to avoid double-build of everythingrebuild=trueif [ "$1" = "--no-rebuild" ]; then shiftelse echo '# Building packages and commands.' time go install -a -v std echofi
第六步. go test -a std
echo '# Testing packages.'time go test std -short -timeout=$(expr 120 \* $timeout_scale)secho
接下來 run.bash 會在標準庫裡所有的包上來運行用 testing 包編寫的單元測試。由於 $GOPATH 和 $GOROOT 中有著相同的命名空間,所以不能直接使用 go test … 否則 $GOPATH 中的每個包也會被逐一測試,因此建立了一個用於標準庫中的包的別名:std。由於一些測試需要比較長的時間,且會消耗大量記憶體,因此用 -short 標誌對一些測試進行了過濾。
第七步. runtime 和 cgo 測試
run.bash 接下來的部分會運行平台對 cgo 支援的測試,執行一些效能測試,並且編譯一些伴隨 Go 發行版一起的雜項程式。隨著時間的流逝,這些雜項程式的清單會越來越長,那麼它們也就會不可避免的被從編譯過程中悄悄剝離出去。
第八步. go run test
(xcd ../testunset GOMAXPROCStime go run run.go) || exit $?
run.bash 的倒數第二步會調用在 $GOROOT 下的 test 目錄裡的編譯器和運行時的測試。他們是對於編譯器和運行時自身的,較為低級細節的測試。會執行語言規格測試,test/bugs 和 test/fixedbugs 子目錄儲存有那些已經被發現並被修複的問題的獨立的測試。驅動測試的是一個小 Go 程式 $GOROOT/test/run.go,會執行 test 目錄裡的每個 .go 檔案。一些 .go 檔案的首行包含了指導 run.go 對結果作出判斷的指令,例如,程式將會失敗,或提供一個確定的輸出隊列。
第九步. go tool api
echo '# Checking API compatibility.'go tool api -c $GOROOT/api/go1.txt,$GOROOT/api/go1.1.txt \ -next $GOROOT/api/next.txt -except $GOROOT/api/except.txt
run.bash 的最後一步調用了 api 工具。api 工具的任務是確保 Go 1 的約定;匯出符號、常量、函數、變數、類型和方法都符合 2012 年發布的 Go 1 API。對於 Go 1 在 api/go1.txt 中有描述,而 Go 1.1 在 api/go1.1.txt。一個附加的檔案,api/next.txt 定義了 Go 1.1 開始向標準庫和運行時補充的符號表。一旦 Go 1.2 發布,這個檔案會成為 Go 1.2 的約定,然後會有一個新的 next.txt。還有一個小檔案:except.txt,包含有已經被獲批准的 Go 1 約定的特例。不應該輕易的向這個檔案新增內容。
附加的技巧和訣竅
你可能已經發現如果對於無需執行測試的構建 Go,make.bash 會很有用;同樣的,構建和測試 Go 運行時,run.bash 也十分有用。這個差異對於以前那樣交叉編譯 Go 或當前工作於標準庫上都十分有用。
更新:感謝 Russ Cox 和 Andrew Gerrand 的反饋和建議。