如何同步.NET的虚拟单体仓库,实现高效协作?
摘要:原文 | Přemek Vysoký 翻译 | 郑子铭 在之前的文章“重塑 .NET 构建和发布方式”中,Matt 介绍了我们近期对 .NET 构建和发布流程的全面改进。这项历时数年的工作,我们称之为“统一构建”,
原文 | Přemek Vysoký
翻译 | 郑子铭
在之前的文章“重塑 .NET 构建和发布方式”中,Matt 介绍了我们近期对 .NET 构建和发布流程的全面改进。这项历时数年的工作,我们称之为“统一构建”,其关键部分在于引入了虚拟单体仓库 (VMR),它聚合了构建 .NET SDK 所需的所有源代码和基础架构。本文将重点介绍单体仓库本身:它的创建过程以及维持其运行的双向同步的技术细节。
直到最近,.NET SDK 都是由数十个代码库的构建产物聚合而成的。这些产物沿着代码库树向下流动,最终拼接在一起生成最终的 .NET SDK。这种方法多年来一直行之有效,但也带来了显著的复杂性和维护开销。自.NET 10 Preview 4起,我们开始使用单体代码库的单个提交来构建 .NET SDK。
什么是虚拟单体存储库?
虚拟单体仓库 (VMR) 是一个单一的 Git 仓库,其中包含构建 .NET SDK 所需的所有源代码和基础架构。您可以在 GitHub 上的dotnet/dotnet目录下找到 VMR。实际上,它主要由数十个其他独立仓库(例如dotnet/runtime或dotnet/sdk )聚合而成,我们称这些仓库为“产品仓库”。此外,它还包含构建基础架构、管道定义和脚本等其他资源。
产品仓库仍然独立存在,并以子目录的形式与虚拟仓库 (VMR) 中的对应仓库同步。这就是虚拟仓库的由来。更改既可以在产品仓库中进行,也可以直接在虚拟仓库中进行。我们的基础架构通过创建拉取请求来保持这两者之间的同步,这些拉取请求会将源更改传递到这两部分。
通往VMR之路
拥有双向同步的单体仓库一直是统一构建项目的必要基石。然而,实现这一点并非一蹴而就,而在此过程中,我们必须持续发布产品。
第一阶段 – 源代码构建压缩包
这段旅程始于 .NET 6 时期,当时我们投入大量资源,致力于让 .NET 能够在各种 Linux 发行版(例如 Ubuntu、Fedora、Debian)以及 Homebrew 等软件包管理器中可用。为了实现这一目标,我们必须遵守这些发行版维护者的规则。这些规则通常可以归纳为:
所有内容均需提供源代码,不允许提供二进制文件。
网络访问受限或无网络访问
换句话说,我们必须能够向维护人员提供一组非二进制源文件,这些文件无需从互联网下载任何内容即可编译成 .NET SDK。我们将此过程称为源代码构建。
源代码构建方法与我们过去构建 .NET SDK 的方式有所不同。在 VMR 之前,我们构建 .NET SDK 的方式是,构建产物通过数十个代码库的依赖关系树逐步流出。两种方法都存在相同的需求——依赖关系流必须到达最终代码库(最初是 <repository> dotnet/installer,后来是 dotnet/sdk<repository>)。然后,您可以收集二进制文件或这些二进制文件背后的源代码,并将它们提供给最终构建。
Source Build 的第一个版本会遍历代码树中每个仓库的提交记录,添加 Source Build 的基础架构(即 Source Build 背后的逻辑),并动态生成一个 tarball 归档文件。然后,该归档文件会被提供给第三方维护者,他们在自己的系统上构建该文件,并将生成的软件包提交到各自的软件包仓库中。
源代码构建补丁
我们经常发现,收集到的源代码无法成功构建。构建方法各不相同,而且在产品依赖关系流程完成之前发现问题往往过于复杂且成本高昂。有时,这甚至会在产品发布前暴露出已存在的集成问题。一旦源代码构建失败,就需要在某个产品代码库中进行修复,然后再次向下传播到依赖关系树。这是一个繁琐、耗时、成本高昂且容易出错的过程。
为了缓解这个问题,我们允许将所谓的“源构建补丁”提交到最后一个代码库。这些包含修复的额外补丁会应用到已收集的源文件之上。然后,我们会将补丁合并到上游原始代码库(即已打补丁的源文件所在的代码库)。一旦修复后的源文件再次向下传递,就可以移除该补丁——因为此时已收集的源文件已经包含了这些更改,所以该补丁将无法再次应用到它们之上。
第二阶段 – VMR-lite
为了迈出实现完整 VMR 代码流程的第一步,我们需要放弃基于 tarball 的方法,转而使用专用的 Git 仓库。仓库内容与 tarball 相同,但迁移到 Git 意味着需要投入代码和变更管理方面的资源,而这些对于最终实现统一构建 VMR 至关重要。2022 年 10 月,我们创建了最初的dotnet/dotnet仓库。它的代号是“ VMR-lite ”,是产品仓库源代码的只读镜像(投影)。
每次我们将提交合并到 SDK 代码库时,都会触发一个单向同步管道。
