本文是后端实践第二篇。总共第四篇笔记。
一、实践选题:
Go 语言入门指南:基础语法和常用特性解析
本文选择Go语言中常用的go module特性,介绍实践如何打包发布v2以上的go库。
二、问题介绍
直接git打tag就可以发布新版本,但在发布v2以上版本时遇到了一些问题,记录一下。
在本文中,
库(被调用方)路径均以github.com/onelib/mylib代替,
业务程序(调用方)路径均以github.com/caller/myservice代替)
还是按照老方法,直接git打tag,发布。结果在调用方就出现了问题。
更新go mod时直接报错:
require github.com/onelib/mylib: version "v2.x.x" invalid: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2
先说结论:
- 如果你是库作者,如何处理这个错误,请见本文后续的高版本号(>=v2)如何良好支持go module特性一节。
- 如果你是库用户,遇到这个错误,那么这属于库作者对go module特性支持不完善,请向其反馈。如果你着急使用,可以参见本文下面的临时解决方案一节。
三、解决方案
库用户如何调用【未良好支持go module特性的库】的临时解决方法
库用户临时凑合调用的方法:在go.mod,import时在版本号后面加+incompatible,如
module github.com/caller/myservice
import (
github.com/onelib/mylib v2.0.0+incompatible
)
(仅作为库未良好支持go module时,调用者可以采用的补丁方案)
但是这样做有缺点, 无法做到同时import多个版本。甚至在不同的文件里都不行,即使你一个文件或目录里只用了一个版本,整个项目也只能导入一份版本。 这样就不能让旧代码仍然使用旧版本,而必须整个项目一起升级到新版,所以有时会造成旧代码的破坏。 同时一些IDE可能由于不识别这种版本号标识而报错。
如果我们仅仅是调用者而非编写者,而实际编写者又没有支持go module时,在确定不会造成旧版本代码破坏后,我们就可以使用这种方案临时解决调用问题。
如果我们就是库的编写者,请继续看下节。
库作者发布高版本号(主版本号>=v2)如何良好支持go module特性
如果你是库作者,更完美的解决方案是:
将库的go.mod中的moulde名称后面增加版本号/vx
如
module github.com/onelib/mylib
->
module github.com/onelib/mylib/v2
注意,文件结构不需要做任何改变,不需要建立v2或者vn文件夹,增加的“/vx”仅仅用于require/import时区分主版本号。
调用时也仍以库文件中声明的package xxx为准来进行xxx.FuncX()调用,而与go.mod中module怎么写的无关。
而后重新在git中打tag v2.x.x,发布。调用方就可以愉快地使用了。
代码示例:
go.mod
module github.com/onelib/mylib/v2
import (
// ...
)
文件目录结构无需改变。
调用方使用方法见下节。
这就是module发布者根据go module特性处理版本问题的正确方法。 同时,库作者请参照下节,告知库用户在require/import时加上合适的主版本号后缀。
库用户如何调用【有良好支持go module特性的库】的方法
库作者在正确支持go module特性后,库用户(调用方)的注意事项:
- go.mod文件中require path需要同样在结尾加上/vx。
require github.com/onelib/mylib/v2 v2.x.x - 代码(.go文件)中的import path需要同样在结尾加上/vx。
import github.com/onelib/mylib/v2
这样,调用者可以在同一项目下import同一模块的多个不同主版本了。
但同一主版本、不同子版本仍然不能共存,这是因为由golang采用的语义化版本号(Semantic Versioning)可知,同一主版本应当是向下兼容,因此同一主版本的高子版本应当能兼容低子版本的功能。
例外:v0和v1似乎还是只能导入一个。有了v1就不能再用v0。 不过在语义化版本号中,v0代表试验的初期不稳定版本,有了v1就基本不会再有用v0的需求,问题不大。
代码示例:
go.mod
module github.com/caller/myservice
require (
github.com/onelib/mylib v1.0.0
github.com/onelib/mylib/v2 v2.1.3
github.com/onelib/mylib/v3 v3.2.1
)
在同一项目的不同的代码文件中使用同一库的不同主版本:
代码文件1.go
import "github.com/onelib/mylib"
func f1() {
mylib.FuncX()
}
代码文件2.go
import "github.com/onelib/mylib/v2"
func f2() {
mylib.FuncX()
}
代码文件3.go
import "github.com/onelib/mylib/v3"
func f3() {
mylib.FuncX()
}
在同一项目的同一代码文件中使用同一库的不同主版本: (这个需求有点少见,不过也能实现,如果各主版本包名冲突,import时指定别名即可) 代码文件x.go
package main
import (
myliba "github.com/onelib/mylib"
mylibb "github.com/onelib/mylib/v2"
mylibc "github.com/onelib/mylib/v3"
)
func main() {
myliba.FuncX()
mylibb.FuncX()
mylibc.FuncX()
}