《Go 語言程序設計》讀書筆記 (九) 命令工具集

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/httputilnet/http包導入,可是不能被net/url包導入。不過net/url包能夠導入net/http/httputil包。

net/http
net/http/internal/chunked
net/http/httputil
net/url

相關文章
相關標籤/搜索