简介
本文演示了一个简单的Go语言包的开发,以及 go tool
命令的使用,包含:获取、构建、安装Go包和命令的标准方法。
go tool
要求用特别的方式来组织你的Go代码。仔细阅读本文,它解释了启动和运行Go安装的最简单方法。
代码组织
概述
- 程序员通常会将所有Go代码保存在一个工作区中
- 工作空间包含许多版本控制的仓库(repo)(例如,由Git管理的)
- 每个仓库包含一个或多个包
- 每个包由一个目录中的一个或多个Go源文件组成
- 包目录的路径确定其导入路径
请注意,这与其他编程环境不同,在这些环境中,每个项目都有一个单独的工作区,工作区与版本控制仓库紧密相关。
工作区
工作空间是一个目录层次结构,其根目录有两个目录:
-
src
用于存放Go原文件 -
bin
存放可执行的命令
go工具构建二进制文件并将其安装到 bin
目录。
src
子目录通常包含多个版本控制的仓库(例如Git),用于跟踪一个或多个源包的开发。
在实践中,工作区应该是什么样子呢? 下面给出一个例子:
bin/
hello # command executable
outyet # command executable
src/
github.com/golang/example/
.git/ # Git repository metadata
hello/
hello.go # command source
outyet/
main.go # command source
main_test.go # test source
stringutil/
reverse.go # package source
reverse_test.go # test source
golang.org/x/image/
.git/ # Git repository metadata
bmp/
reader.go # package source
writer.go # package source
... (many more repositories and packages omitted) ...
上面的这个树型结构展示出一个工作区包含了2个仓库(example
和 image
)。example
仓库包含了2个命令(hello
和 outyet
)和一个库(stringutil
)。image
仓库包含了 bmp
包和 一些其他的包。
通常工作区会包含很多的源仓库(包含需要多和命令)。大多数的Go开发者都会把他们的源代码和依赖存放在一个工作区。
命令和库是从不同类型的源包构建的。我们稍后会讨论这种区别。
GOPATH 环境变量
GOPATH
环境变量用来指定工作区的位置。默认是用户主目录的 go
目录,如在Linux和macOS上是 $HOME/go
, 在Windows上是 C:\Users\YourName\go
。
go env GOPATH
命令会打印出当前有效的 GOPATH
; 如果环境变量没有被设置会打印出默认的位置。
为方便起见,将工作空间的 bin
子目录添加到 PATH
:
$ export PATH=$PATH:$(go env GOPATH)/bin
为简洁起见,本文档其余部分中的脚本使用 $GOPATH
而不是 $(go env GOPATH)
。即
$ export PATH=$PATH:$GOPATH/bin
而如果还没有设置 $GOPATH
就运行写好的脚本,你需要替换为 $HOME/go
, 否则需要执行:
$ export GOPATH=$(go env GOPATH)
要学习更多关于gopath环境变量,可以使用查看帮助 go help gopath
要使用自定义的工作区,可以查看 https://golang.org/wiki/SettingGOPATH
导入路径
导入路径(import path)是唯一标识包的字符串。包的导入路径对应于其在工作空间内或远程仓库中的位置(如下所述)。
标准库中的包具有简短的导入路径,例如 fmt
和 net/http
。对于我们自己开发的包您必须选择一个基本路径,该路径不太可能与将来添加到标准库或其他外部库中发生冲突。
如果将代码保存在某个源仓库中,则应使用该源仓库的根作为基本路径。例如,如果你在 github.com/user
上有一个GitHub帐户,那么这应该是你的基本路径。
请注意,在构建代码之前,无需将代码发布到远程仓库。组织代码只是一个好习惯,好像有一天你会发布它一样。实际上,你可以选择任意路径名称,只要它对标准库和更大的Go生态系统是唯一的。
我们将使用 github.com/user
作为我们的基本路径。在工作区内创建一个目录,用于保存源代码:
$ mkdir -p $GOPATH/src/github.com/user
你的第一个Go程序
要编译和运行一个简单的程序,首先要选择一个包路径(我们会使用 github.com/user/hello
),然后在工作区里创建一个相应的包目录:
$ mkdir $GOPATH/src/github.com/user/hello
接下来,在hello目录里创建一个 hello.go
文件,写入以下内容:
package main
import "fmt"
func main() {
fmt.Println("Hello, world.")
}
现在就可以使用go工具来构建和安装该程序:
go install github.com/user/hello
注意,你可以在系统上的任何地方运行该命令。go工具通过在 GOPATH
指定的工作空间内查找 github.com/user/hello
包来查找源代码。
如果是在这个包目录内运行 go install
也可以忽略包路径:
$ cd $GOPATH/src/github.com/user/hello
$ go install
该命令会生成一个 hello
命令,生成一个可执行的二进制文件。同时安装到工作区目录下的 bin
目录,生成的可执行文件是 hello
(如果是windows则是 hello.exe)。
在本例子中是 $GOPATH/bin/hello
, 也就是 $HOME/go/bin/hello
。
当有错误发生的时候,go工具仅会打印除错误,所以如果没有任何输出的时候说明已经执行成功。
可以通过全路径来运行:
$ $GOPATH/bin/hello
Hello, world.
如果已经把 $GOPATH/bin
加入到了 PATH
, 可以直接输入二进制文件名:
$ hello
Hello, world.
如果你正在使用一个版本控制系统,比如Git,现在是时候来初始化来生成一个仓库(repository),然后添加文件,做第一次提交。
当然这一步是可选的,不一定非要使用版本控制系统来写Go代码
$ cd $GOPATH/src/github.com/user/hello
$ git init
Initialized empty Git repository in /home/user/work/src/github.com/user/hello/.git/
$ git add hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
1 file changed, 1 insertion(+)
create mode 100644 hello.go
$ git push origin master
你的第一个库
我们再来写一个库,并在 hello
程序中使用。
首先,第一步确定好包路径,我们使用 github.com/user/stringutil
, 创建包目录
$ mkdir $GOPATH/src/github.com/user/stringutil
其次,创建一个名为 reverse.go
的文件,并写入以下内容:
// Package stringutil contains utility functions for working with strings.
package stringutil
// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
使用 go build
编译该包:
$ go build github.com/user/stringutil
如果已经在 github.com/user/stringutil
目录里,则直接执行:
$ go build
当然该命令不会生成文件,而是把编译好的包放到了本地的构建(build)缓存里。
确实 stringutil
包被编译后, 修改 hello.go
:
vim $GOPATH/src/github.com/user/hello
修改后的:
package main
import (
"fmt"
"github.com/user/stringutil"
)
func main() {
fmt.Println(stringutil.Reverse("!oG ,olleH"))
}
再次安装
$ go install github.com/user/hello
执行:
$ hello
Hello, Go!
通过上面的一些步骤后,现在我们的结构是这样子的:
bin/
hello # command executable
src/
github.com/user/
hello/
hello.go # command source
stringutil/
reverse.go # package source
包名
在Go原文件中第一个使用的语句必须是
package name
其中 name
就是包的默认名称。一个包中的所有文件必须使用相同的包名。
Go的约定是包名称是导入路径的最后一个元素,例如导入的包 crypto/rot13
, 包名就是 rot13
如果是可执行的文件,包名必须使用 main
。
不强制要求所有的包名都是唯一的,但是要求导入的路径必须是唯一的(全路径文件名)。
更多关于go的命名规范可以查看 Effective Go
测试
Go提供了一个由 go test
和 testing
包组成的测试框架。
通过创建一个以 _test.go
结尾的文件,里面写有以 TestXXX
开头的函数。测试框架会运行每一个这样的函数,如果函数调用了一个失败的函数,如 t.Error
或 t.Error
, 那么测试就算不通过。
通过添加一个测试文件到 stringutil
包中,
$ vim $GOPATH/src/github.com/user/stringutil/reverse_test.go
添加如下代码:
package stringutil
import "testing"
func TestReverse(t *testing.T) {
cases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{"Hello, 世界", "界世 ,olleH"},
{"", ""},
}
for _, c := range cases {
got := Reverse(c.in)
if got != c.want {
t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
}
}
}
然后运行测试 go test
:
$ go test github.com/user/stringutil
ok github.com/user/stringutil 0.165s
如果当前是在 go test github.com/user/stringutil
目录中,则直接执行:
$ go test
ok github.com/user/stringutil 0.165s
更多细节可以运行 go run test 和 查看 测试包文档
远程包
导入路径可以描述如何使用诸如Git之类的版本控制系统来获取包源代码。go工具使用此属性自动从远程仓库获取包。例如,本文档中描述的示例也保存在GitHub github.com/golang/example
上托管的Git仓库中。如果你在包的导入路径中包含仓库URL,那么go将自动获取,构建和安装它:
$ go get github.com/golang/example/hello
$ $GOPATH/bin/hello
Hello, Go examples!
如果指定的包不在工作区, go get
将会通过 GOPATH
把它放到指定的工作区,如果包已经存在, go get
会跳过远程获取,其行为与 go install
相同。
上面 go get
之后的目录结构如下:
bin/
hello # command executable
src/
github.com/golang/example/
.git/ # Git repository metadata
hello/
hello.go # command source
stringutil/
reverse.go # package source
reverse_test.go # test source
github.com/user/
hello/
hello.go # command source
stringutil/
reverse.go # package source
reverse_test.go # test source
在GitHub上托管的 hello
命令取决于同一仓库中的 stringutil
包。 hello.go
文件中的导入使用相同的导入路径约定,因此 go get
命令也能够找到并安装依赖包。
import "github.com/golang/example/stringutil"
此约定是使你的Go包可供其他人使用的最简单方法。
Go Wiki和godoc.org提供了外部Go项目的列表。
有关使用go工具使用远程仓库的更多信息, 可以查看 go help importpath