GolangBlog ModuleSeries - Part 4 | Go Modules: v2 and Beyond

本文探讨了Go模块的大版本语义,如何创建和发布新大版本,以及如何维护模块的多个大版本。介绍了当项目成熟并添加新需求时,如何通过大版本更新整合经验教训,包括移除废弃函数、重命名类型或拆分复杂包。

1. 版权

按https://golang.org/doc/copyright.html, 原文内容使用 Creative Commons Attribution 3.0 License, 代码使用 BSD license.

使用原文之外的部分, 需注明出处: https://blog.youkuaiyun.com/big_cheng/article/details/107734684.

2. 原文

Go Modules: v2 and Beyond
Jean de Klerk and Tyler Bui-Palsulich
7 November 2019

Introduction

This post is part 4 in a series.

As a successful project matures and new requirements are added, past features and design decisions might stop making sense. Developers may want to integrate lessons they’ve learned by removing deprecated functions, renaming types, or splitting complicated packages into manageable pieces. These kinds of changes require effort by downstream users to migrate their code to the new API, so they should not be made without careful consideration that the benefits outweigh the costs.

For projects that are still experimental — at major version v0 — occasional breaking changes are expected by users. For projects which are declared stable — at major version v1 or higher — breaking changes must be done in a new major version. This post explores major version semantics, how to create and publish a new major version, and how to maintain multiple major versions of a module.

Major versions and module paths

Modules formalized an important principle in Go, the import compatibility rule:

If an old package and a new package have the same import path,
the new package must be backwards compatible with the old package.

By definition, a new major version of a package is not backwards compatible with the previous version. This means a new major version of a module must have a different module path than the previous version. Starting with v2, the major version must appear at the end of the module path (declared in the module statement in the go.mod file). For example, when the authors of the module github.com/googleapis/gax-go developed v2, they used the new module path github.com/googleapis/gax-go/v2. Users who wanted to use v2 had to change their package imports and module requirements to github.com/googleapis/gax-go/v2.

The need for major version suffixes is one of the ways Go modules differs from most other dependency management systems. Suffixes are needed to solve the diamond dependency problem. Before Go modules, gopkg.in allowed package maintainers to follow what we now refer to as the import compatibility rule. With gopkg.in, if you depend on a package that imports gopkg.in/yaml.v1 and another package that imports gopkg.in/yaml.v2, there is no conflict because the two yaml packages have different import paths — they use a version suffix, as with Go modules. Since gopkg.in shares the same version suffix methodology as Go modules, the Go command accepts the .v2 in gopkg.in/yaml.v2 as a valid major version suffix. This is a special case for compatibility with gopkg.in: modules hosted at other domains need a slash suffix like /v2.

Major version strategies

The recommended strategy is to develop v2+ modules in a directory named after the major version suffix.

github.com/googleapis/gax-go @ master branch
/go.mod    → module github.com/googleapis/gax-go
/v2/go.mod → module github.com/googleapis/gax-go/v2

This approach is compatible with tools that aren’t aware of modules: file paths within the repository match the paths expected by go get in GOPATH mode. This strategy also allows all major versions to be developed together in different directories.

Other strategies may keep major versions on separate branches. However, if v2+ source code is on the repository’s default branch (usually master), tools that are not version-aware — including the go command in GOPATH mode — may not distinguish between major versions.

The examples in this post will follow the major version subdirectory strategy, since it provides the most compatibility. We recommend that module authors follow this strategy as long as they have users developing in GOPATH mode.

Publishing v2 and beyond

This post uses github.com/googleapis/gax-go as an example:

$ pwd
/tmp/gax-go
$ ls
CODE_OF_CONDUCT.md  call_option.go  internal
CONTRIBUTING.md     gax.go          invoke.go
LICENSE             go.mod          tools.go
README.md           go.sum          RELEASING.md
header.go
$ cat go.mod
module github.com/googleapis/gax-go

go 1.9

require (
    github.com/golang/protobuf v1.3.1
    golang.org/x/exp v0.0.0-20190221220918-438050ddec5e
    golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3
    golang.org/x/tools v0.0.0-20190114222345-bf090417da8b
    google.golang.org/grpc v1.19.0
    honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099
)
$

To start development on v2 of github.com/googleapis/gax-go, we’ll create a new v2/ directory and copy our package into it.

$ mkdir v2
$ cp *.go v2/
building file list ... done
call_option.go
gax.go
header.go
invoke.go
tools.go

sent 10588 bytes  received 130 bytes  21436.00 bytes/sec
total size is 10208  speedup is 0.95
$

Now, let’s create a v2 go.mod file by copying the current go.mod file and adding a v2/ suffix to the module path:

$ cp go.mod v2/go.mod
$ go mod edit -module github.com/googleapis/gax-go/v2 v2/go.mod
$

Note that the v2 version is treated as a separate module from the v0 / v1 versions: both may coexist in the same build. So, if your v2+ module has multiple packages, you should update them to use the new /v2 import path: otherwise, your v2+ module will depend on your v0 / v1 module. For example, to update all github.com/my/project references to github.com/my/project/v2, you can use find and sed:

$ find . -type f \
    -name '*.go' \
    -exec sed -i -e 's,github.com/my/project,github.com/my/project/v2,g' {} \;
$

Now we have a v2 module, but we want to experiment and make changes before publishing a release. Until we release v2.0.0 (or any version without a pre-release suffix), we can develop and make breaking changes as we decide on the new API. If we want users to be able to experiment with the new API before we officially make it stable, we can publish a v2 pre-release version:

$ git tag v2.0.0-alpha.1
$ git push origin v2.0.0-alpha.1
$

Once we are happy with our v2 API and are sure we don’t need any other breaking changes, we can tag v2.0.0:

$ git tag v2.0.0
$ git push origin v2.0.0
$

At that point, there are now two major versions to maintain. Backwards compatible changes and bug fixes will lead to new minor and patch releases (for example, v1.1.0, v2.0.1, etc.).

Conclusion

Major version changes result in development and maintenance overhead and require investment from downstream users to migrate. The larger the project, the larger these overheads tend to be. A major version change should only come after identifying a compelling reason. Once a compelling reason has been identified for a breaking change, we recommend developing multiple major versions in the master branch because it is compatible with a wider variety of existing tools.

Breaking changes to a v1+ module should always happen in a new, vN+1 module. When a new module is released, it means additional work for the maintainers and for the users who need to migrate to the new package. Maintainers should therefore validate their APIs before making a stable release, and consider carefully whether breaking changes are really necessary beyond v1.

3. 笔记

大版本升级增加了用户的迁移负担, 必须慎重.
导入兼容原则: 如果导入路径(import path)不变, 那新包必须与旧包兼容. Go模块里新大版本须以"/v2, /v3, …" 结尾. 为兼容gopkg.in, 也支持".v1, .v2, …" 的后缀 (如gopkg.in/yaml.v2).
推荐在代码仓库的同一分支通过"v2, v3, …" 子目录同时容纳多个大版本, 而非每个大版本各建一个分支 - 与现有工具兼容性最好(尤其当用户使用GOPATH模式时).

### 挂载 `/dev/nvme0n1` 到 `/media/myssd` 的方法及错误处理 当尝试将设备 `/dev/nvme0n1` 挂载到 `/media/myssd` 时,出现如下错误提示: ``` mount: /media/myssd: wrong fs type, bad option, bad superblock on /dev/nvme0n1, missing codepage or helper program, or other error. ``` 此问题通常由文件系统类型未正确识别、超级块损坏、挂载选项错误或缺少必要的挂载辅助程序引起。以下为解决此类问题的完整方法。 --- ### 确认文件系统类型 在尝试挂载前,应确认设备上实际使用的文件系统类型。若未使用 `-t` 选项指定文件系统类型,系统可能无法自动识别。可通过以下命令查看设备的文件系统信息: ```bash blkid /dev/nvme0n1 ``` 该命令将输出设备的文件系统类型(如 `ext4`、`xfs`、`btrfs` 等)。若设备未格式化,或输出为空,则需先进行格式化操作: ```bash mkfs.ext4 /dev/nvme0n1 ``` 若已知文件系统类型为 `ext4`,可使用以下命令挂载: ```bash sudo mount -t ext4 /dev/nvme0n1 /media/myssd ``` 若文件系统为 `xfs`,则应使用: ```bash sudo mount -t xfs /dev/nvme0n1 /media/myssd ``` --- ### 检查文件系统完整性 若设备已格式化且文件系统类型正确,但仍然无法挂载,可能是文件系统损坏导致超级块错误。可使用 `fsck` 检查并修复文件系统: ```bash sudo fsck /dev/nvme0n1 ``` 对于 `ext4` 文件系统,也可使用专用工具进行修复: ```bash sudo e2fsck -f /dev/nvme0n1 ``` 执行此命令后,系统将尝试修复文件系统中的错误,包括超级块损坏等问题。 --- ### 确保挂载点存在并具有正确权限 挂载操作要求挂载点目录必须存在且具有合适的权限。若 `/media/myssd` 不存在,需先创建: ```bash sudo mkdir -p /media/myssd ``` 确保当前用户具有读写权限: ```bash sudo chown $USER:$USER /media/myssd ``` --- ### 检查系统是否缺少挂载辅助程序 某些文件系统(如 `xfs`、`btrfs`)需要额外的挂载工具支持。若系统缺少相关程序,将导致挂载失败。例如,对于 `xfs` 文件系统,需安装 `xfsprogs`: ```bash sudo apt install xfsprogs ``` 对于 `ext4` 文件系统,应确保已安装 `e2fsprogs` 包: ```bash sudo apt install e2fsprogs ``` --- ### 查看内核日志获取更多信息 若上述方法均无法解决问题,可通过 `dmesg` 查看内核日志以获取更详细的错误信息: ```bash dmesg | tail ``` 这将显示最近的内核消息,有助于诊断挂载失败的根本原因。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值