GoCenter的《回到未来之旅

更新:自2021年5月1日起,GoCenter中央存储库已被淘汰,所有功能已弃用。有关中心日落的更多信息,请阅读弃用博客文章
每个科幻迷都知道过去是无法改变的。试着去做,它会破坏导致现在的事件的平衡。这也是我们在制作GoCenter时学到的东西。而试图纠正一个早期去模块设计选择和提高确定性,我们无意中让Go社区的事情变得更加困难。所以,就像每个虚构的时间旅行者一样,我们不得不回溯我们的脚步来撤销改变。
我们在GoCenter的旅程中学到了一些艰难的教训。通过讲述这个故事,我们可以解释我们最初的推理,分享我们学到的东西,并为将来创建Go模块提出更好的实践建议。
背景
去模块在Go 1.11中引入,为Go生态系统添加包版本控制和依赖管理。使用Go模块,Go开发者可以在他们的模块中声明项目需求go.modfiles and specify which versions of which packages are part of their builds.
当开发人员开始在他们的项目中采用Go模块时,他们面临着几个依赖关系,这些依赖关系没有选择提供go.mod描述他们需求的文件。当Go遇到处于该状态的模块时,它会自动创建一个go.mod文件,对该模块没有要求。为了让Go模块实现可复制的构建,这种需求信息的缺乏必须得到解决。Go Modules通过添加瞬态依赖项未在go.mod树中用户模块的依赖项的文件go.mod与/ /间接发表评论。虽然这可以帮助模块实现可复制的构建,但它不能避免不同模块之间的不一致行为。换句话说,我们可以有不同的模块依赖于相同的直接依赖和不同的间接依赖,只是因为它们是在不同的时间点创建的。
今年年初,JFrog推出了GoCenter,公共中央Go模块存储库。在GoCenter中,Go模块是不可变的,总是可用的。Go社区可以利用GoCenter来解决他们的模块需求,并将他们的Go项目CI/CD管道提升到一个新的水平。
当我们开始将项目转换为GoCenter提供的Go模块时,我们也面临着同样的缺失。开发者所面临的Mod文件问题。虽然我们了解如何使用提供的Go模块工具来实现可复制的构建,但我们对模块之间可能存在的不一致行为并不满意,我们决定做些什么,并寻找一个解决方案,让GoCenter为所有包含Go的Go模块提供服务。描述他们需求的Mod文件。
对历史不满意
以确保go.mod需求匹配的模块源代码,Go Modules提供了整理衣柜命令。的需求列表中添加源代码所使用的任何缺失的依赖项go.mod当我们试图将一个项目转换为Go模块时,它可以用于获取需求列表。
有了该命令的支持,我们的第一个方法是实现所有模块的需求go.mod文件的目的是整理所有不包含go.mod文件。
这种方法的第一个问题是,当没有关于依赖关系的可用版本时(在我们的下一个方法中会有更多的版本),整理衣柜会指向需要语句转换为依赖项可用的最新版本。这可能会创建坏的Go模块,因为我们不知道它们是否彼此兼容。当我们考虑到指向依赖项的最新版本可以在模块之间创建不可能及时发生的关系时,就更容易理解了。换句话说,通过整理模块,我们可以让一个模块依赖于未来创建的另一个模块。下图说明:

当我们考虑循环引用时,这个问题会变得更糟。通过整理作为循环成员的模块,我们可以使模块完全不可解析,因为它将依赖于自己的新版本,并导致Go客户端恐慌。在下面的示例中,模块A@v1.0.0将永远无法根据生成的整齐关系进行解析,因为它需要A@v1.1.0作为临时依赖项。
在这一刻,我们意识到过去是很难弥补的。在整理模块时引入时间变量会给过程增加不必要的复杂性,我们认为这种方法是不可可行的。
试图修复过去
在Go模块建立之前,Go社区使用其他依赖管理工具来描述他们的项目需求。这些工具的例子如下部,滑翔,govendor.这些工具提供了描述符文件,在这些文件中,作者根据构建过程中需要的其他项目和版本来描述他们的意图。
为了促进和加速Go模块的采用,Go提供了mod init命令。此命令可以解析其他依赖项管理工具使用的依赖项描述符文件,并创建一个依赖项描述符文件go.mod文件中使用require语句来描述声明的依赖关系。
当我们意识到我们无法完全修复过去的问题时,我们决定至少应该实现作者在其他依赖管理工具中声明的意图,将项目转换为GoCenter提供服务的Go模块。如果没有提供go.mod文件,我们将运行mod init使用作者在该命令支持的另一个依赖描述符文件中描述的依赖项创建并填充它。这将修复过去的部分问题,我们有足够的信息来避免与之前的方法相关的问题。
这种方法引起的第一个问题与验证过程Go模块。模块介绍了Go。和文件,其中包含密码校验和go.mod文件和包含模块包的zip存档。这些校验和用于验证需求的未来下载,并检测内容中的意外更改。通过运行mod init(甚至整理衣柜,但我们当时没有意识到)的项目,并生成了GoCenter版本的go.mod文件,我们将引入校验和不匹配,这取决于用于解析依赖关系的源。
校验和不匹配是不可预期的,即使它背后有良好的意图。像这样的“预期”校验和不匹配场景可能会导致用户完全忽略验证过程,当真正的威胁出现时,这可能会导致大问题。
除此之外,有两个go.mod从VCS解析模块时,一个包含GoCenter提供的需求,另一个不包含Go提供的需求的文件版本,可以完全改变模块解析的依赖树。

这两个问题与Go模块镜像之间所需的互操作性,这使得用户很难在不同的公共Go模块存储库之间切换。因此,我们决定恢复这个特性。
纠正错误
当我们意识到改变过去的任何事情都可能导致严重的不良后果时,我们决定专注于提供一种与用户所知道的行为同步的体验。
要实现这一点,对于没有go.modGoCenter将为您提供一个没有任何要求的文件。这与Go客户端在从VCS解析模块时提供的行为相同。对于已经有go.mod文件,GoCenter将按原样提供服务。这种方法消除了我们试图修复过去的所有痛苦,同时提高了GoCenter和其他公共服务之间的互操作性Go Modules注册表.
在处理和验证一个模块时,GoCenter仍然使用go mod命令整理衣柜而且取模图发现一个模块完全可解析所需的所有需求,但我们不使用这些命令的结果进行更改go.modGoCenter提供的文件。
因为我们必须在GoCenter上线后做出这个改变,所以我们需要清理一些东西go.mod用GoCenter生成的“无需求”版本替换它们。你可以得到更多关于它的信息在这里.
面向未来
我们从试图修复过去中学到了一些教训:
- 由于我们无法穿越回过去,就很难重现事情发生的背景;
- 正因为如此,很难避免遇到产生不可预测结果的矛盾(比如将来创建的带有依赖项的模块);
- 这使得预测一个变化的所有后果变得非常困难。
为了避免将来再次经历这一切,我们建议开源go项目的作者使用这些实践,以改善社区发现和使用go依赖项的方式。
采用Go模块
从Go 1.13开始,Go Modules将成为默认的依赖管理工具,之前的GOPATH方法将被弃用。项目作者应该从其他工具转向Go模块。如果你的一些依赖还没有转换为Go模块,请作者采用它。提供机会。有正确的需求列表的Mod文件是我们在任何环境或场景中实现可复制的构建的唯一方法,我们的模块正在被使用。
Go的作者和社区的其他成员,比如GoCenter,已经围绕Go模块计划了一些其他的特性。你将无法从中受益,除非你跳进Go模块的火车。
避免使用伪版本
提交哈希是VCS的概念,而不是依赖管理的概念。它们带来了混乱,并且使得随着时间的推移更难跟踪版本的进展。提交哈希伪版本的引入是为了让Go模块支持无标记的项目,它们只应该用作一种备用机制。如果需要的依赖项有发布标记,请在require语句中使用这些标记。如果没有,请作者开始标记他们的版本。这就引出了下一项。
标签发布遵循语义版本规则
虽然你仍然可以在Go模块中使用提交哈希伪版本,但你应该始终创建语义上有效的标记为了你的释放。除了语义版本控制提供的模块兼容性好处之外,标记你的版本还可以让用户和GoCenter这样的服务更容易检测模块可用的新版本。这可以减少社区了解您的更改和修复所需的时间。
避免使用替换语句
在你采用Go模块的过程中,你可能会倾向于使用replace语句,以避免不得不修复遍布源代码的import语句,特别是如果你不再提供依赖项的话。虽然这是一种有效且受支持的技术,替换语句是一个只用于主模块的指令,对用户将你的模块作为依赖项使用没有任何好处,这会破坏你的模块。
这可能是一个无聊而痛苦的过程,但是修复这些导入并删除replace语句是使您的模块可以作为依赖项供所有用户使用的唯一方法,而且不需要任何其他步骤。
结束
通过我们GoCenter旅程中的这些简单的见解,我们希望我们可以阐明过去的步骤,并使整个围棋社区在前进的道路上少很多坎坷。
