0%

《Go语言学习笔记》读书笔记(3)工作空间

工作空间

依照规范,工作空间由srcbinpkg三个目录组成。通常需要将空间路径添加到GOPATH环境变量列表中, 以便相关工具能正常工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
workspace/
|
+-- src/
| |
| +-- main.go
| |
| +-- service/
| |
| +-- user.go
|
+-- bin/
| |
| +-- server
|
+-- pkg/
|
+-- linux_amd64/
|
+-- service.a

在工作空间里,包括子包在内的所有源码文件都保存在src目录下。至于binpkg两个目录, 其主要影响 go install/get命令,他们会将编译结果(可执行文件或静态库)安装到这两个目录下,以实现增量编译。

环境变量

编译器等相关工具按GOPATH设置的路径搜索目标。也就是说在导入目标库时,排在列表前面的路径比当前工作空间优先级更高。另外,go get默认将下载的第三方包保存到列表中第一个工作空间内。

环境变量GOPATH用于指示工具链和标准库的存放位置。在生成工具链时,相关路径就已经嵌入到可执行文件内,故无需额外设置。
除通过设置GOROOT环境变量覆盖内部路径外,还可移动目录(改名、符号链接等), 或重新编译工具链来解决。
至于GOBIN, 则是强制替代工作空间的bin目录,作为go install目标保存路径。这可避免将所有工作空间的bin路径添加到PATH环境变量当中。

导入包

使用标准库或第三方包前,须用import导入,参数是工作空间中以src为起始的绝对路径。编译器从标准库开始搜索,然后依次搜索GOPATH列表中的各个工作空间。

1
import "net/http" // 实际路径: /usr/local/go/src/net/http

除使用默认包名外,还可使用别名,以解决同名冲突问题。

1
2
import osx "github.com/apple/osx/lib"
import nix "github.com/linux/lib"

注意: import导入参数是路径,而非包名。尽管习惯将包和目录名保持一致,但这不是强制规定。在代码中引用包成员时,使用包名而非目录名。

有四种不同的导入方式。

1
2
3
4
import    "github.com/Mercy1101/test" // 默认方式: test.A
import X "github.com/Mercy1101/test" // 别名方式: X.A
import . "github.com/Mercy1101/test" // 简便方式: A
import _ "github.com/Mercy1101/test" // 初始化方式: 无法引用,仅用来初始化目标包。

不能直接或间接导入自己,不支持任何形式的循环导入。

未使用的导入(不包括初始化方式)会被编译器视为错误。

相对路径

除工作空间和绝对路径外,部分工具还支持相对路径。可在非工作空间目录下,直接运行、编译一些测试代码。
但在设置了GOPATH的工作空间后相对路径会导致编译失败。go run不受影响。

初始化

包内每个源码文件都可定义一到多个初始化函数,但编译器不保证执行次序。
实际上,所有这些初始化函数(包括标准库和导入的第三方包)都由编译器自动生成的一个包装函数进行调用,因此可保证在单一线程上执行,且仅执行一次。

编译器首先确保完成所有全局变量初始化,然后才开始执行初始化函数。直到这些全部结束后,运行时才正式进入main.main入口函数。
可在初始化函数中创建goroutine,或等到它结束执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func init(){
done := make(chan struct{})

go func(){
defer close(done)
fmt.Println("init:", time.Now)
time.Sleep(time.Second * 2)
} ()

<-done
}

func main () {
fmt.Println("main: ", time.Now())
}

如果在多个初始化函数中引用全局变量,那么最好在变量定义处直接赋值。因无法保证执行次序,所以任何初始化函数中的赋值都有可能”延迟无效”。

内部包

内部包机制相当于增加了新的访问权限控制:所有保存在internal目录下的包(包括自身)仅能被其父目录下的包(包含所有子目录) 访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
workspace/
|
+-- src/
| |
| +-- main.go
| |
| +-- lib/
| |
| +-- internal/
| | |
| | +-- a/
| | |
| | +-- b/
| +-- x/
| |
| +-- y/
|

lib目录外(比如main.go)导入内部包会引发编译错误。

导入内部包必须使用完整路径, 例如: import “lib/internal/a”

依赖管理

如何使用vendor,专门存放第三方包,实现将源码和依赖完整打包分发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
workspace/
|
+-- src/
| |
| +-- main.go
| |
| +-- server/
| |
| +-- vendor/
| | |
| | +-- github.com/
| | |
| | +-- mercy1101/
|
+-- test/
1
2
3
4
5
package main
import "github.com/Mercy1101/test"
func main(){
test.Hello()
}

main.go中导入github.com/mercy1101/test时,优先使用vendor/github.com/mercy1101/test

导入vendor中的第三方包,参数是以vendor/为起点的绝对路径。这避免了vendor目录位置带来的麻烦,让导入无论使用vendor,还是GOPATH都能保持一致。

注意:vendor优先级比标准库高

当多个vendor目录嵌套时,匹配规则如下:
从当前源文件所在目录开始,逐级向上构造vendor全路径,直到发现路径匹配的目标为止。匹配失败,则依旧搜索GOPATH

要使用vendor机制,须开启GO15VENDOREXPERIMENT=1环境变量开关(Go 1.6默认开启),且必须设置了GOPATH的工作空间。

使用go get下载第三方包时,依旧使用GOPATH第一个工作空间,而非vendor目录。当前工具链中并没有真正意义上的包依赖管理,好在由不少第三放工具可选。