關於 go 指令
Go 發行版包含一個名為「go
」的指令,可自動下載、建置、安裝和測試 Go 套件和指令。本文將說明我們為何撰寫新的指令、指令的內容、指令的限制以及如何使用指令。
動機
您可能看過 Rob Pike 在早期 Go 演講中開玩笑地說,Go 的構想是源自於等待大型 Google 伺服器編譯時。這的確是 Go 的動機:建構一種語言,能順利建置 Google 編寫和執行的龐大軟體。從一開始就清楚,這種語言必須提供一種方式,明確表達程式庫之間的相依性,因此有了套件分組和明確的匯入區塊。從一開始也清楚,您可能需要任意的語法來描述要匯入的程式碼;這就是匯入路徑是字串文字的原因。
從一開始,Go 的明確目標就是能夠僅使用來源本身找到的資訊來建置 Go 程式碼,不需要撰寫 makefile 或許多現代 makefile 替換程式之一。如果 Go 需要一個設定檔來說明如何建置您的程式,那麼 Go 就失敗了。
一開始,沒有 Go 編譯器,而最初的開發重點在於建置一個編譯器,然後為編譯器建置程式庫。為了加快速度,我們延後使用 make 和撰寫 makefile 來自動化建置 Go 程式碼。當編譯單一套件需要呼叫 Go 編譯器多次時,我們甚至使用一個程式為我們撰寫 makefile。如果您深入探索儲存庫記錄,就能找到它。
新的 go 指令的目的是回歸這個理想,即 Go 程式應在開發人員撰寫必要的 import 陳述之外,無需設定或額外工作即可編譯。
設定與慣例
達成無設定系統簡潔性的方法是建立慣例。系統僅在遵循這些慣例的範圍內運作。當我們首次推出 Go 時,許多人發布了必須使用某些建置工具,在特定位置和名稱下安裝的套件才能使用。這是可以理解的:這是大多數其他語言的運作方式。在過去幾年中,我們持續提醒人們關於 goinstall
指令(現在已由 go get
取代)及其慣例:首先,匯入路徑是以已知方式從原始碼的 URL 衍生而來;其次,儲存在本地檔案系統中的原始碼位置是以已知方式從匯入路徑衍生而來;第三,原始碼樹中的每個目錄對應到一個套件;第四,套件僅使用原始碼中的資訊建置。如今,絕大多數套件都遵循這些慣例。因此,Go 生態系統更為簡潔且強大。
我們收到了許多要求,希望允許套件目錄中的 makefile 提供比原始碼中更多的額外設定。但這會引入新的規則。由於我們沒有同意這些要求,因此我們能夠撰寫 go 指令並消除我們對 make 或任何其他建置系統的使用。
了解 go 指令不是一般建置工具非常重要。它無法設定,而且除了 Go 套件之外,它不會嘗試建置任何東西。這些是重要的簡化假設:它們不僅簡化了實作,更重要的是簡化了工具本身的使用。
Go 的慣例
go
指令要求程式碼遵守一些關鍵且已建立的慣例。
首先,匯入路徑會以已知的方式從原始碼的 URL 中衍生。對於 Bitbucket、GitHub、Google Code 和 Launchpad,儲存庫的根目錄會由儲存庫的主要 URL 識別,不含 https://
前置詞。子目錄會透過新增至該路徑來命名。例如,Google 記錄套件 glog
的原始碼會透過執行以下指令取得
git clone https://github.com/golang/glog因此 glog 套件的匯入路徑為「
github.com/golang/glog
」。
這些路徑較長,但作為交換,我們取得了自動管理的匯入路徑名稱空間,以及讓 go 指令等工具得以查看陌生的匯入路徑並推論出取得原始碼的位置的能力。
其次,儲存在本機檔案系統中的來源位置會以已知的方式從匯入路徑衍生,特別是 $GOPATH/src/<import-path>
。如果未設定,$GOPATH
預設為使用者家目錄中名為 go
的子目錄。如果 $GOPATH
設定為路徑清單,go 指令會對清單中的每個目錄嘗試 <dir>/src/<import-path>
。
根據慣例,這些樹狀結構各包含一個名為「bin
」的頂層目錄,用於存放已編譯的可執行檔,以及一個名為「pkg
」的頂層目錄,用於存放可匯入的已編譯套件,還有「src
」目錄,用於存放套件原始碼檔案。強制使用這個結構讓我們能讓這些目錄樹各自分開:已編譯的格式和來源總是彼此相鄰。
這些命名慣例也讓我們可以從目錄名稱反向運作,到它的匯入路徑。此對應對於許多 go 指令的子指令而言非常重要,如下所示。
第三,來源樹中的每個目錄都對應到一個單一套件。透過將目錄限制到單一套件,我們不必建立混合匯入路徑,先指定目錄,再指定目錄內的套件。此外,大多數檔案管理工具和使用者介面都將目錄視為基本單位。將 Go 的基本單位(套件)連結到檔案系統結構,表示檔案系統工具會變成 Go 套件工具。複製、移動或刪除套件,對應到複製、移動或刪除目錄。
第四,每個套件僅使用來源檔案中存在的資訊來建置。這使得工具更有可能適應變動的建置環境和條件。例如,如果我們允許額外的設定,例如編譯器旗標或命令列範本,則每次建置工具變更時,就需要更新該設定;它也會與特定工具鏈的使用緊密連結。
開始使用 go 指令
最後,快速瀏覽如何使用 go 指令。如上所述,Unix 上的預設 $GOPATH
是 $HOME/go
。我們會將程式儲存在那裡。若要使用不同的位置,您可以設定 $GOPATH
;請參閱 如何撰寫 Go 程式碼 以取得詳細資料。
我們先新增一些來源程式碼。假設我們想要使用 codesearch 專案的索引函式庫,以及一個左傾紅黑樹。我們可以使用「go get
」子指令安裝這兩個
$ go get github.com/google/codesearch/index $ go get github.com/petar/GoLLRB/llrb $
這兩個專案現在已下載並安裝到 $HOME/go
,其中包含兩個目錄 src/github.com/google/codesearch/index/
和 src/github.com/petar/GoLLRB/llrb/
,以及這些函式庫和其依賴項目的已編譯套件 (在 pkg/
中)。
由於我們使用版本控制系統 (Mercurial 和 Git) 來檢出來源,因此來源樹也包含對應儲存庫中的其他檔案,例如相關套件。「go list
」子指令會列出對應其引數的匯入路徑,而模式「./...
」表示從目前目錄 («./
») 開始,並找出該目錄以下的所有套件 («...
»)
$ cd $HOME/go/src $ go list ./... github.com/google/codesearch/cmd/cgrep github.com/google/codesearch/cmd/cindex github.com/google/codesearch/cmd/csearch github.com/google/codesearch/index github.com/google/codesearch/regexp github.com/google/codesearch/sparse github.com/petar/GoLLRB/example github.com/petar/GoLLRB/llrb $
我們也可以測試這些套件
$ go test ./... ? github.com/google/codesearch/cmd/cgrep [no test files] ? github.com/google/codesearch/cmd/cindex [no test files] ? github.com/google/codesearch/cmd/csearch [no test files] ok github.com/google/codesearch/index 0.203s ok github.com/google/codesearch/regexp 0.017s ? github.com/google/codesearch/sparse [no test files] ? github.com/petar/GoLLRB/example [no test files] ok github.com/petar/GoLLRB/llrb 0.231s $
如果 go 子指令在沒有列出路徑的情況下被呼叫,它會在目前目錄中執行
$ cd github.com/google/codesearch/regexp $ go list github.com/google/codesearch/regexp $ go test -v === RUN TestNstateEnc --- PASS: TestNstateEnc (0.00s) === RUN TestMatch --- PASS: TestMatch (0.00s) === RUN TestGrep --- PASS: TestGrep (0.00s) PASS ok github.com/google/codesearch/regexp 0.018s $ go install $
「go install
」子指令會將套件的最新副本安裝到 pkg 目錄中。由於 go 指令可以分析依賴關係圖,「go install
」也會安裝此套件匯入的任何套件,但這些套件已過時,並遞迴執行。
請注意,「go install
」能夠根據目錄命名慣例,判斷目前目錄中套件的匯入路徑名稱。如果我們可以選擇儲存原始碼的目錄名稱,會更方便一些,而且我們可能會選擇較短的名稱,但這種功能需要工具額外的設定和複雜性。輸入一或兩個額外的目錄名稱,是為了換取更高的簡潔性和功能,代價很小。
限制
如上所述,go 命令並非通用建置工具。特別是,它沒有任何功能可在建置期間產生 Go 原始檔,雖然它確實提供 go
generate
,它可以在建置之前自動建立 Go 檔。對於更進階的建置設定,您可能需要撰寫 makefile(或您選擇的建置工具設定檔),以執行建立 Go 檔的工具,然後將這些產生的原始檔檢查到您的儲存庫中。對您而言,這項工作較多,但對您的使用者而言,工作量會少很多,他們可以使用「go get
」而不需要取得和建置任何其他工具。
更多資訊
如需更多資訊,請閱讀 如何撰寫 Go 程式碼,並參閱 go 命令文件。