《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

相关文章
相关标签/搜索