golang 的包管理一直为人所诟病,从GOPATH到vendor再到vgo,都不太理想。于是出现了各种第三方的解决方案。最近发布的 1.11版本,官方推出了 go module,试图统一这种乱象,解决包管理方面的不足。

go modules 是什么

go modules 是golang 官方在 go 1.11 之后推出的包管理机制。

为什么使用 go modules

简单介绍下golang 包管理的发展里程,说明go modules的出现是为了解决什么问题。

go 包管理的发展分为三个阶段:

1.使用 $GOPATH 环境变量

即包需要都放在 $GOPATH/src 目录下。项目引用到的包,都会在这个目录搜索。实际开发中,会有使用同样的包名

使用 GOPATH 有几个缺陷:

  • 能拉取源码的平台很有限,绝大多数依赖的是 github.com
  • 不能区分版本,以至于令开发者以最后一项包名作为版本划分
  • 依赖 列表/关系 无法持久化到本地,需要找出所有依赖包然后一个个 go get
  • 只能依赖本地全局仓库(GOPATH/GOROOT),无法将库放置于局部仓库($PROJECT_HOME/vendor)

于是衍生出其他解决方案

2.使用 vendor 机制。

vendor 机制,在 go 1.5版本推出。工作机理很简单,就是项目中的同名包优先级高于 GOPATH/src目录下的包这种需求,可通过将包放到项目根目录下的 vendor 目录解决。

简而言之,就是vendor下的包,优先级比 GOPATH/src 下的高。项目代码编译时首先搜索的是项目目录下的vendor目录。

使用vendor已经解决项目开发中的难点,然而还谈不上好用,算不上包管理(因为对源代码的复制移动,根本没有算不上对包进行了管理)。具体来说,没有其他语言的包管理工具那样好用的功能。例如

  • vendor目录中依赖包没有版本信息。这样依赖包脱离了版本管理,对于升级、问题追溯,会有点困难。
  • 无法方便的得到本项目依赖了哪些包。
  • 需要把依赖的包人工拷贝到 vendor 目录

于是出现了各种第三方的解决方案,比如godep、govendor glide。

3. Go module

go 1.11 之后官方开始支持 go module。即为了解决以上痛点而生。值得一提的是,推出时,引起一定的争议和反对,许多开发者认为,其设计并不比第三方解决方案出色。但是由于golang官方决定在 1.13(2019年8月) 时正式移除GOPATH的支持,默认使用 go module( GO111MODULE 在 1.11和1.22时使用的是auto,即两种共存,到1.13,将会设置为on,关于GO111MODULE的说明,请看下文)。所以我们还是今早拥抱变化,以官方的为准。

如何使用 Go module

1.相关文件和环境变量说明

go.mod

模块管理以赖于此文件。该文件记录了所依赖的包,及包的版本,类似开发iOS 和 mac 项目中的 Podfile 文件,或者说node.js 中的 package.json文件的作用

格式:

require github.com/astaxie/beego v1.11.1

go.sum

go.sum 的作用,其实有点类似开发 iOS 和mac项目的包管理cocopods的Podfile.lock文件的作用。Podfile.lock 记录的是当时所使用的确切的三方库的版本。go.sum 记录的是编译项目时,各个包的hash快照,都是为了能确定当时编译时所使用的代码,以保证不同项目在不同机器上编译部署的一致性。

GO111MODULE

作为后续版本推出的功能,必然需要一个过渡期,所以golang使用了一个环境变量GO111MODULE 作为该功能的开关。GO111MODULE 有三个值可设置

GO111MODULE=off 无模块支持,go 会从 GOPATH 和 vendor 文件夹寻找包。

GO111MODULE=on 模块支持,go 会忽略 GOPATH 和 vendor 文件夹,只根据 go.mod 下载依赖。

GO111MODULE=auto$GOPATH/src 外面且根目录有 go.mod 文件时,开启模块支持。

使用步骤

1.创建 go.sum 文件

如果项目中原来没有go.sum文件,即未使用go module管理,那么需要创建 在项目根目录下执行

go mod init packagename

packagename,即为当前项目所起的包名

注意,go mod init 必须GO111MODULE=on 开启状态。

如果没有开启,输入命令:export GO111MODULE=on

2.检查并拉取所依赖的包

通过命令

go get ./..

可将当前项目所引用到的包,记录到 go.sum 文件,并下载到 $GOPATH/pkg/mod/cache/ 目录下。这样,编译项目时,可索引到这个位置。

如果希望同时使用vendor的机制呢?

通过命令

go mod vendor

就会将所用到的包,复制一份到项目根目录的 vendor 文件夹下面,这样做的好处是,项目可完整拷贝到其他机器上编译运行。并且可被 vccode 插件 gocode识别,方便 autocomplete。虽然也是拷贝,但比原来的vendor机制一个一个包拷贝进去,这样可以一键完成,算是一种改进。

需要注意的是,如果 GO111MODULE 是开启状态,go build 将不会搜索 vendor目录下的包进行编译,需要在编译时添加参数 go build -mod=vendor

3. go module 其他常用功能解析

3.1 如何升级依赖库?

要升级或降级到更具体的版本,go get 允许通过在 package 参数中添加@version 后缀或“模块查询”来覆盖版本选择,例如 go get foo@v1.6.2go get foo @ e3702bed2,或者 go foo @'<v1.6.2'

  • go get -u 使用最新的次要版本或补丁版本((x.y.z, z是补丁版本号, y是次要版本号))
  • go get -u=patch 将会升级到最新的补丁版本
  • go get package@version 将会升级到指定的版本号version
  • go list -u -m all 查看所有直接和间接依赖项的可用 minor 和 patch 程序升级

3.2 迁移项目时怎么做?

执行 go mod download将所需要的包下载到 $GOPATH/pkg/mod/cache/ 目录下即可。

3.3 怎么替换掉实际下载路径?

什么情况下需要替换下载路径呢?主要三种场景:

  • 场景一:需要科学上网才能下载到的包
  • 场景二:公司内部的项目地址未提交最新,优先使用本地的代码进行开发
  • 场景三:go get 必须要 https,需要下载http的包。
3.3.1 场景一

重新映射下载地址,比如 golang.org 域名被屏蔽,我们可从github上下载包

replace (
	golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac => github.com/golang/crypto v0.0.0-20180820150726-614d502a4dac
	golang.org/x/net v0.0.0-20180821023952-922f4815f713 => github.com/golang/net v0.0.0-20180826012351-8a410e7b638d
	golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0
)
3.3.2 场景二

replace 到本地地址即可

module example.com/proj.git
replace (
example.com/server/common/pub.git => /localpath
)
require (
example.com/server/common/pub.git v1.0.0
)
3.3.3 场景三

并不是所有的包都是https的,如果公司内部的包实用的http,但go 官方并不打算支持http的包下载。那么可通过配合修改git配置来解决。

  • 修改 .gitconfig 文件

    [url "git@insecure-git.com:"]
    insteadOf = http://insecure-git.com/
    

另外还有一种使用场景,如果需要返回的输入