迁移到GoModule(Migrating to Go Modules译文)

Posted -_-void

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了迁移到GoModule(Migrating to Go Modules译文)相关的知识,希望对你有一定的参考价值。

迁移到GoModule

英文原版:https://blog.golang.org/migrating-to-go-modules

介绍

这是系列文章的第二部分

Go项目使用多种依赖管理策略,像dep和glide这种vendor模式的工具很流行,但是他们的行为有很大差异,而且有时并不能同时正常工作。有些项目将整个GOPATH存到git仓库,也有的只依靠go get将最新依赖安装到GOPATH。

在Go1.11引入的module模式,提供了官方的依赖管理解决方案并内置到go命令中。本文介绍将项目迁移到module模式的工具与技术。

请注意:如果你的项目已经打上了v2.0.0或者更高的版本,则需要在添加go.mod文件时将导包路径升级。我们将在后续文章(GoModule: v2及新版本)中解释如何在不破坏你的用户使用的情况下完成工作。

将项目迁移到GoModule

一个项目在准备迁移到GoModule时通常处于如下三种情况:

  • 全新的Go项目
  • 已存在的Go项目且使用了非module的依赖管理
  • 已存在的Go项目且未使用任何依赖管理

第一种情况已经在前文(使用GoModule)中介绍,因此本文主要介绍后两种情况。

已使用依赖管理

将一个已经使用了依赖管理工具的项目迁移需要执行如下命令:

$ git clone https://github.com/my/project
[...]
$ cd project
$ cat Godeps/Godeps.json

    "ImportPath": "github.com/my/project",
    "GoVersion": "go1.12",
    "GodepVersion": "v80",
    "Deps": [
        
            "ImportPath": "rsc.io/binaryregexp",
            "Comment": "v0.2.0-1-g545cabd",
            "Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
        ,
        
            "ImportPath": "rsc.io/binaryregexp/syntax",
            "Comment": "v0.2.0-1-g545cabd",
            "Rev": "545cabda89ca36b48b8e681a30d9d769a30b3074"
        
    ]

$ go mod init github.com/my/project
go: creating new go.mod: module github.com/my/project
go: copying requirements from Godeps/Godeps.json
$ cat go.mod
module github.com/my/project

go 1.12

require rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$

go mod init新建go.mod文件,并自动从Godeps.json``Gopkg.lock或者其他所支持的格式中导入依赖。go mod init命令的参数是module的导包路径。

已支持的格式:

"GLOCKFILE":          ParseGLOCKFILE,
"Godeps/Godeps.json": ParseGodepsJSON,
"Gopkg.lock":         ParseGopkgLock,
"dependencies.tsv":   ParseDependenciesTSV,
"glide.lock":         ParseGlideLock,
"vendor.conf":        ParseVendorConf,
"vendor.yml":         ParseVendorYML,
"vendor/manifest":    ParseVendorManifest,
"vendor/vendor.json": ParseVendorJSON,

在下一步之前最好先执行以下go buildgo test,因为后续步骤将会修改go.mod文件内容,因此现在的go.mod最接近之前的依赖管理工具所指定的依赖声明。

$ go mod tidy
go: downloading rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
go: extracting rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$ cat go.sum
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca h1:FKXXXJ6G2bFoVe7hX3kEX6Izxw5ZKRH57DFBJmHCbkU=
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
$

go mod tidy命令会find所有当前module依赖或传递依赖的package,它会添加被已知的module依赖的package,同时删除未被任何module使用的package。如果module提供的package仅仅被当前module所依赖的module依赖,则会被标记上// indirect。在每次commitgo.mod到版本控制之前最好都执行一下go mod tidy

然后来确保构建与测试通过:

$ go build ./...
$ go test ./...
[...]
$

注意其他依赖管理可能会在单个package或整个代码库(非module)级别指定依赖,并且通常并未意识到依赖项的go.mod中指定的要求。因此可能无法获得与以前每个软件包完全相同的版本,而且升级过去的修改可能会有风险。所以在上面的命令之后最好检查一下依赖:

$ go list -m all
go: finding rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
github.com/my/project
rsc.io/binaryregexp v0.2.1-0.20190524193500-545cabda89ca
$

然后跟之前的依赖管理文件对比一下所列出的依赖是否合适。如果发现哪个版本有问题,可以执行go mod why -mgo mod graph来查看原因,然后可以用go get升级或降级到正确版本。(如果需要的版本早于所选版本,go get会根据需要降级其他依赖项以保持兼容性。)例如:

$ go mod why -m rsc.io/binaryregexp
[...]
$ go mod graph | grep rsc.io/binaryregexp
[...]
$ go get rsc.io/binaryregexp@v0.2.0
$

未使用依赖管理

对于没有使用依赖管理的go项目,从新建go.mod文件开始:

$ git clone https://go.googlesource.com/blog
[...]
$ cd blog
$ go mod init golang.org/x/blog
go: creating new go.mod: module golang.org/x/blog
$ cat go.mod
module golang.org/x/blog

go 1.12
$

在没有依赖管理配置文件的情况下,go mod init新建一个只包含modulegogo.mod。在本例中将module的路径设置为golang.org/x/blog,因为这是他的导包路径。用户可以用这个路径来import,同时必须小心注意不要修改它。

module声明module路径,go声明用于编译此module的go版本。

接下来运行go mod tidy来添加依赖:

$ go mod tidy
go: finding golang.org/x/website latest
go: finding gopkg.in/tomb.v2 latest
go: finding golang.org/x/net latest
go: finding golang.org/x/tools latest
go: downloading github.com/gorilla/context v1.1.1
go: downloading golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
go: downloading golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
go: extracting github.com/gorilla/context v1.1.1
go: extracting golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
go: downloading gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
go: extracting gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
go: extracting golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
go: downloading golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
go: extracting golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
$ cat go.mod
module golang.org/x/blog

go 1.12

require (
    github.com/gorilla/context v1.1.1
    golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
    golang.org/x/text v0.3.2
    golang.org/x/tools v0.0.0-20190813214729-9dba7caff850
    golang.org/x/website v0.0.0-20190809153340-86a7442ada7c
    gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
)
$ cat go.sum
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
[...]
$

go mod tidy按module中import添加所需module,并生成go.sum保存每个依赖在指定版本下的checksums。接下来确保正常工作:

$ go build ./...
$ go test ./...
ok  	golang.org/x/blog	0.335s
?   	golang.org/x/blog/content/appengine	[no test files]
ok  	golang.org/x/blog/content/cover	0.040s
?   	golang.org/x/blog/content/h2push/server	[no test files]
?   	golang.org/x/blog/content/survey2016	[no test files]
?   	golang.org/x/blog/content/survey2017	[no test files]
?   	golang.org/x/blog/support/racy	[no test files]
$

注意,当go mod tidy添加依赖是,他会添加该module的最新版本。如果你的GOPATH存在一个低版本并且它做过重大更改,那么在执行go mod tidy或者go build``go test之后可能会报错。如果发生了这种情况,尝试使用go get来降级到旧版本,或者找时间使你的module兼容到依赖的最新版本。

在module模式下测试

迁移到GoModule之后有些测试需要调整。

如果测试需要在package目录下写文件,那么当这个包处于只读的module缓存时可能会失败,进而导致go test all失败。测试应将需要写入的文件复制到一个临时目录中。

如果测试依赖相对路径(../package-in-another-module)来查找和读取另一个包中的文件,如果这个包在另一个module中,那么操作会失败。因为该包可能存在于一个版本化的子目录中或者是在replace指令中指定的路径。这种情况下,应该需要把测试输入复制到你的module,或者将测试输入从资源文件编进go文件。

如果测试期望测试中的go命令运行在GOPATH模式将会fail。这种情况下需要在被测的文件树中添加一个go.mod或者设置环境变量GO111MODULE=off

release

最终你需要给module打上标签并release。如果你之前没有发不过去任何版本,那么这个是可选的。但是如果没有官方release,下游的用户就不能通过pseudo-versions来指定依赖版本,那样更难支持。

$ git tag v1.2.0
$ git push origin v1.2.0

go.mod定义了规范的导包路径,并添加了最低版本要求。如果你的用户已经在用正确的导包路径,并且你的依赖没有大的变更,那么添加的go.mod是向后兼容的,但是他是一个重大变更,且有可能暴露现有问题。如果已经存在了版本tag,那应该升级minor版本号。发布GoModule这篇文章会告诉你如果升级并发布版本。

导入与规范module路径

每个module都在go.mod中声明了module路径。module中每个引用module内部报的import声明都必须将module路径作为包路径的前缀。但是,go命令可能会通过许多不同的remote import paths遇到包含module的库。例如,golang.org/x/lintgithub.com/golang/lint都会解析到go.googlesource.com/lint这个代码库。go.mod包含的这个库声明其路径为golang.org/x/lint,所以只有这个路径对应到正确的module。

Go1.4提供了一个通过使用// import comments声明规范的导包路径的机制,但是package的作者并不总是提供它。结果导致在module之前编写的代码可能已经为module使用了不规范的导包路径,而没有出现不匹配的错误。使用module时,导包路径必须与规范的module路径匹配,因此开发者可能需要更新import语句:例如,需要将github.com/golang/lint改为golang.org/x/lint

另一中不规范的情况可能发生在使用了major版本为2或更高的module,它的导包路径可能与其代码库的路径不同。major版本大于1的module必须在其module路径中加上major版本号作为后缀:例如v2.0.0版本的module必须有v2后缀。然而import语句可能已经引用了module中没有该后缀的package,例如非module模式的用户使用v2.0.1版本的github.com/russross/blackfriday/v2可能被写成了github.com/russross/blackfriday,需要更新为带v2的情况。

结论

对于大多数用户而言,转换到GoModule应该是一个简单的过程。偶然出现的问题可能是由于不规范的导包路径或者依赖库中有破坏性更改。后续文章将会介绍发布v2及更高的新版本,以及异常情况调试。

以上是关于迁移到GoModule(Migrating to Go Modules译文)的主要内容,如果未能解决你的问题,请参考以下文章

Migrating Oracle 11g R2 To Oracle 19c

Vue: Migrating to vue3?

如何从 webpack v1 迁移到 webpack v2

将应用程序从websphere迁移到tomcat

SVN 到带有提交历史的 GIT 迁移(使用 git svn)

jira迁移方法