Go語言的工具箱集合了一系列的功能的命令集。它能夠看做是一個包管理器(相似於Linux中的apt和rpm工具),用於完成包的查詢、計算的包依賴關係、從遠程版本控制系統和下載它們等任務。它也是一個構建系統,計算文件的依賴關係,而後調用編譯器、彙編器和鏈接器構建程序。它被設計成沒有標準的make命令那麼複雜。它也是一個單元測試和基準測試的驅動程序。html
Go語言工具箱的命令有着相似「瑞士軍刀」的風格,帶着一打子的子命令,有一些咱們常常用到,例如get、run、build和fmt等。你能夠運行go或go help命令查看內置的幫助文檔,爲了查詢方便,咱們列出了最經常使用的命令:linux
$ go ... build compile packages and dependencies clean remove object files doc show documentation for package or symbol env print Go environment information fmt run gofmt on package sources get download and install packages and dependencies install compile and install packages and dependencies list list packages run compile and run Go program test test packages version print Go version vet run go tool vet on packages Use "go help [command]" for more information about a command. ...
注:go 命令在不一樣的Go版本下運行會有不一樣的輸出,尤爲是在 1.11版本後會有 mod 子命令,咱們不會涉及go mod
這個話題。git
使用Go語言工具箱的go命令,不只能夠根據包導入路徑找到本地工做區的包,甚至能夠從互聯網上找到和更新包。github
使用命令go get
能夠下載一個單一的包或者用...
下載整個子目錄裏面的每一個包。go命令同時計算並下載所依賴的每一個包。一旦go get
命令下載了包,後面就是安裝包或包對應的可執行的程序。golang
go get
命令支持當前流行的託管網站GitHub、Bitbucket和Launchpad,能夠直接向它們的版本控制系統請求代碼。對於其它的網站,你可能須要指定版本控制系統的具體路徑和協議,例如 Git或Mercurial。運行go help importpath
獲取相關的信息。shell
go get
命令獲取的代碼是真實的代碼倉庫,而不只僅只是複製源文件,所以你依然可使用版本管理工具比較本地代碼的變動或者切換到其它的版本。例如golang.org/x/net包目錄對應一個Git倉庫:小程序
$ cd $GOPATH/src/golang.org/x/net $ git remote -v origin https://go.googlesource.com/net (fetch) origin https://go.googlesource.com/net (push)
須要注意的是golang.org/x/net
包的導入路徑含有的網站域名和本地Git倉庫對應遠程服務地址並不相同,真實的Git地址是go.googlesource.com。這實際上是Go語言工具的一個特性,可讓包用一個自定義的導入路徑,可是真實的代碼倒是由更通用的服務提供,例如googlesource.com或github.com。由於頁面 https://golang.org/x/net/html 包含了以下的元數據,它告訴Go語言的工具當前包真實的Git倉庫託管地址:瀏覽器
$ go build gopl.io/ch1/fetch $ ./fetch https://golang.org/x/net/html | grep go-import <meta name="go-import" content="golang.org/x/net git https://go.googlesource.com/net">
若是指定-u
命令行標誌參數,go get
命令將確保全部的包和依賴的包的版本都是最新的,而後從新編譯和安裝它們。若是不包含該標誌參數的話,並且若是包已經在本地存在,那麼將不會被自動更新。架構
go get -u
命令只是簡單地保證每一個包是最新版本,若是是第一次下載包則是比較很方便的;可是對於已經發布的程序則多是不合適的,由於程序可能須要對依賴的包作精確的版本依賴管理。ide
go build
命令編譯命令行參數指定的每一個包。若是包是一個庫,則忽略輸出結果;這能夠用於檢測包是否能夠被正確編譯。若是包的名字是main,go build
將調用鏈接器在當前目錄建立一個可執行程序;以導入路徑的最後一段做爲可執行程序的名字。
由於每一個目錄只包含一個包,所以每一個對應可執行程序的包,會要求放到一個獨立的目錄中。這些目錄有時候會放在名叫cmd目錄的子目錄下面,例如用於提供Go文檔服務的golang.org/x/tools/cmd/godoc命令就是放在cmd子目錄。
每一個包能夠由它們的導入路徑指定,就像前面看到的那樣,或者用一個相對目錄的路徑指定,相對路徑必須以.
或..
開頭。若是沒有指定參數,那麼默認指定爲當前目錄對應的包。 下面的命令用於構建同一個包, 雖然它們的寫法各不相同:
$ cd $GOPATH/src/gopl.io/ch1/helloworld $ go build
或者:
$ cd anywhere $ go build gopl.io/ch1/helloworld
或者:
$ cd $GOPATH $ go build ./src/gopl.io/ch1/helloworld
但不能這樣:
$ cd $GOPATH $ go build src/gopl.io/ch1/helloworld Error: cannot find package "src/gopl.io/ch1/helloworld".
也能夠指定包的源文件列表,這通常這隻用於構建一些小程序或作一些臨時性的實驗。若是是main包,將會以第一個Go源文件的基礎文件名做爲最終的可執行程序的名字。
$ cat quoteargs.go package main import ( "fmt" "os" ) func main() { fmt.Printf("%q\n", os.Args[1:]) } $ go build quoteargs.go $ ./quoteargs one "two three" four\ five ["one" "two three" "four five"]
特別是對於這類一次性運行的程序,咱們但願儘快的構建並運行它。go run
命令其實是結合了構建和運行的兩個步驟:
$ go run quoteargs.go one "two three" four\ five ["one" "two three" "four five"]
第一行的參數列表中,第一個不是以.go
結尾的將做爲可執行程序的參數運行。
默認狀況下,go build
命令構建指定的包和它依賴的包,而後丟棄除了最後的可執行文件以外全部的中間編譯結果。依賴分析和編譯過程雖然都是很快的,可是隨着項目增長到幾十個包和成千上萬行代碼,依賴關係分析和編譯時間的消耗將變的可觀,有時候可能須要幾秒種,即便這些依賴項沒有改變。
go install
命令和go build
命令很類似,可是它會保存每一個包的編譯成果,而不是將它們都丟棄。被編譯的包會被保存到$GOPATH/pkg目錄下,目錄路徑和 src目錄路徑對應,可執行程序被保存到$GOPATH/bin目錄。(不少用戶會將$GOPATH/bin添加到可執行程序的搜索列表中。)還有,go install
命令和go build
命令都不會從新編譯沒有發生變化的包,這可使後續構建更快捷。爲了方便編譯依賴的包,go build -i
命令將安裝每一個目標所依賴的包。
由於編譯對應不一樣的操做系統平臺和CPU架構,go install
命令會將編譯結果安裝到GOOS和GOARCH對應的目錄。例如,在Mac系統,golang.org/x/net/html包將被安裝到$GOPATH/pkg/darwin_amd64目錄下的golang.org/x/net/html.a文件。
針對不一樣操做系統或CPU的交叉構建也是很簡單的。只須要設置好目標對應的GOOS和GOARCH,而後運行構建命令便可。下面交叉編譯的程序將輸出它在編譯時操做系統和CPU類型:
func main() { fmt.Println(runtime.GOOS, runtime.GOARCH) }
下面以64位和32位環境分別執行程序:
$ go build gopl.io/ch10/cross $ ./cross darwin amd64 $ GOARCH=386 go build gopl.io/ch10/cross $ ./cross darwin 386
有些包可能須要針對不一樣平臺和處理器類型使用不一樣版本的代碼文件,以便於處理底層的可移植性問題或提供爲一些特定代碼的優化。若是一個文件名包含了一個操做系統或處理器類型名字,例如net_linux.go或asm_amd64.s,Go語言的構建工具將只在對應的平臺編譯這些文件。
還有一個特別的構建註釋能夠提供更多的構建過程控制。例如,文件中可能包含下面的註釋:
// +build linux darwin
在包聲明和包註釋的前面,該構建註釋參數告訴go build
只在編譯程序對應的目標操做系統是Linux或Mac OS X時才編譯這個文件。下面的構建註釋則表示不編譯這個文件:
// +build ignore
更多細節,能夠參考go/build包的構建約束部分的文檔。
$ go doc go/build
Go語言的編碼風格鼓勵爲每一個包提供良好的文檔。包中每一個導出的成員和包聲明前都應該包含目的和用法說明的註釋。
Go語言中包文檔註釋通常是完整的句子,第一行是包的摘要說明,註釋後僅跟着包聲明語句。註釋中函數的參數或其它的標識符並不須要額外的引號或其它標記註明。例如,下面是fmt.Fprintf的文檔註釋。
// Fprintf formats according to a format specifier and writes to w. // It returns the number of bytes written and any write error encountered. func Fprintf(w io.Writer, format string, a ...interface{}) (int, error)
Fprintf函數格式化的細節在fmt包文檔中描述。若是註釋後僅跟着包聲明語句,那註釋對應整個包的文檔。包文檔對應的註釋只能有一個(譯註:其實能夠有多個,它們會組合成一個包文檔註釋),包註釋能夠出如今任何一個源文件中。若是包的註釋內容比較長,通常會放到一個獨立的源文件中;fmt包註釋就有300行之多。這個專門用於保存包文檔的源文件一般叫doc.go。
好的文檔並不須要面面俱到,文檔自己應該是簡潔但可不忽略的。事實上,Go語言的風格更喜歡簡潔的文檔,而且文檔也是須要像代碼同樣維護的。對於一組聲明語句,能夠用一個精煉的句子描述,若是是顯而易見的功能則並不須要註釋。
有兩個工具能夠幫到你。
首先是go doc
命令,該命令打印包的聲明和每一個成員的文檔註釋,下面是整個包的文檔:
$ go doc time package time // import "time" Package time provides functionality for measuring and displaying time. const Nanosecond Duration = 1 ... func After(d Duration) <-chan Time func Sleep(d Duration) func Since(t Time) Duration func Now() Time type Duration int64 type Time struct { ... } ...many more...
或者是某個具體包成員的註釋文檔:
$ go doc time.Since func Since(t Time) Duration Since returns the time elapsed since t. It is shorthand for time.Now().Sub(t).
或者是某個具體包的一個方法的註釋文檔:
$ go doc time.Duration.Seconds func (d Duration) Seconds() float64 Seconds returns the duration as a floating-point number of seconds.
第二個工具,名字也叫godoc
,它提供能夠相互交叉引用的HTML頁面,可是包含和go doc
命令相同以及更多的信息。godoc的在線服務 https://godoc.org ,包含了成千上萬的開源包的檢索工具。
你也能夠在本身的工做區目錄運行godoc服務。運行下面的命令,而後在瀏覽器查看 http://localhost:8000/pkg 頁面:
$ godoc -http :8000
在Go語言程序中,包的封裝機制是一個重要的特性。沒有導出的標識符只在同一個包內部能夠訪問,而導出的標識符則是面向全宇宙都是可見的。有時候,一箇中間的狀態可能也是有用的,對於一小部分信任的包是可見的,但並非對全部調用者均可見。例如,當咱們計劃將一個大的包拆分爲不少小的更容易維護的子包,可是咱們並不想將內部的子包結構也徹底暴露出去。同時,咱們可能還但願在內部子包之間共享一些通用的功能。
爲了知足這些需求,Go語言的構建工具對導入路徑包含internal的包作了特殊處理。這種包叫internal包,一個internal包只能被和internal目錄有同一個父目錄的包所導入。例如,net/http/internal/chunked
內部包只能被net/http/httputil
或net/http
包導入,可是不能被net/url
包導入。不過net/url
包能夠導入net/http/httputil
包。
net/http net/http/internal/chunked net/http/httputil net/url