go module 多版本依赖功能验证

我们这里要创建的 module,它的引用方法比较特殊又比较常见——通过别名引用。

module 初始创建

在目录 go_mod_example_pkg 中创建名为 lijiaocn.com/go/example/mod 的 module:

$ mkdir go_mod_example_pkg
$ cd go_mod_example_pkg
$ go mod init lijiaocn.com/go/example/mod
go: creating new go.mod: module lijiaocn.com/go/example/mod

只在 version.go 中实现一个打印版本号的函数,第一个版本是 v0.0.0:

package mod

func Version() {
    println("v0.0.0")
}

上传到 github introclass/go_mod_example_pkg

然后将 println("v0.0.0") 修改为 println("v1.0.0") ,发布 v1 版本:

$ git add .
$ git commit -m "release v1.0.0"
$ git push
$ git tag v1.0.0
$ git push origin v1.0.0

然后按照兼容 GOPATH 的方式,创建 v2 版本:

$ mkdir v2
$ cd v2
$ go mod init lijiaocn.com/go/example/mod/v2

在 v2 目录中重新实现 version.go:

package v2

func Version() {
    println("v2.0.0")
}

提交代码,打标签,发布 v2.0.0:

$ git add .
$ git commit -m "release v2.0.0"
$ git push
$ git tag v2.0.0
$ git push origin v2.0.0

最后为了验证不使用独立主版本目录的效果,直接修改 version.go,发布 v3.0.0 版本:

$ git add .
$ git commit -m "release v3.0.0"
$ git push
$ git tag v3.0.0
$ git push origin v3.0.0

module 别名设置

上面创建的 module 现在还不能通过 lijiaocn.com/go/example/mod 引用,需要进行额外设置。

引用 github 上的代码通常使用 "import github.com/xxx/xxx" 的形式,import 指令后面是 repo 路径。

但是你一定遇到这种情况,项目在 github 上,但是 import 使用的是另一个路径(别名)。 譬如上一节中的 rsc.io/quote 代码路径是 "github.com/rsc/quote",引用时用的是 "rsc.io/quote":

import (
    "rsc.io/quote"
    quoteV3 "rsc.io/quote/v3"
)

这是怎样做到的?

在浏览器中打开 https://rsc.io/quote 不会有任何收获,要用下面的方法才能发现端倪:

$ curl  https://rsc.io/quote/v3 |grep go-import
<meta name="go-import" content="rsc.io/quote git https://github.com/rsc/quote">

名为 go-import 的 meta 标签的 content 中包含 git 仓库地址,这是 go get 的能力之一:从 meta 中获取 repo 地址,详情见 Remote import paths

要通过 lijiaocn.com/go/example/mod 名称引用前面的 module,需要在站点 lijiaocn.com 中创建下面的文件:

$ mkdir -p go/example/mod
$ cat <<EOF > go/example/mod/index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="lijiaocn.com/go/example/mod git https://github.com/introclass/go_mod_example_pkg">
</head>
<body>
<p>codes at: <a href="https://github.com/introclass/go_mod_example_pkg">https://github.com/introclass/go_mod_example_pkg</a></p>
</body>
</html>
EOF

module 引用效果

前期工作准备完成,现在验证效果。

$ mkdir usemod
$ cd usemod 
$ go mod init usemod

代码如下:

package main

import "lijiaocn.com/go/example/mod"

func main() {
    mod.Version()
}

不指定版本号时:

$ go get lijiaocn.com/go/example/mod

执行结果为 v1.0.0:

$ go run main.go 
v1.0.0

怀疑不指定版本号时,默认使用 v1 主版本的最新版本,按上一节的步骤加一个 v1.1.0 版本后:

$ go get lijiaocn.com/go/example/mod
go: finding lijiaocn.com/go/example/mod v1.1.0

果然,目标 module 中的 v2.0.0 和 v3.0.0 被直接无视,使用的是 v1 的最新版本。

尝试引用 v3.0.0 版本,结果出错,必须要有 /v3 ( 第一个结论 ):

$ go get lijiaocn.com/go/example/[email protected]
go: finding lijiaocn.com/go/example/mod v3.0.0
go get lijiaocn.com/go/example/[email protected]: lijiaocn.com/go/example/[email protected]: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3

使用 v2 和 v3 后缀,需要为 v2 和 v3 准备一个 index.html( 同一个 repo 不需要修改 ):

$ mkdir go/example/mod/v3
$ cp go/example/mod/index.html go/example/mod/v2
$ cp go/example/mod/index.html go/example/mod/v3

同时引用 v1 和 v2:

package main

import "lijiaocn.com/go/example/mod"
import "lijiaocn.com/go/example/mod/v2"

func main() {
    mod.Version()
    v2.Version()
}

运行结果:

$ go run main.go 
v1.1.0
v2.0.0

最后测试下没有独立目录的 v3 是否能引用:

package main

import "lijiaocn.com/go/example/mod"
import "lijiaocn.com/go/example/mod/v2"
import "lijiaocn.com/go/example/mod/v3"

func main() {
    mod.Version()
    v2.Version()
    v3.Version()
}

结论是不可以,必须要有独立目录:

$ go run main.go 
go: finding lijiaocn.com/go/example/mod/v3 v3.0.0
build command-line-arguments: cannot load lijiaocn.com/go/example/mod/v3: 
    module lijiaocn.com/go/example/mod@latest (v1.1.0) found, 
    but does not contain package lijiaocn.com/go/example/mod/v3

上一节提出的问题答案:v1 之后的主版本都必须有独立的目录,repo 中代码冗余不可避免,至少现阶段 go 1.13 是这样的(2019-12-31 16:53:07)。

参考

  1. 李佶澳的博客