Go 部落格
2019 年 Go Modules
多麼棒的一年!
2018 年對於 Go 生態系統來說是極好的一年,其中包管理是我們關注的主要焦點之一。在二月,我們發起了一場社群範圍的討論,探討如何將包管理直接整合到 Go 工具鏈中,並在八月,我們在 Go 1.11 中交付了該功能——Go modules 的第一個粗略實現。遷移到 Go modules 將是自 Go 1.0 以來對 Go 生態系統影響最深遠的變革。將整個生態系統——程式碼、使用者、工具等——從 GOPATH 遷移到 modules 將需要在許多不同領域付出努力。模組系統反過來將幫助我們為 Go 生態系統提供更好的認證和更快的構建速度。
這篇博文是 Go 團隊在 2019 年關於 modules 的計劃預覽。
釋出
Go 1.11 於 2018 年 8 月釋出,引入了對 modules 的初步支援。目前,modules 支援與傳統的基於 GOPATH 的機制並行維護。當 `go` 命令在 GOPATH/src 外部的目錄樹中執行時,並在根目錄中由 `go.mod` 檔案標記時,它預設處於 module 模式。可以透過設定過渡性環境變數 `$GO111MODULE` 為 `on` 或 `off` 來覆蓋此設定;預設行為是 `auto` 模式。我們已經看到了 Go 社群在 modules 方面的顯著採用,以及許多有用的建議和錯誤報告,以幫助我們改進 modules。
Go 1.12 定於 2019 年 2 月釋出,將完善 modules 支援,但仍預設保持 `auto` 模式。除了許多錯誤修復和其他小改進之外,Go 1.12 中最重要的變化可能是,像 `go run x.go` 或 `go get rsc.io/2fa@v1.1.0` 這樣的命令現在可以在 `GO111MODULE=on` 模式下執行,而無需顯式的 `go.mod` 檔案。
我們的目標是讓定於 2019 年 8 月釋出的 Go 1.13 預設啟用 module 模式(即,將預設值從 `auto` 更改為 `on`)並棄用 GOPATH 模式。為了實現這一目標,我們一直在努力改進工具支援以及對開源 module 生態系統的支援。
工具 & IDE 整合
在我們擁有 GOPATH 的八年中,已經建立了大量假設 Go 原始碼儲存在 GOPATH 中的工具。遷移到 modules 需要更新所有做出此類假設的程式碼。我們設計了一個新的包,golang.org/x/tools/go/packages,它抽象了查詢和載入給定目標 Go 原始碼資訊的操作。這個新包會自動適應 GOPATH 和 modules 模式,並且還可以擴充套件到特定於工具的程式碼佈局,例如 Bazel 使用的佈局。我們一直在與 Go 社群的工具作者合作,幫助他們在工具中採用 golang.org/x/tools/go/packages。
作為這項工作的一部分,我們還致力於將 gocode、godef 和 go-outline 等各種原始碼查詢工具統一到一個工具中,該工具可以從命令列使用,並且還支援現代 IDE 使用的語言伺服器協議。
向 modules 的過渡以及包載入的變化也促使 Go 程式分析發生了重大變化。作為重寫 `go vet` 以支援 modules 的一部分,我們引入了一個用於增量分析 Go 程式的通用框架,在該框架中,分析器一次針對一個包呼叫。在此框架中,一個包的分析可以將事實寫出,供匯入第一個包的分析的其他包使用。例如,`go vet` 對log 包的分析確定並記錄了 `log.Printf` 是 `fmt.Printf` 的包裝器的這一事實。然後 `go vet` 可以在呼叫 `log.Printf` 的其他包中檢查 printf 風格的格式字串。這個框架應該能夠支援許多新的、複雜的程式分析工具,以幫助開發人員更早地發現 bug 並更好地理解程式碼。
Module 索引
`go get` 原始設計中最重要的一部分是它是去中心化的:我們當時認為——並且今天仍然認為——任何人都可以將他們的程式碼釋出到任何伺服器上,這與 Perl 的 CPAN、Java 的 Maven 或 Node 的 NPM 等中心化登錄檔不同。將域名放在 `go get` 匯入空間的開頭重用了現有的去中心化系統,並避免了重新解決誰可以使用哪些名稱的問題。它還允許公司在私有伺服器上匯入程式碼,同時匯入公共伺服器上的程式碼。在轉向 Go modules 時,保持這種去中心化至關重要。
Go 依賴的去中心化帶來了許多好處,但也帶來了一些顯著的缺點。第一個是很難找到所有公開可用的 Go 包。每個想要提供包資訊的網站都必須自己進行抓取,否則就必須等到使用者查詢某個特定包時才能獲取它。
我們正在開發一項新服務,即 Go Module Index,它將提供一個公共日誌,記錄進入 Go 生態系統的包。像 godoc.org 和 goreportcard.com 這樣的網站將能夠監視此日誌的新條目,而不是各自獨立實現查詢新包的程式碼。我們還希望該服務能夠透過簡單的查詢來查詢包,以便 `goimports` 可以為尚未下載到本地系統的包新增匯入。
Module 認證
如今,`go get` 依賴於連線級認證(HTTPS 或 SSH)來檢查它是否正在與正確的伺服器通訊以下載程式碼。程式碼本身沒有額外的檢查,這使得在 HTTPS 或 SSH 機制以某種方式被破壞時存在中間人攻擊的可能性。去中心化意味著構建程式碼是從許多不同的伺服器獲取的,這意味著構建依賴於許多系統來提供正確程式碼。
Go modules 設計透過在每個模組中儲存 `go.sum` 檔案來改進程式碼認證;該檔案列出了每個模組依賴項的預期檔案樹的加密雜湊。使用 modules 時,`go` 命令使用 `go.sum` 來驗證依賴項是否與預期的版本逐字相同,然後再將它們用於構建。但 `go.sum` 檔案僅列出該模組使用的特定依賴項的雜湊。如果你使用 `go get -u` 新增新依賴項或更新依賴項,`go.sum` 中沒有相應的條目,因此對下載的位元組沒有直接認證。
對於公開可用的 modules,我們打算執行一個名為notary的服務,該服務會跟隨 module index 日誌,下載新 modules,並以“module M 在版本 V 具有檔案樹雜湊 H”的形式對語句進行加密簽名。notary 服務將在一個可查詢的、證書透明度風格的防篡改日誌中釋出所有這些公證的雜湊,以便任何人都可以驗證 notary 是否行為正確。此日誌將作為公共的、全域性的 `go.sum` 檔案,`go get` 可以使用它在新增或更新依賴項時對 modules 進行認證。
我們的目標是在 Go 1.13 中開始,讓 `go` 命令檢查 `go.sum` 中尚未存在的公開可用的 modules 的公證雜湊。
Module 映象
由於去中心化的 `go get` 從多個源伺服器獲取程式碼,因此獲取程式碼的速度和可靠性僅取決於最慢、最不可靠的伺服器。在 modules 出現之前,唯一的防禦措施是將依賴項打包到自己的儲存庫中。雖然打包將繼續得到支援,但我們更希望找到一個適用於所有 modules(而不僅僅是你已經使用的那些)的解決方案,並且不需要將依賴項複製到使用它的每個儲存庫中。
Go module 設計引入了 module proxy 的概念,它是一個 `go` 命令向其請求 modules 的伺服器,而不是源伺服器。一種重要的代理型別是module mirror,它透過從源伺服器獲取 modules 來響應請求,然後快取它們以供將來的請求使用。一個執行良好的 mirror 即使在一些源伺服器宕機時也應該快速可靠。我們計劃在 2019 年為公開可用的 modules 推出一個 mirror 服務。GoCenter 和 Athens 等其他專案也在計劃推出 mirror 服務。(我們預計公司將有多種選項來執行自己的內部 mirror,但這篇博文重點關注公共 mirror。)
Mirrors 的一個潛在問題是它們正是中間人伺服器,這使它們成為攻擊的自然目標。Go 開發人員需要一些保證,即 mirrors 提供與源伺服器相同的位元組。上一節所述的 notary 流程恰恰解決了這個問題,並且它將同時適用於使用 mirrors 和源伺服器的下載。mirrors 本身不必被信任。
我們的目標是讓 Google 執行的 module mirror 從 Go 1.13 開始在 `go` 命令中預設可用。使用備用 mirror,或根本不使用 mirror,都可以輕鬆配置。
Module 發現
最後,我們之前提到 module index 將使構建 godoc.org 等網站更加容易。我們在 2019 年的工作將包括對 godoc.org 的重大改造,使其對需要發現可用 modules 並決定是否依賴給定 module 的開發人員更有用。
大局
此圖展示了 module 原始碼如何透過本文中的設計進行傳輸。

以前,所有 Go 原始碼的消費者——`go` 命令和 godoc.org 等任何網站——直接從每個程式碼主機獲取程式碼。現在,它們可以從快速可靠的 mirror 獲取快取的程式碼,同時仍然能夠驗證下載的位元組是否正確。而 index 服務使 mirrors、godoc.org 和任何其他類似網站能夠輕鬆跟上每天新增到 Go 生態系統的所有精彩新程式碼。
我們對 Go modules 在 2019 年的未來感到興奮,希望你也是。新年快樂!
下一篇文章:Go 1.12 已釋出
上一篇文章:Go 2,我們來了!
部落格索引