包管理是Go一直被诟病做得不好的功能之一。先前版本(go 1.11之前)的主要缺点之一是go get是缺乏对依赖包版本的管理和对可复制构建(reproducible build)的支持。Go社区已经开发了一些包管理器和工具作为版本化包依赖的事实标准解决方案,如glide,dep以及一些辅助工具等。
“我在生产构建中使用go get。” – 没有人这么说过。
Go语言的包管理实现可追溯到Google公司内的代码依赖管理(Google将内部所有源代码都存放在一个巨大的单体存储库中)。我们来分析一下在”Go module”之前Go语言的包管理工具都出了什么问题。
依赖包的版本化
依赖包的本地缓存(vendor)
GOPATH的必要性
依赖包的版本化
go get默认情况下不支持包版本控制。go软件包管理的第一版实现背后的想法是-不需要包版本控制,不需要第三方包存储库,您可以从当前分支中构建所有内容。
在Go 1.11之前的版本中,添加依赖项意味着将该依赖项的源代码仓库克隆到$GOPATH下面。就是这样,没有版本的概念。版本始终指向克隆时刻的主分支。出现了另一个主要问题是,当不同的项目需要依赖包的不同版本时,Go包管理工具无法实现。
依赖包的本地缓存(vendor)
依赖包本地缓存通常是指相关依赖包与项目存储在同一位置。这通常意味着将您的依赖项源码也提交到源管理系统中,例如Git。
考虑这样一种情况- A使用依赖项B,而B使用了C版本在1.5版本中引入一个功能,这时B必须确保A在构建时使用的也是C 1.5或更高版本。在Go 1.5之前的版本中,没有一种机制可以在不重写导入路径的情况下将依赖包代码与命令绑定在一起。
GOPATH的必要性
GOPATH存在的主要原因有两个:
在Go中,import声明通过其完全限定的导入路径来引用包。GOPATH存在可以方便Go工具计算GOPATH/src内的任何目录所涉及软件包的绝对导入路径。
它是Go get命令存储包依赖项的位置。
这有什么问题?
GOPATH 不允许开发人员像其他语言一样选择任意喜欢的目录签出项目的源代码。
此外,GOPATH不允许开发人员同时检出某个项目(或其依赖项)的多个副本。
Go Module介绍
Go 1.11引入了对Go模块(module)的初步支持。下面摘自Go Wiki:
一个模块是一组相关的Go包的集合,这个包集合被当做一个独立的单元进行统一版本管理。模块精确记录了依赖要求并支持创建可复制的构建。
Go模块带来了三个重要的内置功能:
go.mod文件,它与package.json或Pipfile文件的功能类似。
机器生成的传递依赖项描述文件 – go.sum。
不再有GOPATH限制。模块可以位于任何路径中。