模块(Modules)第01部分:为什么和做什么模块(Modules)

系列索引

为什么和什么
项目,依赖项和Gopls
最小版本选择
镜像,校验和和雅典

介绍

自Go发行以来,有三个问题一直困扰着开发人员:

  • 可以在GOPATH工作区之外使用Go代码。
  • 能够对依赖进行版本控制并确定要使用的最兼容版本。
  • 可以使用Go工具原生环境管理依赖项。

模块为三个关键问题提供了集成解决方案。

随着Go 1.13的发布,这三个问题已经成为过去。在过去的两年中,Go团队花费了很多精力才能使所有人达成一致。在本文中,我将重点介绍从GOPATH到模块的过渡以及模块解决的问题。在此过程中,我会提供足够的语义,以便你可以更好地了解模块如何在更高的层次上工作。也许更重要的是,为什么他们以这种方式工作。

GOPATH

使用GOPATH指向磁盘上的静态位置来创建Go的工作区,并在此为Go的开发人员提供良好的服务。然而不幸的是对于非Go开发人员来说,这变成了一个瓶颈,他们可能不定期的运行Go的项目,但是他们并没有进行相关的Go工作区设置。对此Go团队想要解决一个问题,就是允许将Go的库(repo)克隆到磁盘上的任何位置(GOPATH外部),然后通过工具进行定位,构建和测试代码。

图1

模块(Modules)第01部分:为什么和做什么模块(Modules)第01部分:为什么和做什么
image

图1显示了conf包的GitHub存储库。这个库声明了一个包,支持处理应用程序中的配置。在使用模块之前,如果你想使用这个包,那么你需要使用go get将其克隆到你GOPATH目录下,并且使用“规范的名称”表明这个包在磁盘上的确切位置。这个“规范的名称”是远程库的根目录和存储库名称的混合。

举一个没有模块之前的例子,如果你运行go get github.com/ardanlabs/conf,那么代码将会被克隆到本地磁盘上的$GOPATH/src/github.com/ardanlabs/conf。因为有了GOPAHT并知道了存储的规范名称,所以无论开发人员将工作区放到机器上的任何位置,Go的工具都可以找到代码。

解决imports

代码1
github.com/ardanlabs/conf/blob/master/conf_test.go

01 package conf_test
02
03 import (
...
10     "github.com/ardanlabs/conf"
...
12 )

代码1展示了来自conf仓库的测试文件conf_test.go的部分。当包中测试代码使用了_test的命名(就像01行看到的那样),这意味着测试代码与被测试代码位于不同的包中,并且测试代码必须像外部用户一样导入该包。你可以在第10行看见如何使用规范的名称导入conf包,借助GOPATH机制,可以在磁盘上完成导入,以及构建和测试代码。

如果GOPATH不存在了,并且文件结构不再与规范名称库匹配时,这些功能要如何工作?

代码2

import "github.com/ardanlabs/conf"

// GOPATH mode: Physical location on disk matches the GOPATH
// and Canonical name of the repo.
$GOPATH/src/github.com/ardanlabs/conf

// Module mode: Physical location on disk doesn’t represent
// the Canonical name of the repo.
/users/bill/conf

代码2展示了当你希望克隆conf到任意位置时的问题。当开发人员可以选择在任意位置克隆代码时,所有可以判断重复导入的信息消失了。

解决这个问题有一个方法,创建一个特殊问题,这里包含了这个类库的规范命名。
该文件在磁盘上的位置作为GOPATH的替代,并且在文件内定义了仓库的规范名称,这样无论包被克隆到何处,Go工具都可以解析导入。

这个特殊文件命名为go.mod
这个文件中定义了类库规范名称的新的实体,我们称之为模块(module)

代码3
github.com/ardanlabs/conf/blob/v1.1.0/go.mod

01 module github.com/ardanlabs/conf
02
...
06

代码3显示了conf仓库中go.mod文件的第一行。该行定义了模块的名称,为了项目中任意代码可以引用此库,这个名称代表了开发人员期望使用的规范的命名。现在,无论这个库克隆到什么位置,Go的工具都可以使用模块文件用来定位并解析内部导入。例如测试文件中的导入。

通过模块的概念,可以将代码克隆到磁盘上的任何位置,下一个要解决的问题是对代码进行捆绑和版本控制的支持。

//TODO =========

捆绑和版本控制

大多数VCS都可以在你的库中的任何提交点打上标签。这些标签通常用于发布新功能(v1.0.0,v2.3.8等),通常被视为不可变。

图2

模块(Modules)第01部分:为什么和做什么模块(Modules)第01部分:为什么和做什么
image

图2展示了conf包的作者已标记了三个不同版本。这些标记的版本遵循语义化的版本控制格式。

使用VCS工具,开发人员可以通过引用特定标签将conf包的任何特定版本克隆到本地磁盘。但是,有几个问题需要先进行解答:

  • 我应该使用哪个版本的软件包?
  • 我怎么知道哪个版本与我正在编写和使用的所有代码兼容?

回答完这两个问题后,我们需要面对第三个问题:

  • 我要克隆库到哪里,Go工具才可以找到和访问它?

然后情况变得糟糕。你不能在自己的项目中使用conf包的某个版本,除非你还克隆了conf依赖的包。这是项目传递依赖的问题。

在GOPATH模式下运行时,解决方案是用于go get识别所有依赖项的所有存储库并将其克隆到GOPATH工作空间中。但是,这不是一个完美的解决方案,因为go get它只知道如何为每个依赖项从master分支中克隆和更新最新代码。在编写初始代码时,从每个依赖项的master分支中提取代码可能很好。但是最终,在依赖关系独立发展了几个月(或几年)之后,依赖关系的最新master代码可能不再与你的项目兼容。这是因为你的项目不遵循版本标记,因此任何升级都可能包含重大更改。

在新的模块模式下运行时,我们不在需要go get将所有依赖项的仓库克隆到一个定义好的工作空间中。此外,还需要找到一种方式来引用对整个项目都有效的每个依赖项的兼容版本。然后,在导入同一包的依赖项需要依赖不同主要版本的情况下,支持在项目中使用同一依赖项的不同主要版本。

尽管社区开发的一些工具(例如dep,godep,glide等)针对这些问题提供了一些解决方案,但是Go需要一个集成的解决方案。解决方案是重用模块文件,以维护按版本列出的直接或间接的依赖关系列表。然后将任何给定的仓库版本视为一个不可变的代码绑定。这个版本化的不可变的捆绑包称为模块。

//TODO ==========

综合解决方案

图3

模块(Modules)第01部分:为什么和做什么模块(Modules)第01部分:为什么和做什么
image

图3显示了一个库和模块之间的关系。它显示了一个库如何导入模块给定版本中的包。这这个例子中,模块conf内的1.1.0版本可以从模块go-cmp0.3.1版本中导入cmp包。由于conf模块列出了依赖关系信息(通过模块文件),因此Go工具可以获取任何选定版本的模块,因此可以成功构建。

一旦有了模块,就会有很多工程上的妙用:

  • 你可以为全球Go开发者提供支持(除某些例外),以构建,保留,认证,验证,获取,缓存和重用模块。
  • 你可以搭建代理服务器来支持不同的VCS并提供上述支持。
  • 你可以验证模块(对于任何给定版本)是否包含已知存在于该模块中的完全相同的代码,而不管它被构建了多少次,从何处获取以及由谁获取。

最棒的消息是,关于模块所能提供的功能,Go团队已经在Go的1.13版本中完成了很多支持。

结论

这篇文章主要是奠定基础,以了解什么是模块以及Go团队如何最终获得此解决方案。还有很多话要说,例如:

  • 如何选择使用特定版本的模块?
  • 模块文件的结构如何,你必须通过哪些选项来控制模块的选择?
  • 如何在本地构建,获取和缓存模块以解决导入问题?
  • 模块如何验证通用的“语义化版本“?
  • 在自己的项目中应如何使用模块?最佳实践是什么?

在以后的文章中,我计划针对这些问题以及更多其他内容来阐述我的理解。现在,请确保您了解存储库,程序包和模块之间的关系。如有任何疑问,请随时在Slack上找到我。有一个很棒的渠道#modules,人们随时可以提供帮助。

模块文档

目前已经有了很多Go文档。以下是Go小组发布的一些帖子。

模块Wiki
1.13 Go发行说明
Go博客:模块镜像与数据库校验和
Go博客:发布Go模块
提案:保护公共Go模块生态系统
GopherCon 2019:Katie Hockman-Go模块代理:查询生命周期

本文来源于互联网:模块(Modules)第01部分:为什么和做什么模块(Modules)第01部分:为什么和做什么

       

One Pingback

留言

本站文章如未特殊注明,均为原创,转载请注明出处: 未必平凡  本文链接地址: https://vv2014.com/1515.html