前言

在企业内部项目的开发过程中,我们经常会发现一些跨项目的代码,为了便于维护,我们将这部分代码进行抽象,单独作为一个扩展包,这种方式极大的提高了在多个项目中的维护效率。

但是同时也有一个问题,就是如何管理这些扩展包,并且让 Go modules 能够快速的找到它!

设置 GOPRIVATE

go env -w GOPRIVATE=gitlab.example.com

GOPRIVATE 支持设置多个私有库,也支持使用通配符*

设置 GOPROXY

这个需要 Gitlab 开启 Go Proxy 功能,目前 Go Package Registry 生产环境不可用,所以不建议使用这种方式!!!

go env -w GOPROXY='https://gitlab.example.com/api/v4/projects/{id}/packages/go,https://proxy.golang.org,direct'

最好将私有扩展包的 URL 放在代理列表的最前面,另外就是如果有多个包,且网络允许的情况下,直接将 GOPROXY 的值设为 direct 即可。

配置身份认证

这一步主要是为了 Go modules 从 Gitlab 下载 Release 包时能够通过身份认证,因为私有项目是需要登录后才能访问的。

在当前系统用户目录下创建一个 .netrc 文件,用于存放内部 Gitlab 的 Personal Access Token。

echo "machine <url> login <username> password <token>" >> ~/.netrc
  • <url>: 替换为内部 Gitlab 的域名,e.g. gitlab.example.com;
  • <username>: Gitlab 的用户名, e.g. root@gitlab.example.com,在 CI/CD 中则可以使用 gitlab-ci-token 来替代实际的用户;
  • <token>: 你的 Gitlab Personal Access Token,在 CI/CD 中则可以使用 $CI_JOB_TOKEN 环境变量。

禁用扩展包校验

使用 Go 1.13 及更高版本下载依赖项时,获取的源将根据校验和数据库 sum.golang.org 进行验证。因为我们的扩展包是私有的,所以 sum.golang.org 无法对其进行校验。

go env -w GONOSUMDB="gitlab.example.com/services/modules"

这里可以具体到某个项目,也可以只到域名级别。如果设置了 GOPRIVATE 那么所有该域名下的包都不会再进行校验!

使用扩展包

现在就可以通过 go get 命令来安装私有扩展包了。

go get gitlab.example.com/services/modules/inquiry@latest

扩展包版本

对于我们需要再测试环境我们要使用某个分支的最新提交来说,我们可以使用如下命令:

go get gitlab.example.com/services/modules/inquiry@<branch>

# 例如我要使用最新的 develop 分支,执行后将包从原来的 v1.1.0 升级到了 v1.1.1-0.20231123032709-e22204ae03de
go get gitlab.test/modules/mail@develop
go: downloading gitlab.test/modules/mail v1.1.1-0.20231123032709-e22204ae03de
go: upgraded gitlab.test/modules/mail v1.1.0 => v1.1.1-0.20231123032709-e22204ae03de

上面的 v1.1.1-0.20231123032709-e22204ae03de 版本是处于开发阶段的自动伪版本号,由 Go Module 自动生成其语法如下:

baseVersionPrefix-timestamp-revisionIdentifier

  • baseVersionPrefix (v1.1.1-0): 基于当前包的最新 release 版本派生,例如我上面的例子中从 v1.1.0 自动派生为 v1.1.1-0,如果该包尚未发布任何 tag,则为 v0.0.0
  • timestamp (20231123032709): 这是 Commit 的 UTC 时间戳
  • revisionIdentifier (e22204ae03de): 是 Git commit hash 的前 12 字符,或者是 Subversion 零填充的修订号。

对于主版本变更,Go 官方给出的最佳实践是,同时变更包的导入路径,例如上面的 gitlab.test/modules/mail 这个包,升级到 v2,则需要修改包中的 go.mod 声明:

module "gitlab.test/modules/mail/v2"

如果不符合这种版本管理的最佳实践,该包在 go.modrequire 声明中会在版本后面加上 +incompatible

这样的设计虽然可以在同一个项目中引入同一个包的多个不兼容版本,但是对于管理源代码来说挺麻烦的,例如同时维护 v1 和 vX 等后续主版本的代码,在分之间切来切去 go.mod 各种冲突!

个人感觉这种方式并不能很好的解决版本问题,相反给开发者维护老版本的 PATCH 增加工作量!而且对于包的使用者来说,不仅要适配新版本语法,而且还要全局替换包的导入 PATH。

以上是我个人的理解,更多关于 Go module 版本可以参考 Go 官方文档:Module version numbering

总结

实现了私有扩展包的安装后,又有了新的问题,每次都需要将私有扩展包的改动 PUSH 到 Gitlab Repository 后,再手动在项目中执行 go get 才能更新项目中的依赖。

这样的流程太过于麻烦,所以我在想有没有一种类似 PHP Composer 的那种解决方案,按找环境区分依赖的来源。例如在开发环境可以将某个依赖的扩展包指向到本地的一个目录,在 CI/CD 构建时,则从 Gitlab 中获取扩展包。

终于找到解决方案了,在 Go 1.18 版本中新增了 Workspace 功能,能够很好的解决这个问题,具体的方案可以参考《Go 使用 Workspace 实现本地扩展包》

I hope this is helpful, Happy hacking…