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

1 分钟阅读

工作空间

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

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列表中的各个工作空间。

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

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

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

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

有四种不同的导入方式。

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,或等到它结束执行。

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目录下的包(包括自身)仅能被其父目录下的包(包含所有子目录) 访问。

workspace/
  |
  +-- src/
  |    |
  |    +-- main.go
  |    |
  |    +-- lib/
  |         |
  |         +-- internal/
  |         |       |
  |         |       +-- a/
  |         |       |
  |         |       +-- b/
  |         +-- x/
  |             |
  |             +-- y/
  |

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

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

依赖管理

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

workspace/
  |
  +-- src/
  |    |
  |    +-- main.go
  |    |
  |    +-- server/
  |         |
  |         +-- vendor/
  |         |       |
  |         |       +-- github.com/
  |         |              |
  |         |              +-- mercy1101/
                                  |
                                  +-- test/
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目录。当前工具链中并没有真正意义上的包依赖管理,好在由不少第三放工具可选。