版本化 Go 之旅

简介:   本文译自 A Tour of Versioned Go (vgo), Go & Versioning 的第 2 部分, 版权@归原文所有.  对我而言, 设计意味着构建, 拆除和再次构建, 一遍又一遍.  为了编写新的版本控制提案, 我构建了一个原型 vgo, 来处理许多细微的细节. 这篇博文展示了如何使用 vgo.  你现在可以通过运行 go get golang/x/vgo 下载并尝试 vgo. Vgo 是 go 命令的一个直接替换(和分支拷贝).  你运行 vgo 而不是 go, 它将使用你安装在 $GOROOT (Go 1.10 beta1 或更高版本) 的编译器和

  本文译自 A Tour of Versioned Go (vgo), Go & Versioning 的第 2 部分, 版权@归原文所有.

  对我而言, 设计意味着构建, 拆除和再次构建, 一遍又一遍.

  为了编写新的版本控制提案, 我构建了一个原型 vgo, 来处理许多细微的细节. 这篇博文展示了如何使用 vgo.

  你现在可以通过运行 go get golang/x/vgo 下载并尝试 vgo. Vgo 是 go 命令的一个直接替换(和分支拷贝).

  你运行 vgo 而不是 go, 它将使用你安装在 $GOROOT (Go 1.10 beta1 或更高版本) 的编译器和标准库.

  随着我们更多地了解什么可行, 什么不可行, vgo 的语义和命令行细节可能会发生变化.

  但是, 我们打算避免 go.mod 文件格式的向后不兼容的更改, 以便今天添加了 go.mod 的项目以后也可以工作. 在我们完善提案时, 我们也会相应地更新 vgo.

  该部分演示怎么使用 vgo. 请按照步骤进行实验.

  从安装 vgo 开始:

  $ go get -u golang/x/vgo

  你一定会遇到有趣的 bug, 因为 vgo 现在最多只有轻微的测试. 请使用 Go 问题跟踪 进行 bug 上报, 标题以 "x/vgo" 开头. 多谢.

  我们来写一个有趣的 "Hello, world" 程序. 在 GOPATH/src 目录之外创建一个目录并切换到它:

  $ cd $HOME

  $ mkdir hello

  $ cd hello

  然后创建一个 hello.go:

  package main // import "github/you/hello"

  import (

  "fmt"

  "rsc/quote"

  )

  func main() {

  fmt.Println(quote.Hello())

  }

  或者下载它:

  $ curl -sS swtch/hello.go >hello.go

  创建一个空的 go.mod 文件来标记此模块的根目录, 然后构建并运行新程序:

  $ echo >go.mod

  $ vgo build

  vgo: resolving import "rsc/quote"

  vgo: finding rsc/quote (latest)

  vgo: adding rsc/quote v1.5.2

  vgo: finding rsc/quote v1.5.2

  vgo: finding rsc/sampler v1.3.0

  vgo: finding golang/x/text v0.0.0-20170915032832-14c0d48ead0c

  vgo: downloading rsc/quote v1.5.2

  vgo: downloading rsc/sampler v1.3.0

  vgo: downloading golang/x/text v0.0.0-20170915032832-14c0d48ead0c

  $ ./hello

  Hello, world.

  $

  注意这里没有显式的需要运行 vgo get. 普通的 vgo build 将在遇到未知导入时查找包含它的模块, 并将该模块的最新版本作为依赖添加到当前模块中.

  运行任何 vgo 命令的一个副作用是必要时会更新 go.mod. 这种情况下, vgo build 会写入新的 go.mod 文件:

  $ cat go.mod

  module "github/you/hello"

  require "rsc/quote" v1.5.2

  $

  由于 go.mod 已写入, 下一次 vgo build 将不会再次解析导入或打印那么多:

  $ vgo build

  $ ./hello

  Hello, world.

  $

  即使明天发布了 rsc/quote v1.5.3 或 v1.6.0, 该目录中的构建仍将继续使用 v1.5.2, 除非进行明确的升级(见下文).

  go.mod 文件列举了依赖的最小集合, 忽略了已列举中所隐含的.

  在这种情况下, rsc/quote v1.5.2 依赖特定版本的 rsc/sampler 和 golang/x/text, 所以在 go.mod 中重复列举它们是冗余的.

  使用 vgo list -m 仍然可以找到构建所需的全套模块:

  $ vgo list -m

  MODULE VERSION

  github/you/hello -

  golang/x/text v0.0.0-20170915032832-14c0d48ead0c

  rsc/quote v1.5.2

  rsc/sampler v1.3.0

  $

  此时你可能想知道为什么我们简单的 "hello world" 程序会使用 golang/x/text.

  实际上 rsc/quote 依赖 rsc/sampler, 后者又依赖 golang/x/text 进行 language matching .

  $ ./hello

  Bonjour le monde.

  $

  我们已经看到, 当必须将新模块添加到构建以解决新的导入时, vgo 会采用最新的模块.

  此前, 它需要 rsc/quote, 并发现 v1.5.2 是最新的. 但除了解析新的导入, vgo 仅使用 go.mod 文件中列出的版本.

  在我们的例子中, rsc/quote 间接依赖于 golang/x/text 和 rsc/sampler 的特定版本.

  事实证明, 这两个软件包都有较新的版本, 正如我们通过 vgo list -u (检查更新的软件包)看到的那样:

  $ vgo list -m -u

  MODULE VERSION LATEST

  github/you/hello - -

  golang/x/text v0.0.0-20170915032832-14c0d48ead0c v0.0.0-20180208041248-4e4a3210bb54

  rsc/quote v1.5.2 (2018-02-14 10:44) -

  rsc/sampler v1.3.0 (2018-02-13 14:05) v1.99.99 (2018-02-13 17:20)

  $

  这两个软件包都有更新的版本, 所以我们可能想在我们的 hello 程序中升级它们.

  首先升级 golang/x/text:

  $ vgo get golang/x/text

  vgo: finding golang/x/text v0.0.0-20180208041248-4e4a3210bb54

  vgo: downloading golang/x/text v0.0.0-20180208041248-4e4a3210bb54

  $ cat go.mod

  module "github/you/hello"

  require (

  "golang/x/text" v0.0.0-20180208041248-4e4a3210bb54

  "rsc/quote" v1.5.2

  )

  $

  vgo get 命令将查找给定模块的最新版本, 并通过更新 go.mod 来将该版本添加为当前模块的依赖.

  从现在开始, 未来的构建将使用较新的 text 模块:

  $ vgo list -m

  MODULE VERSION

  github/you/hello -

  golang/x/text v0.0.0-20180208041248-4e4a3210bb54

  rsc/quote v1.5.2

  rsc/sampler v1.3.0

  $

  当然, 升级之后, 测试一切仍然工作良好是个好主意.

  我们的依赖 rsc/quote 和 rsc/sampler 尚未使用较新的 text 模块进行测试. 我们可以在我们创建的配置中运行他们的测试:

  $ vgo test all

  ? github/you/hello [no test files]

  ? golang/x/text/internal/gen [no test files]

  ok golang/x/text/internal/tag 0.020s

  ? golang/x/text/internal/testtext [no test files]

  ok golang/x/text/internal/ucd 0.020s

  ok golang/x/text/language 0.068s

  ok golang/x/text/unicode/cldr 0.063s

  ok rsc/quote 0.015s

  ok rsc/sampler 0.016s

  $

  在原版 go 命令中, 软件包模式 all 意味着 GOPATH 中能找到的所有软件包. 这几乎总是太多而无用.

  在 vgo 中, 我们已经将 all 的含义缩小为 "当前模块中的所有软件包, 以及它们以递归方式导入的软件包". rsc/quote 模块的 1.5.2 版本包含一个 buggy 包:

  $ vgo test rsc/quote/...

  ok rsc/quote (cached)

  --- FAIL: Test (0.00s)

  buggy_test.go:10: buggy!

  FAIL

  FAIL rsc/quote/buggy 0.014s

  (exit status 1)

  $

  然而, 除非我们模块中的某个包导入 buggy, 否则它是不相干的, 所以它不包含在 all 里面. 无论如何, 升级的 x/text 看起来可以工作. 此时我们多半可以提交 go.mod.

  另一种选择是使用 vgo get -u 升级构建所需的所有模块:

  $ vgo get -u

  vgo: finding golang/x/text latest

  vgo: finding rsc/quote latest

  vgo: finding rsc/sampler latest

  vgo: finding rsc/sampler v1.99.99

  vgo: finding golang/x/text latest

  vgo: downloading rsc/sampler v1.99.99

  $ cat go.mod

  module "github/you/hello"

  require (

  "golang/x/text" v0.0.0-20180208041248-4e4a3210bb54

  "rsc/quote" v1.5.2

  "rsc/sampler" v1.99.99

  )

  $

  在这里, vgo get -u 保留了升级后的 text 模块, 并将 rsc/sampler 升级到其最新版本 v1.99.99.

  让我们来运行测试:

  $ vgo test all

  ? github/you/hello [no test files]

  ? golang/x/text/internal/gen [no test files]

  ok golang/x/text/internal/tag (cached)

  ? golang/x/text/internal/testtext [no test files]

  ok golang/x/text/internal/ucd (cached)

  ok golang/x/text/language 0.070s

  ok golang/x/text/unicode/cldr (cached)

  --- FAIL: TestHello (0.00s)

  quote_test.go:19: Hello()="99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."

  FAIL

  FAIL rsc/quote 0.014s

  --- FAIL: TestHello (0.00s)

  hello_test.go:31: Hello([en-US fr])="99 bottles of beer on the wall, 99 bottles of beer, ...", want "Hello, world."

  hello_test.go:31: Hello([fr en-US])="99 bottles of beer on the wall, 99 bottles of beer, ...", want "Bonjour le monde."

  FAIL

  FAIL rsc/sampler 0.014s

  (exit status 1)

  $

  看起来 rsc/sampler v1.99.99 出了问题. 果然:

  $ vgo build

  $ ./hello

  99 bottles of beer on the wall, 99 bottles of beer, ...

  $

  vgo get -u 获取每个依赖的最新版本的行为正和 go get 下载所有不在 GOPATH 的包所做的一样. 在一个 GOPATH 里空无一物的系统上:

  $ go get -d rsc/hello

  $ go build -o badhello rsc/hello

  $ ./badhello

  99 bottles of beer on the wall, 99 bottles of beer, ...

  $

  重要的区别是, 默认情况下, vgo 不会以这种方式运行. 你也可以通过降级撤消它.

  要降级软件包, 请使用 vgo list -t 显示可用的标记(tag)版本:

  $ vgo list -t rsc/sampler

  rsc/sampler

  v1.0.0

  v1.2.0

  v1.2.1

  v1.3.0

  v1.3.1

  v1.99.99

  $

  然后使用 vgo 获取要求的特定版本, 例如 v1.3.1:

  $ cat go.mod

  module "github/you/hello"

  require (

  "golang/x/text" v0.0.0-20180208041248-4e4a3210bb54

  "rsc/quote" v1.5.2

  "rsc/sampler" v1.99.99

  )

  $ vgo get rsc/[email protected]

  vgo: finding rsc/sampler v1.3.1

  vgo: downloading rsc/sampler v1.3.1

  $ vgo list -m

  MODULE VERSION

  github/you/hello -

  golang/x/text v0.0.0-20180208041248-4e4a3210bb54

  rsc/quote v1.5.2

  rsc/sampler v1.3.1

  $ cat go.mod

  module "github/you/hello"

  require (

  "golang/x/text" v0.0.0-20180208041248-4e4a3210bb54

  "rsc/quote" v1.5.2

  "rsc/sampler" v1.3.1

  )

  $ vgo test all

  ? github/you/hello [no test files]

  ? golang/x/text/internal/gen [no test files]

  ok golang/x/text/internal/tag (cached)

  ? golang/x/text/internal/testtext [no test files]

  ok golang/x/text/internal/ucd (cached)

  ok golang/x/text/language (cached)

  ok golang/x/text/unicode/cldr (cached)

  ok rsc/quote 0.016s

  ok rsc/sampler 0.015s

  $

  降级一个软件包可能需要降级其他软件包. 例如:

  $ vgo get rsc/[email protected]

  vgo: finding rsc/sampler v1.2.0

  vgo: finding rsc/quote v1.5.1

  vgo: finding rsc/quote v1.5.0

  vgo: finding rsc/quote v1.4.0

  vgo: finding rsc/sampler v1.0.0

  vgo: downloading rsc/sampler v1.2.0

  $ vgo list -m

  MODULE VERSION

  github/you/hello -

  golang/x/text v0.0.0-20180208041248-4e4a3210bb54

  rsc/quote v1.4.0

  rsc/sampler v1.2.0

  $ cat go.mod

  module "github/you/hello"

  require (

  "golang/x/text" v0.0.0-20180208041248-4e4a3210bb54

  "rsc/quote" v1.4.0

  "rsc/sampler" v1.2.0

  )

  $

  在这种情况下, rsc/quote v1.5.0 是第一个需要 rsc/sampler v1.3.0 的版本; 早期专科证书版本只需要 v1.0.0(或更高版本).

  降级选择了 rsc/quote v1.4.0, 这是与 v1.2.0 兼容的最新版本.

  也可以通过指定 none 作为版本来完全删除一个依赖, 这是一种极端的降级形式:

  $ vgo get rsc/[email protected]

  vgo: downloading rsc/quote v1.4.0

  vgo: finding rsc/quote v1.3.0

  $ vgo list -m

  MODULE VERSION

  github/you/hello -

  golang/x/text v0.0.0-20180208041248-4e4a3210bb54

  rsc/quote v1.3.0

  $ cat go.mod

  module "github/you/hello"

  require (

  "golang/x/text" v0.0.0-20180208041248-4e4a3210bb54

  "rsc/quote" v1.3.0

  )

  $ vgo test all

  vgo: downloading rsc/quote v1.3.0

  ? github/you/hello [no test files]

  ok rsc/quote 0.014s

  $

  让我们回到一切都是最新版本的状态, 包括 rsc/sampler v1.99.99:

  $ vgo get -u

  vgo: finding golang/x/text latest

  vgo: finding rsc/quote latest

  vgo: finding rsc/sampler latest

  vgo: finding golang/x/text latest

  $ vgo list -m

  MODULE VERSION

  github/you/hello -

  golang/x/text v0.0.0-20180208041248-4e4a3210bb54

  rsc/quote v1.5.2

  rsc/sampler v1.99.99

  $

  在确定 v1.99.99 并不适用于我们的 hello world 程序后, 我们可能想记录下这个事实, 以避免将来出现问题.

  我们可以通过向 go.mod 添加 exclude 指令来做到这一点:

  exclude "rsc/sampler" v1.99.99

  之后的操作表现的好像该模块不存在一样:

  $ echo 'exclude "rsc/sampler" v1.99.99' >>go.mod

  $ vgo list -t rsc/sampler

  rsc/sampler

  v1.0.0

  v1.2.0

  v1.2.1

  v1.3.0

  v1.3.1

  v1.99.99 # excluded

  $ vgo get -u

  vgo: finding golang/x/text latest

  vgo: finding rsc/quote latest

  vgo: finding rsc/sampler latest

  vgo: finding rsc/sampler latest

  vgo: finding golang/x/text latest

  $ vgo list -m

  MODULE VERSION

  github/you/hello -

  golang/x/text v0.0.0-20180208041248-4e4a3210bb54

  rsc/quote v1.5.2

  rsc/sampler v1.3.1

  $ cat go.mod

  module "github/you/hello"

  require (

  "golang/x/text" v0.0.0-20180208041248-4e4a3210bb54

  "rsc/quote" v1.5.2

  "rsc/sampler" v1.3.1

  )

  exclude "rsc/sampler" v1.99.99

  $ vgo test all

  ? github/you/hello [no test files]

  ? golang/x/text/internal/gen [no test files]

  ok golang/x/text/internal/tag (cached)

  ? golang/x/text/internal/testtext [no test files]

  ok golang/x/text/internal/ucd (cached)

  ok golang/x/text/language (cached)

  ok golang/x/text/unicode/cldr (cached)

  ok rsc/quote (cached)

  ok rsc/sampler (cached)

  $

  排除仅适用于当前模块的构建. 如果当前模块被更大的构建所依赖, 则排除不适用.

  例如, rsc/quote 的 go.mod 中的排除不适用于我们的 "hello, world" 构建.

  这一策略的权衡让当前模块的作者几乎可以任意控制自己的构建, 而不会受到它们依赖的模块几乎任意控制的影响.

  此时, 正确的下一步是联系 rsc/sampler 的作者并在 v1.99.99 中报告问题, 因此它可以在 v1.99.100 中修复. 不幸的是, 作者有一个博文依赖它而不予修复.

  如果确实在依赖中发现了问题, 则需要一种方法将其暂时替换为一个合适的副本. 假设我们想改变一些关于 rsc/quote 的行为.

  也许我们想要解决 rsc/sampler 中的问题, 或者我们想要做其他的事情. 第一步是使用通常的 git 命令检出 quote 模块:

  $ git clone github/rsc/quote ../quote

  Cloning into '../quote'...

  然后编辑 ../quote/quote.go 来改变 func Hello 的一些内容. 例如, 我把它的返回值从 sampler.Hello() 更改为 sampler.Glass(), 这是一个更有趣的问候语.

  $ cd ../quote

  $

  $

  改变了克隆代码之后, 我们可以通过向 go.mod 添加 replace 指令来让我们的构建使用它来代替真正的构建:

  replace "rsc/quote" v1.5.2=> "../quote"

  然后我们可以使用它来构建我们的程序:

  $ cd ../hello

  $ echo 'replace "rsc/quote" v1.5.2=> "../quote"' >>go.mod

  $ vgo list -m

  MODULE VERSION

  github/you/hello -

  golang/x/text v0.0.0-20180208041248-4e4a3210bb54

  rsc/quote v1.5.2

  => ../quote

  rsc/sampler v1.3.1

  $ vgo build

  $ ./hello

  I can eat glass and it doesn't hurt me.

  $

  你也可以将一个不同的模块命名为替换模块. 例如, 你可以克隆 github/rsc/quote, 然后将更改推送到你自己的分支.

  $ cd ../quote

  $ git commit -a -m 'my fork'

  [master 6151719] my fork

  1 file changed, 1 insertion(+), 1 deletion(-)

  $ git tag v0.0.0-myfork

  $ git push github/you/quote v0.0.0-myfork

  To github/you/quote

  * [new tag] v0.0.0-myfork -> v0.0.0-myfork

  $

  然后你可以使用它作为替换:

  $ cd ../hello

  $ echo 'replace "rsc/quote" v1.5.2=> "github/you/quote" v0.0.0-myfork' >>go.mod

  $ vgo list -m

  vgo: finding github/you/quote v0.0.0-myfork

  MODULE VERSION

  github/you/hello -

  golang/x/text v0.0.0-20180208041248-4e4a3210bb54

  rsc/quote v1.5.2

  => github/you/quote v0.0.0-myfork

  rsc/sampler v1.3.1

  $ vgo build

  vgo: downloading github/you/quote v0.0.0-myfork

  $ ./hello

  Je peux manger du verre, ?a ne me fait pas mal.

  $

  即使你想为你的项目使用 vgo, 你也不可能要求你的所有的用户都有 vgo.

  相反, 你可以创建一个 vendor 目录, 以允许 go 命令用户生成几乎相同的构建(当然, 在 GOPATH 中编译):

  $ vgo vendor

  $ mkdir -p $GOPATH/src/github/you

  $ cp -a . $GOPATH/src/github/you/hello

  $ go build -o vhello github/you/hello

  $ ./vhello

  Puedo comer vidrio, no me hace da?o.

  $

  我说这些构建 "几乎相同", 因为工具链看到的并在最终二进制文件中记录的导入路径是不同的. vendored 版本参见 vendor 目录:

  $ go tool nm hello | grep sampler.hello

  1170908 B rsc/sampler.hello

  $ go tool nm vhello | grep sampler.hello

  11718e8 B github/you/hello/vendor/rsc/sampler.hello

  $

  除了这种差异, 构建应该产生相同的二进制文件. 为了提供优雅的转换, 基于 vgo 的构建完全忽略 vendor 目录, 一如既往的模块感知 go 命令构建.

  请尝试 vgo. 在存储库中开始标记(tagging)版本. 创建并检入(check in) go.mod 文件. 在 golang/issue 上上报问题, 并在标题开头添加 "x/vgo:" 明天会有更多的博文. 谢谢, 玩得开心!

目录
相关文章
|
Dubbo 应用服务中间件 API
Go语言微服务框架重磅升级:dubbo-go v3.2.0 -alpha 版本预览
随着 Dubbo3 在云原生微服务方向的快速发展,Dubbo 的 go 语言实现迎来了 Dubbo3 版本以来最全面、最大幅度的一次升级,这次升级是全方位的,涉及 API、协议、流量管控、可观测能力等。
|
6月前
|
Go 开发者
GVM:Go语言版本和包管理的神器!
GVM,Go版本管理器,简化了在单机上切换不同Go版本的任务。
130 0
|
存储 Go API
怎么发布 Go Modules v1 版本?
怎么发布 Go Modules v1 版本?
62 0
|
缓存 JSON Go
Go 语言各个版本支持 Go Modules 的演进史
Go 语言各个版本支持 Go Modules 的演进史
97 1
|
Shell Go 网络安全
openssl 证书生成笔记(go 1.15版本以上)
openssl 证书生成笔记(go 1.15版本以上)
|
存储 Go API
Go Modules 如何创建和发布 v2 及更高版本?
Go Modules 如何创建和发布 v2 及更高版本?
144 0
|
资源调度 Kubernetes Go
SchedulerX支持Go版本SDK
Go语言越来越流行,SchedulerX是阿里云的分布式任务调度服务,新增支持Go版本SDK
119 0
|
Shell Go 网络安全
openssl 证书生成学习笔记(go 1.15版本以上) | 周末学习
golang 1.15+版本上,用 gRPC通过TLS实现数据传输加密时,会报错证书的问题
191 0
openssl 证书生成学习笔记(go 1.15版本以上) | 周末学习
|
缓存 Java 编译器
Go语言10年版本演进(2012.03~2022.03)
Go语言10年版本演进(2012.03~2022.03)
72 0
Go语言10年版本演进(2012.03~2022.03)
|
XML 存储 边缘计算
Excelize 发布 2.7.1 版本,Go 语言 Excel 文档基础库
Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,2023年4月10日,社区正式发布了 2.7.1 版本,该版本包含了多项新增功能、错误修复和兼容性提升优化。
151 3
Excelize 发布 2.7.1 版本,Go 语言 Excel 文档基础库