import的基本用法

1. 导入包的3种方法

Go中导入包有3种方法:

  • 标准库
  • 相对路径
  • 绝对路径(相对于$GOPATH/src的路径)

例如:

1
2
3
import "fmt"          // 标准库,去GOROOT下加载该模块
import "./controller" // 相对路径,当前文件同一目录的controller目录。不推荐
import "models/User" // 相对于$GOPATH/src目录的路径,加载$GOPATH/src/models/User模块。推荐

执行import时会去寻找$GOPATH下的src文件夹,src文件夹是默认的源代码所在的文件夹。使用GoLand IDE时,如果没有src文件夹,它会自动生成一个,在导入的路径下还会生成一个.idea文件夹。

2. vendor文件夹

2.1. 提出问题

我们知道,一个工程稍大一点,通常会依赖各种各样的包。而Go使用统一的GOPATH管理依赖包,且每个包仅保留一个版本。而不同的依赖包由各自的版本工具独立管理,所以当所依赖的包在新版本发生接口变更或删除时,会面临很多问题。

为避免此类问题,我们可能会为不同的工程设置不同的GOPATH,或者更改依赖包路径名称。这样手动维护起来也很头疼。

2.2. 解决问题

Go 1.5引入了vendor文件夹,其对语言使用,go命令没有任何影响。若某个路径下边包含vendor文件夹,则在某处引用包时,会优先搜索vendor文件夹下的包。vendor文件夹必须要在$GOPATH/src下。

在Go 1.5开启该项特性需设置GO15VENDOREXPERIMENT=1,而从Go 1.6开始,该项特性默认开启。

2.3. 使用方式

2.3.1 vendor搜索方式

vendor包的搜索方式为:自包引用处,从其所在文件夹查询是否有vendor文件夹包含所引用包;若没有,然后从其所在文件夹的上层文件夹寻找是否有vendor文件夹包含所引用包,若没有,则再搜索上层文件夹的上层文件夹…,直至搜索至$GOPATH/src并搜索完成时止。

例如,如下代码中,$GOPATH/src/x/y/z/main.go引用了包”v”,则不论vendor/v/v.go置于

  • src/
  • src/x/
  • src/x/y/
  • src/x/y/z/

中任意一个文件夹下,均可以找到。

1
2
3
4
5
6
7
8
9
10
11
$ cat $GOPATH/src/x/y/z/main.go

package main

import (
"v"
)

func main() {
v.V()
}
1
2
3
4
5
6
7
8
9
$ cat vendor/v/v.go

package v

import "fmt"

func V() {
fmt.Println("I'm a vendor test")
}
1
2
3
$ go run main.go

I'm a vendor test

当vendor存在嵌套时,若不同的vendor文件夹包含相同的包,且该包在某处被引用,寻找策略仍遵循如上规则。即从包引用处起,逐层向上层文件夹搜索,首先找到的包即为所引,也就是从$GOPATH/src来看,哪个vendor包的路径最长,使用哪个。

如下代码中,$GOPATH/src/x/y/z/main.go所在工程有两个vendor文件夹,分别位于:

  • $GOPATH/src/x/vendor/v/
  • $GOPATH/src/x/y/z/vendor/v/

包含相同的包”v”,目录树为:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ tree $GOPATH/src

src
└ x
├ vendor
│ └ v
│ └ v.go
└ y
└ z
├ vendor
│ └ v
│ └ v.go
└ main.go
1
2
3
4
5
6
7
8
9
$ cat $GOPATH/src/x/vendor/v/v.go

package v

import "fmt"

func V() {
fmt.Println("I'm a vendor test, My path is x/vendor/v/")
}
1
2
3
4
5
6
7
8
9
$ cat $GOPATH/src/x/y/z/vendor/v/v.go

package v

import "fmt"

func V() {
fmt.Println("I'm a vendor test, My path is x/y/z/vendor/v/")
}

输出为:

1
2
3
$ go run main.go

I'm a vendor test, My path is x/y/z/vendor/v/

可以看到,真正调用的是$GOPATH/src/x/y/z/vendor/v/v.go

2.3.2 vendor使用规约

使用vendor时,建议遵循如下两条规约。

  • 当欲将某包vendor时,可能想将所有依赖包均vendor;
  • 尽量将vendor依赖包结构扁平化,不要vendor套vendor。

如下示例代码演示vendor扁平化使用。
main.go位于$GOPATH/src/github.com/olzhy/test下。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"strings"
"sync"
"time"

"github.com/x"
"github.com/y"
"golang.org/z"
)
...

$GOPATH/src/github.com/olzhy/test目录树:

1
2
3
4
5
6
7
├─ main.go
└─ vendor
├─ github.com
│ ├─ x
│ └─ y
└─ golang.org
└─ z

3. 寻找导入的package的方法

在导入了一个工程,比如github上的repo之后,寻找工程中import的包的方法:

  • GO111MODULE=off 禁用 go module,编译时会从 GOPATH 和 vendor 文件夹中查找包;
  • GO111MODULE=on 启用 go module,编译时会忽略 GOPATH 和 vendor 文件夹,只根据 go.mod下载依赖;
  • GO111MODULE=auto(默认值),当项目在 GOPATH/src 目录之外,并且项目根目录有 go.mod 文件时,开启 go module。

关于Go Module的介绍: Go Module

在没有开启Go Module的时候,寻找依赖包的顺序:

  • 寻找本地的vendor路径(如果有的话);
  • 如果不能在vendor路径下找到被导入的包,到当前路径的上一级路径寻找vendor路径(如果有的话),看是否能找到被导入的包;
  • 继续第2步,直到到达$GOPATH/src
  • 如果还没有找到被导入的包,查看$GOROOT
  • 如果$GOROOT下也没有,在$GOPATH/src路径下寻找。

参考资料

[1] https://leileiluoluo.com/posts/golang-vendoring.html