前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Go modules基础精进,六大核心概念全解析(下)

Go modules基础精进,六大核心概念全解析(下)

原创
作者头像
Bain
修改于 2021-12-06 09:36:17
修改于 2021-12-06 09:36:17
72200
代码可运行
举报
文章被收录于专栏:Go ModulesGo Modules
运行总次数:0
代码可运行

在上篇中,我们介绍了模块路径、版本号与兼容性原则、伪版本号三大概念,而在下篇我们将会继续介绍Go Modules核心概念。

四:主版本号后缀

从主版本号 2 开始,模块路径中必须添加一个像 /v2 这样的一个和主版本号匹配的后缀。举个例子如果一个模块在版本 v1.0.0 是的路径为 example.com/test,那么它在 v2.0.0 时的路径将是 example.com/test/v2。

主版本号后缀遵循导入兼容规则:

如果一个新代码包和老代码包拥有同样的导入路径,那么新包必须保证对老代码包的向后兼容。

  • 根据定义,模块的新主版本中的包与先前主版本中的相应包不向后兼容。 因此,从 v2 开始,包需要新的导入路径。 这是通过向模块路径添加主版本后缀来实现的。 由于模块路径是模块内每个包的导入路径的前缀,因此将主版本后缀添加到模块路径可为每个不兼容的版本提供不同的导入路径。
  • 主版本 v0 或 v1 不允许使用主版本后缀。 v0 和 v1 之间的模块路径不需要更改,因为 v0 版本为不稳定,没有兼容性保证。 此外,对于大多数模块,v1 向后兼容最新的 v0 版本, v1 版本才开始作为对兼容性的承诺。
  • 这里有一个特例,以 gopkg.in/ 开头的模块路径必须始终具有主版本后缀,即使是 v0 和 v1 版本。 后缀必须以点而不是斜线开头(例如,gopkg.in/yaml.v2)。因为在 Go Modules 推出之前,gopkg.in 就沿用了这个规则,为了能让引入 gopkg.in 包的代码能继续导入编译, Go 做了一些兼容性工作。
  • 主版本后缀可以让一个模块的多个主版本共存于同一个构建中。 这可以很好的解决钻石依赖性问题(diamond dependency conflict) https://jlbp.dev/what-is-a-diamond-dependency-conflict。 通常,如果传递依赖项在两个不同版本中需要一个模块,则将使用更高的版本。 但是,如果两个版本不兼容,则任何一个版本都不会满足所有的调用者。 由于不兼容的版本必须具有不同的主版本号,因此主版本后缀具有不同的模块路径,这样就不存在冲突了:具有不同后缀的模块被视为单独的模块,并且它们的包的导入路径也是不同的。
  • 因为很多 Go 项目在迁移到 Go 模块之前就发布了 v2 或更高版本的版本,所以没有使用主要版本后缀。对于这些版本,Go 使用 +incompatible 构建标记来进行注释(例如,v2.0.0+incompatible)。

五:解析包路径到模块路径的流程

通常在使用“go get”时可能是指定到一个包路径,而非模块路径,Go 是如何找到模块路径的呢?

go 命令会在主模块(当前模块)的 build list 中搜索有哪些模块路径匹配这个包路径的前缀。举个例子,如果导入的包路径是 example.com/a/b,发现 example.com/a 是一个模块路径,那么就会去检查 example.com/a 在 b 目录中是否包含这个包,在这个目录中要至少存在一个 go 源码文件才会被认为是一个有效的包。编译约束(Build Constraints)在这一过程中不会被应用。 如果确实在 build list 中找到了一个模块包含这个包,那么这个模块将被使用。如果没有发现模块能提供这个包或者发现两个及两个以上的模块提供了这个包,那么 go 命令会提示报错。但是你可以指定 -mod=mod 来使 go 命令尝试下载本地找不到的包,并且更新 go.mod 和 go.sum。go get 和 go mod tidy 这两个命令会自动的做这些工作。

当 go 命令试图下载一个新的代码包时,它回去检查 GOPROXY 环境变量,这是一个使用逗号分隔的 URL 列表,当然也支持像 direct 和 off 这样的关键字。代理 URL 代表 go 将使用 GOPROXY 协议拉取模块,direct 表示 go 需要和版本控制系统直接交互,off 不需要和外界做任何交互。另外,GOPRIVATE 和 GONOPROXY 环境变量也可以精细的控制 go 下载代码包的策略。

对于 GOPROXY 列表中的每一项, go 命令回去请求模块路径的每一个前缀。对于请求成功的模块,go 命令回去下载最新模块并且检查这个某块是否包含请求的包。如果多个模块包含了请求的包,拥有最长路径的将被选择。如果发现的模块中没有包含这个包,会报错。如果没有模块被发现,go 命令会尝试 GOPROXY 列表中的下一个配置项,如果最终都尝试过没有发现则会报错。举个例子,假设用户想要去获取 golang.org/x/net/html 这个包,之前配置的 GOPROXY 为 https://corp.example.com,https://goproxy.io。go 命令会遵循下面的请求顺序:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
向 https://corp.example.com/ 发起请求 (并行):
Request for latest version of golang.org/x/net/html
Request for latest version of golang.org/x/net
Request for latest version of golang.org/x
Request for latest version of golang.org

如果 https://corp.example.com/ 上面都失败了返回 410 或者 404 状态码,向 https://proxy.golang.org/ 发起请求:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Request for latest version of golang.org/x/net/html
Request for latest version of golang.org/x/net
Request for latest version of golang.org/x
Request for latest version of golang.org

当一个需要的模块被发现后,go 命令会将这个依赖模块的路径和对应版本添加到主模块的 go.mod 文件中。这样就确保了以后在编译该模块时,同样的模块版本将被使用,保证了编译的可重复性。如果解析的代码包没有被主模块直接引用,在 go.mod 文件中添加的新依赖后会有 // indirect 注释。

六:go.mod 文件

就像前面提到过的,模块的定义是由一个 UTF-8 编码的名为 go.mod 文本文件定义的。 这个文件是按照“行”进行组织的(line-oriented)。每一行都有一个独立的指令,有一个预留关键字和一些参数组成。比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
module example.com/my/thing
go 1.17
require example.com/other/thing v1.0.2
require example.com/new/thing/v2 v2.3.4
exclude example.com/old/thing v1.2.3
replace example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5
retract [v1.9.0, v1.9.5]

开头的关键词可以以行的形式被归总为块,就像日常所用的 imports 一样,所以可以改成下面这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
require (
 example.com/new/thing/v2 v2.3.4
 example.com/old/thing v1.2.3
)

go.mod 文件的设计兼顾了开发者的可读性和机器的易写性。go 命令也提供了几个子命令来帮组开发者修改 go.mod 文件。举个例子,go get 命令可以在需要的时候更新 go.mod 文件。go mod edit 命令可以对文件做一些底层的修改操作。如果我们也有类似的需求,可以使用 golang.org/x/mod/modfile 包以编程方式进行同样的更改。通过这个包,也可以一窥底层 go.mod 的 struct 结构:

代码语言:go
AI代码解释
复制
// go.mod 文件的组成形式
type File struct {
 Module *Module  // 模块路径
 Go *Go  // Go 版本
 Require []*Require // 依赖模块
 Exclude []*Exclude // 排除模块
 Replace []*Replace // 替换模块
 Retract []*Retract // 撤回模块
}
// A Module is the module statement.
type Module struct {
 Mod module.Version
 Deprecated string
}
// A Go is the go statement.
type Go struct {
 Version string // "1.23"
}
// An Exclude is a single exclude statement.
type Exclude struct {
 Mod module.Version
}
// A Replace is a single replace statement.
type Replace struct {
 Old module.Version
 New module.Version
}
// A Retract is a single retract statement.
type Retract struct {
 VersionInterval
 Rationale string
}

从上面的 Module 的 struct 中可以看到 “Deprecated”这一结构,在 Go Modules 推出的早期是没有这个设计的,那么这个字段是做什么用的呢? 估计很多人都不知道,如果我们维护的一个模块主版本从 v1 演进到了 v2,而不再维护 v1 版本了,希望用户尽可能使用 v2,通过上面的介绍知道v1 和 v2 是不同的 import path,“Retract”也无能为力,这时候这个 “Deprecated”就起作用了,看下面的例子:

代码语言:go
AI代码解释
复制
// Deprecated: in example.com/a/b@v1.9.0, the latest supported version is example.com/a/b/v2.
module example.com/a/b
go 1.17

当用户再去获取 example.com/a/b 这个版本时,go 命令可以感知到这个版本已经不再维护了,会报告给用户:

代码语言:go
AI代码解释
复制
go get -d example.com/a/b@v1.9.0
go: warning: module example.com/deprecated/a is deprecated: in example.com/a/b@v1.9.0, the latest supported version is example.com/a/b/v2

用户就可以根据提示进行 v2 代码拉取了。

《Go modules基础精进,六大核心概念全解析》一文全面介绍了 Go Modules 中的模块、模块路径、包、包路径、如何通过包路径寻找模块路径,还介绍了版本号和伪版本号,最后简单介绍了 go.mod 文件,以及其中不为人知的“Deprecated”功能,了解这些概念、设计理念和兼容性原则,将对管理和维护自己的 Go 模块大有帮助。

以上这些概念都是平常使用 Go 语言会高频接触到的内容,理解版本号和伪版本号的区别和设计原则,可以帮助我们清楚按照 semver 的标准定义自己的 tag 是多么重要。同时,遵循Go Modules 定义的兼容性原则,上下游开发者在社区协同时将会变得更加友好和高效。接下来的系列文章将会开始具体来了解 Go Modules 中的设计细节,例如 go.mod 文件详解以及配套的 go mod 子命令等,敬请期待。另外,腾讯云 goproxy 企业版已经产品化,需要了解的同学可以点击这里

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Go Modules 详解
Go 1.11 和 Go 1.12 包含了初步的 Go Modules 支持,且计划在 2019 年 8 月发布的 Go 1.13 会在所有开发过程中默认使用 Go Modules。
码农编程进阶笔记
2021/07/20
8750
Go Modules 详解
Go modules基础精进,六大核心概念全解析(上)
上一篇文章中,笔者介绍了如何以经典的 hello world 为例创建一个 Go module 模块,需要说明的是一个模块中是可以包含多个包(package)的,它们是可以被一起发布、打包、版本化的。同时,Go Modules 也可以通过版本管理系统(github、gitlab)或者 goproxy 代理进行下载。在使用 Go Modules 之前,建议大家弄清楚息息相关的六大核心概念,以方便大家在后期的开发、使用过程中理解更加深入。
Bain
2021/12/06
8053
Go modules基础精进,六大核心概念全解析(上)
发布 Go Modules
请注意:这篇文章涵盖了 v1 及其之前的开发,如果您对 v2 感兴趣,请参阅 Go Modules: v2 and Beyond。
恋喵大鲤鱼
2020/09/16
7780
Go 模块--开始使用Go Modules
Go的1.11和1.12版本包括对模块--新的Go依赖管理系统的初步支持,使依赖版本信息变得明确且更易于管理。这篇博客文章介绍了开始使用模块所需的基本操作。
KevinYan
2020/01/14
2.6K0
Go Modules 终极入门
Go modules 是 Go 语言中正式官宣的项目依赖解决方案,Go modules(前身为vgo)于 Go1.11 正式发布,在 Go1.14 已经准备好,并且可以用在生产上(ready for production)了,Go 官方也鼓励所有用户从其他依赖项管理工具迁移到 Go modules。
madneal
2020/03/10
1.9K0
Go Modules 终极入门
GoLang 新版包管理 -- go module 的使用
上一篇文章中,我们介绍了 GoLang 中包的使用与包管理机制。 GoLang 包的使用与管理
用户3147702
2022/06/27
2.4K0
GoLang 新版包管理 -- go module 的使用
GOPATH 模式怎么迁移至 Modules 模式?
Go 项目使用多种依赖管理策略。诸如 dep 和 glide 很受欢迎,但是它们在使用上有很大差异,并且并不总是能很好地协同工作。某些项目将其整个 GOPATH 目录存储在单个 Git 存储库中。其他人只是依靠 go get 获取,并期望在 GOPATH 中安装相当新版本的依赖项。
frank.
2020/11/12
2.2K0
Go mod的简单实践
Go mod简介: Go mod是官方推荐的包管理方式,开始于go1.11,在go1.12版本基本稳定,go1.13之后开始默认开启。
灰子学技术
2020/05/20
10.5K0
迁移到 Go Modules
Go 项目使用多种依赖管理策略,其中对 vendor 包的管理有两个比较流行的工具 dep 和 glide,但他们在行为上有很大的差异,而且并不是总能很好地同时使用。一些项目将其整个 GOPATH 目录存储在一个 Git 仓库中。其他人则只依赖于 go get 并期望在GOPATH中安装较新版本的依赖项。
恋喵大鲤鱼
2020/09/08
8520
深入理解 Go Modules 的 go.mod 与 go.sum
流行的现代编程语言一般都提供依赖库管理工具,如 Java 的 Maven 、Python 的 PIP、Node.js 的 NPM 和 Rust 的 Cargo 等。Go 最为一门新生代语言,自然也有其自己的库管理方式。
恋喵大鲤鱼
2022/06/12
15K0
深入理解 Go Modules 的 go.mod 与 go.sum
Go的包管理工具(三):Go Modules
在前面的文章,我们先是介绍了Go 的几种包管理方式,然后具体介绍了一种包管理的工具:glide。随着 Go 1.11 的发布,官方的包管理工具 Go Modules 变得流行起来。在发布不久的 Go 1.12 版本中,增强了对 Go Modules 的支持。本文将会介绍如何在项目中安装和使用 Go Modules 。
aoho求索
2019/05/07
1.5K0
Golang中的包管理工具 - Go Modules
在Go1.5之前使用GOROOT和GOPATH这2个系统环境变量来决定包的位置,对于开发者主要使用GOPATH。GOPATH 解决了第三方源码依赖的问题,看一下我本机 $GOPATH/src 下的目录:
猿哥
2019/08/01
1.6K0
干货满满的 Go Modules 和 goproxy.cn
大家好,我是一只普通的煎鱼,周四晚上很有幸邀请到 goproxy.cn 的作者 @盛傲飞(@aofei) 到 Go 夜读给我们进行第 61 期 《Go Modules、Go Module Proxy 和 goproxy.cn》的技术分享。
madneal
2019/11/28
1.3K0
Go 模块:v2 及更高版本
随着一个成功项目的成熟和新需求的增加,过去的特性和设计决策可能不再有意义。开发人员可能希望通过删除不推荐的函数、重命名类型或将复杂的包拆分成可管理的部分来集成他们学到的经验教训。这类更改需要下游用户努力将代码迁移到新的 API,因此,如果不仔细考虑好处大于成本,就不应该进行这些更改。
恋喵大鲤鱼
2020/09/16
1.1K0
Go语言技巧 - 5.【初探Go Module】Go语言的版本管理
Go语言自从推出了go mod作为版本管理工具后,结束Go语言版本管理工具的纷争,实现了大一统。
junedayday
2021/08/05
9470
GO 依赖管理工具go Modules(官方推荐)
以前写过一篇关于go管理依赖包工具 dep的文章,当时认为dep将会成为官方依赖工具,现在看来是自己图样图斯内幕破了,正如官方一直提到dep是“official experiment”官方实验项目的那样,随着go modules 在go1.11版推出,go1.12版功能不断改进,再到go1.13版完善优化,正式扶正。预计dep将来也只能定格在“official experiment”了。
孤烟
2020/09/27
1.8K1
Golang 1.16 中 Module 有什么变化?
golang 1.16 默认开启 Modules,即使不存在 go.mod,Go 命令现在默认情况下也会在 module-aware(模块感知)模式下构建包。
frank.
2021/03/09
2K0
Go modules 简介
Golang的版本管理视频推荐:Go with Versions - GopherConSG 2018
ppxai
2020/09/23
5520
Go包管理工具
想想Java的Maven, Nodejs的NPM,还有我们赞颂一万遍也不过分的Python包管理,为什么生命总要浪费在这些事情上面呢? 陷入了深深的沉思~~
happy123.me
2019/12/30
7000
Go Modules知识点总结
起初Go语言在1.5之前没有依赖管理工具,若想引入依赖库,需要执行go get命令将代码拉取放入GOPATH/src目录下,作为GOPATH下的全局依赖,这也就意味着没有版本控制及隔离项目的包依赖;
Golang梦工厂
2023/02/26
8630
Go Modules知识点总结
相关推荐
Go Modules 详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验