Go 语言做开发时,路径是如何定义的?Go Mudules又为此带来了哪些改变?本文将会全面介绍Go Modules六大核心概念,包括了设计理念与兼容性原则等,掌握这些技术点对于管理和维护Go 模块有重要价值。

在上篇中,我们介绍了模块路径、版本号与兼容性原则、伪版本号三大概念,而在下篇我们将会继续介绍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 命令会遵循下面的请求顺序:

向 https://corp.example.com/ 发起请求 (并行):

Request for latest version of golang.org/x/net/htmlRequest for latest version of golang.org/x/netRequest for latest version of golang.org/xRequest for latest version of golang.org

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

Request for latest version of golang.org/x/net/htmlRequest for latest version of golang.org/x/netRequest for latest version of golang.org/xRequest for latest version of golang.org

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

go.mod 文件

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

module example.com/my/thinggo 1.17require example.com/other/thing v1.0.2require example.com/new/thing/v2 v2.3.4exclude example.com/old/thing v1.2.3replace example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5retract [v1.9.0, v1.9.5]

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

require (example.com/new/thing/v2 v2.3.4example.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.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.VersionDeprecated 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.VersionNew module.Version}// A Retract is a single retract statement.type Retract struct {VersionIntervalRationale string}

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

// Deprecated: in example.com/a/b@v1.9.0, the latest supported version is example.com/a/b/v2.module example.com/a/bgo 1.17

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

go get -d example.com/a/b@v1.9.0go: 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 子命令等,敬请期待。

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

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

    Go 语言做开发时,路径是如何定义的?Go Mudules又为此带来了哪些改变?本文将会全面介绍Go Modules六大核心概念,包括了设计理念与兼容性原则等,掌握这些技术点对于管理和维护Go 模块有 ...

  2. 很无聊但是又很重要的 计算机网络基础知识 ---“网络核心概念“

    目录 网络核心概念 传输方式 分组交换 存储转发传输 排队时延和分组丢失 电路交换 分组交换和电路交换的对比 分组交换网的时延.丢包和吞吐量 单播.广播.多播和任播 广播(Broadcast) 多播( ...

  3. 很无聊但是又很重要的 计算机网络基础知识 --- “HTTP 核心概念(二) ” 【面试必问】

    目录 HTTP 标头 通用标头 Cache-Control Connection 持久性连接 Date Pragma Trailer Transfer-Encoding Upgrade Via War ...

  4. Tableau实战系列Tableau基础概念全解析 (三)-维度和度量

    前言 连接到新数据源时,Tableau 会将该数据源中的每个字段分配为 "数据"窗格的维度或度量,具体情况视字段包含的数据类型而定.你使用这些字段来构建数据的视图. 以下是我为大家 ...

  5. Tableau实战系列Tableau基础概念全解析 (二)-万字长文解析数据类型及数据集

    前言 以下是我为大家准备的几个精品专栏,喜欢的小伙伴可自行订阅,你的支持就是我不断更新的动力哟! MATLAB-30天带你从入门到精通 MATLAB深入理解高级教程(附源码) tableau可视化数据 ...

  6. Tableau可视化分析实战系列Tableau基础概念全解析 (一)-数据结构及字段

    前言 什么是维度和度量?为何有一些字段维度和其他度量? 为何一些字段的背景颜色是蓝色,而另外一些字段的背景颜色是绿色? 添加筛选器会对我的可视化项产生怎样的影响? 以下是我为大家准备的几个精品专栏,喜 ...

  7. 机器学习核心概念、常用术语整理(建议收藏)

    [转]机器学习核心概念完全解析(建议收藏) 原文链接:https://mp.weixin.qq.com/s/wEpmF1gdvsIimnvXrxKdRw AI干货知识库 刚接触机器学习框架 Tenso ...

  8. 【Camera专题】Camera驱动源码全解析_下

    系列文章 1.手把手撸一份驱动 到 点亮 Camera 2.Camera dtsi 完全解析 3.Camera驱动源码全解析上 4.Camera驱动源码全解析下 上篇文章分析了C文件函数的实现,本文继 ...

  9. Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离

    vue入门–基础命令+axios+案例练习 vue入门–vue常用属性.生命周期.计算属性.过滤器.组件.虚拟DOM.数组的响应式方法.页面闪烁.ES6简单语法增强 vue入门–js高阶函数(箭头函数 ...

最新文章

  1. Spring Boot 实现定时任务的 4 种方式
  2. JavaScript 中 call、apply和bind的用法区别
  3. 将本地工程上传到github
  4. XP系统限制修改IP有新招
  5. 三本新书(包含新系列)隆重上市
  6. octave安装 缺java_Octave信号包安装
  7. 使用HTML5 IndexDB存储图像和文件
  8. 在单位用oracle备份到磁带的脚本(看不明白的地方交流)
  9. 央采数据库集采:甲骨文、微软、腾讯、阿里等 21 家中标
  10. 吴恩达神经网络和深度学习-学习笔记-32-卷积神经网络示例
  11. 图片裁剪上传插件——jquery.photoClip.js
  12. Who's in the Middle - poj 2388 (快速排序寻找中位数)
  13. 有限域:基本性质和特征
  14. 7z解压crc错误_7-Zip - 常见问题解答(FAQ)
  15. 抖音小店无货源,出现退货的情况怎么处理?千万别大意
  16. Could not resolve com.huawei.agconnect:agcp
  17. 如何给证件照换底色;如何调整证件照大小
  18. kafka零拷贝总结
  19. 单片机编程学习:自己编写的一个很简单的传感器控制电机
  20. 一个成功软件测试项目的经验(转载)

热门文章

  1. 使用JavaScript变量需要注意哪些语法细节?
  2. 怎样用c语言解一元一次方程,问一道算法题目(解一元一次方程的问题)
  3. mac下的intellij idea常用快捷键
  4. 表白这件事,比解 bug 要难多少?
  5. Linux下将Mysql和Apache加入到系统服务里的方法
  6. CSS3模拟IOS滑动开关
  7. 域名年龄-SEO搜索引擎优化
  8. ProtoBuf使用笔记
  9. 设计模式之Pimpl模式
  10. Python LEGB (Local, Enclosing, Global, Build in) 规则