▲点击上方“DotNet NB”关注公众号回复“1”获取开发者路线图学习分享丨作者 / 郑 子 铭这是DotNet NB 公众号的第249篇原创文章原文 | Matt Mitchell翻译 | 郑子铭落实愿景——交付统一构建版本统一构建项目大致可以分为 4 个阶段初步构思和设计.NET 7 ——统一构建项目的初步设计工作始于 2022 年初当时正值 .NET 7 开发阶段历时约 4 个月完成。该项目于 2022 年晚些时候获得全面批准启动目标是在 .NET 9 RTM 发布前完成。其中设置了一些关键的启动/停止节点在这些节点上即使放弃项目我们仍然可以在基础设施方面取得净收益。基础性工作.NET 8 ——在 .NET 8 期间统一构建项目专注于基础性工作旨在提升源代码构建基础架构的可持续性并构建支持完整构建所需的各项功能。这些投入旨在为 .NET 整体带来积极影响即便在概念验证阶段发现了一些重大未知问题导致我们不得不改变方向。垂直构建/代码流探索早期 .NET 9 ——在基础工作完成后我们开始为三大主流操作系统系列Mac、Windows 和 Linux分别实现垂直构建。我们的目标是尽可能多地识别产品化阶段需要解决的问题。我们尤其关注之前未知的任何产品构建连接点。与此同时我们对代码流和代码管理的各种方案进行了更深入的研究最终验证并确定了以下实现方案。产品化.NET 9 后期 - .NET 10 ——在经历了春夏两季的延期后.NET 9 的最终实现工作在 .NET 9 末期才正式启动。由于延期发布日期被推迟到了 .NET 10。但这最终却因祸得福。这为我们争取到了大约 6 个月的额外开发时间并使我们能够在 .NET 10 预览版/候选版周期预览版 4的中期开始使用统一构建产品流程。.NET 预览版 4 发布了新的构建流程但仍然沿用旧的代码流程。预览版 5 加入了新的代码流程此后我们再也没有回头。在接下来的几个月里我们进一步优化了开发人员的工作流程并为构建和代码流程的开发预留了更多的时间。经过近 4 年的梦想和努力Unified Build 终于随 .NET 10 RTM 一起发布了让我们来看看这个项目的关键组成部分。VMR——虚拟单体存储库dotnet /dotnet VMR即“虚拟单体仓库”是统一构建项目的基石。它是所有 .NET 组件包括我们的 Linux 发行版合作伙伴构建的源代码布局。它扮演着协调者的角色。从功能上看它与 .NET 8.0 之前的源代码布局并没有太大区别。只是将之前的源代码布局正式化为了一个 Git 仓库而不是源代码压缩包。这一点至关重要因为它允许开发人员既可以在各自的组件仓库中工作在这些仓库中开发工作流程可能非常精细也可以在需要进行跨领域更改时在 VMR 中工作。.NET 获得了分布式仓库的大部分优势同时避免了代码一致性问题。垂直建造垂直构建是 .NET 向一系列垂直组件vertical build构建资源的转型。垂直组件是指在单台机器上运行的单个构建命令用于构建 .NET 产品的一部分而无需其他垂直组件的输入。通常我们会根据要构建的运行时环境来划分垂直组件。例如Windows x64、MonoAOT、Linux arm64 和 PGO Profile Windows x86。总共有 35 到 40 个不同的垂直组件。我们将这些垂直组件分为“短栈”和“长栈”。短栈仅构建运行时环境而长栈则会构建整个 SDK。最初的设想是如果我们把所有并行垂直领域的输出整合起来就能拥有 .NET 所需的一切。这样的架构效率极高而且对上游合作伙伴也很友好。然而多年来.NET 产品的设计中已经包含了一些必要的整合。例如如果没有对跨多个操作系统构建的众多软件包的访问权限就无法构建 .NET 工作负载包。为了解决这个问题我们最终增加了两个构建步骤。好消息是这些额外的步骤只涉及部分垂直领域以及这些领域内的部分组件。虽然并不完美但尚可接受。代码流统一构建项目最有趣的方面或许在于代码流的管理方式。.NET 在这方面对标准开发模式进行了一些颠覆。正如前文所述将产品维护为相互依赖的组件图同时将代码流扁平化到一个共享的、连贯的布局中需要实现“双向”代码流。变更需要从组件流入共享布局而共享布局中的变更也需要能够回流到组件仓库。从概念上讲代码流算法并不比在给定项目的单个 Git 仓库中建模的任何算法更复杂。关键在于使用没有相关 Git 历史记录的仓库来实现这一点。注该算法的具体细节将在后续文章中由另一位团队成员详细介绍。届时我会更新本文并添加链接。现在我们先来看一些基本知识VMR 和组件仓库都会跟踪来自合作伙伴的最新代码流。eng/Version.Details.xml虽然可以想象将其保存在其他地方但此信息会与标准依赖项信息一起跟踪。dotnet/runtime 知道上次“回流”的 VMR SHA 即从 VMR 到 dotnet/runtime 的流。VMR 知道最后一个“正向流”的 dotnet/runtime SHA 即从 dotnet/runtime 到 VMR 的流。其目的是确定“上次流”与当前流之间的差异。例如在一个非常简单的例子中当向 dotnet/runtime 提交新代码而src/dotnet/runtimeVMR 中没有任何更改时依赖关系流系统将执行以下步骤确定两个点 A 和 B用于计算差异。在本例中点 A 是 dotnet/runtime 最后一次提交到 VMR或当前处于 PR 中的版本。点 B 是 dotnet/runtime 的最新提交。构建一个补丁文件将 src/runtime 文件重新映射到 VMR 的目录结构。请提交包含差异的 PR。请参阅正向流程示例和反向流程示例。.NET 8 和 .NET 9 使用仅支持单向代码流的虚拟主版本库 (VMR)。在另一端无需进行任何更改的情况下这些情况简单且稳定。但当开发人员开始在两端都进行更改并且依赖关系流随着时间推移而发生变化时情况就会变得复杂起来。计算差异点变得更有趣需要知道“最后一次流动”的方向。合并冲突时有发生需要以开发人员能够理解的方式进行处理。代码流的源端和目标端的变化可能会造成严重后果因此需要强大的错误处理和恢复机制。代码流程部分就先讲到这里。敬请期待更多内容。场景测试验证统一构建的最后一个主要支柱是额外的场景测试。需要明确的是.NET 并不缺乏测试。如果可行.NET 运行时可以在每个 PR 上花费数月的机器时间来验证其数百万个测试用例。我们的审批、构建、验证和签核流程确保了高质量的交付组件。然而当直接在 VMR 中进行更改时扁平化的流程会在更改和针对每个 VMR 组件进行深入验证之间引入新的延迟。虽然我们无法在 PR 和 CI 上运行所有测试但我们确实意识到更好的自动化场景测试可以在防止回归方面发挥重要作用。我们的目标是添加涵盖广泛产品功能的测试这些功能与构建系统或代码库基础架构没有直接关联。相反这些测试针对最终构建的产品执行。如果场景测试通过则可以很好地判断产品功能已达到良好水平贡献者也不会受到阻碍。结果那么.NET 经过近四年的梦想、筹划和辛勤努力最终获得了什么在一个项目上投入如此多的精力结果是否物有所值事实证明我们收获颇丰。让我们先从最显而易见的结果开始然后再深入了解一下背后的原因。灵活性、可预测性和速度迄今为止我们看到的投资回报最大的莫过于灵活性。分布式产品构建速度缓慢构建一致的版本也同样耗时。提交新的修复或内容需要协调以避免“重置构建”因为在分布式开源生态系统中最终交付的内容和构建方式是紧密相连的。提交新的修复可能意味着你没有准备好进行验证。扁平化流程消除了这种一致性问题将“做什么”和“怎么做”分离。这在推进RTM版本或服务版本发布的过程中至关重要。这意味着我们可以在发布周期的后期进行修复从而将更多精力放在这些修复是否符合服务标准上而不是能否实际构建和交付变更。这种灵活性对客户来说非常有利。这种灵活性部分源于构建速度的提升。这听起来可能非常慢.NET 是一个庞大而复杂的产品但 .NET 的目标是在 4 小时内生成未签名版本在 7 小时内生成签名版本。这比 .NET 8.0 和 .NET 9.0 中显著延长了构建时间。即使一切顺利8.0 或 9.0 的构建也很容易耗时 24 小时。7 小时内完成签名版本意味着每天需要验证大约 3 次新的 .NET 资源。构建时间的显著提升主要来自于简化构建流程减少不必要的开销。部分灵活性也源于可预测性。分布式产品构建涉及更多环节更多的人为干预点系统和流程出现故障的可能性也更大。这往往会导致结果难以预测。“如果我向 dotnet/runtime 提交一个修复什么时候才能生成可用的构建版本 ”在分布式系统中这是一个难以回答的问题。我知道 dotnet/runtime 的构建需要多长时间但这个更改何时才能通过依赖流向下游显现届时是否有人可以审核并批准它下游的 PR/CI 验证状态如何在我们获得一个完整的构建版本之前是否会有新的重要更改合并到 dotnet/aspnetcore 中从而导致验证工作延误在 .NET 10 中这个问题要容易得多。更改会流入 VMR或直接在 VMR 中进行并会在下一个构建版本中显现。下一个构建版本需要 N 小时。基础设施的稳健性和完整性在那些耀眼的指标背后是多年来对基础设施进行的质量提升这些提升日复一日地带来显著效益。.NET 8 中对源代码构建基础设施的改进降低了 Linux 发行版源代码构建的运行成本。之前的成本很大一部分来自于变更提交到最终通过依赖关系图到达共享源代码布局并发现其是否会破坏构建之间的延迟。在预览版中期之前源代码构建 .NET SDK 经常无法“预构建干净”也无法由发行版合作伙伴发布。.NET 8 的基础设施改进使得在 PR 阶段更容易识别新的预构建输入从而更容易诊断和解决这些问题避免它们进入源代码布局。现在我们 100% 实现了预构建干净。这减轻了源代码构建团队的负担使他们能够腾出精力在其他领域开展工作。他们增加了构建并行性、更可预测的依赖关系流、更好的日志记录、移除了不必要的复杂性……等等。让产品获得成功的投资。为了支持在所有平台上对各种类型的归档文件进行签名我们的签名工具必须进行全面改造。如果没有这项工作我们就无法发布统一构建版本。但这种扩展支持带来的益处远不止于核心 .NET 产品。许多辅助代码库也因此简化了构建流程避免了将文件从 Mac/Linux 传输到运行签名工具的 Windows 机器。更低的构建开销更快更简单的构建过程。未来方向那么统一构建项目下一步该如何发展呢虽然我们不会像之前那样大力投资 .NET 11但我们会针对基础设施进行有针对性的改进以提升开发者的工作流程和用户体验主要集中在代码流方面。我特别感兴趣的一个领域是 AI 代理它可以监控代码流将产品创建过程中涉及的各个系统连接起来并识别问题。从 PR 到产品发布变更的交付涉及众多系统和参与方Azure DevOps、GitHub、代码流服务及其配置、代码镜像、开发者审批、机器分配等等。如果一切顺利就万事大吉。但如果出现问题通常需要人工追踪才能准确找出出错的环节。这既繁琐又耗时。我们虽然有工具但主要还是需要将众多环节连接起来。我们可以为此编写一个规则引擎但我感觉它会很脆弱而且非常复杂。能够以更模糊的方式审视系统的代理才是这类任务的理想选择。更少的辛劳更好的 .NET。最后在 .NET 11 之后或许还会有另一波消除连接点的浪潮。这样做的好处显而易见更简单、更快速也更便于贡献者使用。我们现在已经确切地知道如果消除剩余的连接点构建速度会有多快不到 4 小时。结论如果你读到这里非常感谢很高兴能深入探讨 .NET 的构建和发布方式。你已经了解到分布式依赖流产品构建模型并非总是能够可靠且可预测地交付软件。这类系统往往具有较高的复杂性和开销从而增加开发时间。你也了解了 .NET 统一构建项目起源于 .NET Linux 发行版源代码构建以及将这些概念应用于 .NET 时遇到的困难。最后你还了解了 .NET 如何应用这些概念以及我们在日常工作中看到的显著改进。详细介绍扁平化代码流程算法的博文很快就会发布。敬请期待链接统一构建设计文档产品构建的滚动 CI/PR 构建原文链接Reinventing how .NET Builds and Ships (Again)推荐阅读【译】 再次革新 .NET 的构建和发布方式二【译】 再次革新 .NET 的构建和发布方式一【译】 数据摄取构建模块简介预览版(二)【译】 数据摄取构建模块简介预览版(一)【译】 如何使用 .NET MAUI 构建 iOS 小部件【译】 Microsoft.Testing.Platform 现已在 Azure DevOps 中得到全面支持点击下方卡片关注DotNet NB一起交流学习▲点击上方卡片关注DotNet NB一起交流学习请在公众号后台回复【路线图】获取.NET 2024开发者路线回复【原创内容】获取公众号原创内容回复【峰会视频】获取.NET Conf大会视频回复【个人简介】获取作者个人简介回复【年终总结】获取作者年终回顾回复【加群】加入DotNet NB 交流学习群长按识别下方二维码或点击阅读原文。和我一起交流学习分享心得。