Go 包管理的进化史

撸了一段时间的 Go,受不了 Go 的包管理知识点,觉得非常混乱,特意写下本文章进行梳理!

Go 的 workspace

官方文档说:

The go tool requires you to organize your code in a specific way.

翻译过来就是:如果要使用 go tool(go 命令行),那么就必须将代码组织成一种特殊的形式。

特殊形式如下:

1
2
3
4
$GOPATH
├── bin // 可执行的二进制文件
├── pkg // 包的二进制,有这个文件夹的目的是 为了避免重复编译 和 为了平台交叉编译
├── src // 我们的项目代码 和 包的源码

src 这个目录一般由我们自己手动创建。
pkgbin 不用我们手动创建,在执行特定的 go 命令时会自动创建。

在使用 go get 命令时:

1
go get github.com/rogpeppe/godef

执行这条命令时,会将 godef 的源码下载到 src 中,然后进行编译,
由于 godef 是一个工具(tool),所以会在 bin 目录生成 godef.exe 可执行的二进制文件。
(项目源码中 package mainmain 函数?)

1
go get github.com/sony/sonyflake

执行这条命令时,会将 sonyflake 的源码下载到 src 中,然后进行编译,
由于 sonyflake 是一个类库(library),所以会在 bin 目录生成 sonyflake.a 包的二进制文件。
(项目源码中 package mainmain 函数?)
生成包的二进制文件是 为了避免重复编译 和 为了平台交叉编译。

1
2
3
4
5
6
$GOPATH
├── bin
├── pkg
├── src
└── ProjectA // 这里用大驼峰法命名法,是为了和 bin pkg src 明细区分
└── hello.go
1
2
3
4
5
package main

func main(){
println("hello world!")
}

如果 hello.go 是上面代码,
那么执行 go install 则会在 bin 生成 ProjectA.exe 可执行二进制文件。

1
2
3
4
5
package ProjectA

func hello(){
println("hello world!")
}

如果 hello.go 是上面代码,
那么执行 go install 则会在 pkg 生成 ProjectA.a 包的二进制文件。

随便总结一下,go rungo buildgo install的区别

go run:编译并直接运行程序(所以只能对有 main 函数的文件使用),它会产生一个临时文件(但不会生成 .exe 文件),
直接在命令行输出程序执行结果,方便用户调试。

go build:用于测试编译包,主要检查是否会有编译错误,
如果是一个可执行文件的源码(即是 main 包),就会直接生成一个可执行文件。

go install:作用有两步:
第一步是编译导入的包文件,所有导入的包文件编译完才会编译主程序;
第二步是将编译后生成的可执行文件放到 bin 目录下,编译后的包二进制文件放到 pkg 目录下。

多项目包依赖问题

我们再次来看看 Go 的 workspace

1
2
3
4
5
6
7
8
$GOPATH
├── bin
├── pkg
├── src
└── github.com // 用 go get 获取到的包源码
└── ProjectA
└── ProjectB
└── ProjectZ

如果 ProjectA 和 ProjectB 已经进行了一段时间,进入维护阶段,
然后我们开始新的 ProjectZ,导入包的时候,包的作者进行了一些更新,
因此导致 ProjectA 和 ProjectB 编译不通过了,需要进行修改和回归测试…
多么蛋疼的一件事…

或者可以给 $GOPATH 设置多个变量 (目录路径),

1
2
3
4
5
6
$GOPATH_A
├── bin
├── pkg
├── src
└── github.com // 用 go get 获取到的包源码
└── Project
1
2
3
4
5
$GOPATH_Z
├── bin
├── pkg
├── src
└── Project

但用 go get 获取获取的包只会存在 $GOPATH_Asrc

Vendor 解决方案

注意,本方案是是指 go 的版本在 1.6 - 1.11 之间,go1.11 会有新的解决方案。
而写这博文的时候,go 的版本是 1.11.4,所以 Vendor 解决方案只做了解。

官方提供的解决方案是:每个项目下有自己的 vendor 文件夹,这样就能独立依赖包的引用。
然后在使用编译命令的时候,go查找依赖包路径的规则如下:

  1. 当前包下的vendor目录。
  2. 向上级目录查找,直到找到src下的vendor目录。
  3. 在GOPATH下面查找依赖包。
  4. 在GOROOT目录下查找。
1
2
3
4
5
6
7
8
9
10
11
12
13
$GOPATH
├── bin
├── pkg
├── src
└── github.com
└── ProjectA
│ └── vendor // 每个项目下有自己的 vendor 文件夹,这样就能独立依赖包的引用

└── ProjectB
│ └── vendor // 每个项目下有自己的 vendor 文件夹,这样就能独立依赖包的引用

└── ProjectZ
└── vendor // 每个项目下有自己的 vendor 文件夹,这样就能独立依赖包的引用

如果我们用 go get 命令,依赖包还是会下载到src目录中,
那么怎么将包下载到项目中的 vendor 目录中和进行管理 (查看,更新,删除) 呢?
有三个工具:

  • dep (这个是官方提供的工具)
  • glide
  • govendor

由于 go1.11 有新的解决方案,而且上面三个工具的作者也表明不再更新,最多维护一段时间。
所以我们来看看新的 Modules 解决方案。

为了更加详细地讲解 Modules,所以决定用开多一篇新的博文~

觉得文章对您有帮助,请我喝瓶肥宅快乐水可好 (๑•̀ㅂ•́)و✧
  • 本文作者: 阿彬~
  • 本文链接: https://iweixubin.github.io/posts/go-dependency-management-tool-evolution/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 免责声明:本媒体部分图片,版权归原作者所有。因条件限制,无法找到来源和作者未进行标注。
         如果侵犯到您的权益,请与我联系删除