Carbon Language 提案 p001344 解析:如何把 LLVM 移出仓库并完成破坏性历史清理

Carbon Language 提案 p001344 解析:如何把 LLVM 移出仓库并完成破坏性历史清理 Carbon Language 提案 p001344 解析如何把 LLVM 移出仓库并完成破坏性历史清理【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang本文基于 Carbon Language 仓库中的提案文档 p001344-remove-llvm-from-the-repository-and-clean-up-history.md 展开完整还原该提案的问题背景、具体方案、迁移操作步骤与备选方案权衡并结合当前仓库中 Bazel 拉取 LLVM 快照、应用补丁的实际实现MODULE.bazel、bazel/llvm_project/llvm_project.bzl说明仓库瘦身之后 LLVM 依赖是如何无缝工作的。读完本文你既能理解一次大型开源项目破坏性历史重写的完整决策链也能掌握 fork/clone 迁移的实操命令还能看懂 Carbon 当前构建系统中 LLVM 的获取与补丁机制。一、问题背景为什么 LLVM 源码必须移出 Carbon 仓库提案的 Problem 一节开宗明义Carbon 曾先后尝试用Git submodules和Git subtrees两种方式来管理 LLVM 源码的引入两种方案都带来了长期无法忍受的问题。1.1 Submodules 的问题仓库易用性大幅下降Git submodules 虽然生态支持完善但它让整个仓库变得对用户不友好一些常见操作——尤其是首次克隆仓库——在存在子模块时会变得烦人地复杂某些操作如切换分支甚至会触发难以诊断的报错它不直接支持在子模块上携带本地补丁carrying local patches。正是这些问题促使 Carbon 从 submodules 切换到了 subtrees。1.2 Subtrees 的问题更新 LLVM 困难且易错Git subtrees 一旦配置好对大多数用户来说就是一个普通目录并且制作和携带本地补丁也特别方便。但它的短板在于初始化subtree 和更新subtree 的操作容易出 bug与 GitHub 的 pull request 工作流配合得非常糟糕结果就是更新 LLVM 这一操作既极其困难又极易出错。提案中还特别指出一个被 subtrees 暴露出来的根本问题LLVM 仓库本身极其庞大包含大量 Carbon 几乎不可能用到的大型复杂项目。把 LLVM 的全部历史与空间从一开始就塞进 Carbon 仓库毫无意义它导致git status、克隆仓库等操作的成本被成倍放大却几乎没有带来任何收益。二、提案核心Bazel 构建时下载 LLVM 快照 破坏性历史重写2.1 方案内容提案给出的方案是改用 Bazel 在构建时下载 LLVM 的快照snapshot替代把 LLVM 源码放在仓库里对 Carbon 仓库做一次破坏性的历史重写destructive history re-write把 LLVM 从历史中彻底移除。方案预期效果所有针对 LLVM 的构建继续无缝工作但仓库本身会变得非常小、操作非常快而快照下载的成本远低于克隆或操作一个带完整历史的 LLVM。2.2 为什么必须做破坏性重写提案明确指出唯一能拿到瘦身收益的方式就是对仓库历史做破坏性更新。这虽然不令人愉快但必须尽快做、并且要在项目转为公开public之前做。提案还给出了一个关键的成本论证尽管这次变更代价不小但不做这件事的成本会随项目生命期无上限地增长——LLVM 占用的历史空间会被永久携带。重要提示原文加粗强调这一变更必然要求每个 fork 和 clone 都进行一次手动更新提案随后给出了完整的更新步骤见本文第五节。三、实现细节用 git-filter-repo 清理历史3.1 工具选择提案选择使用git-filter-repo工具来完成历史重写。该工具可以非常方便地从历史中剔除任意目录树并把剩余历史重放replay一遍得到干净、清晰的结果。3.2 顺手移除的其他无用目录既然已经要切断历史提案主张顺便把价值抓住——让新历史最小化、只保留真正在用的仓库内容。因此除了 LLVM 之外还会一并移除三个已不再使用的目录src/jekyll/src/firebase/website/其中最大的是src/jekyll压缩后仍有 988KB但提案认为最干净的做法是三个一起移除。3.3 当前仓库中的佐证快照机制确实落地了从当前仓库的构建配置看提案中Bazel 构建时下载 LLVM 快照的设计已经完整实现固定上游 LLVM 版本MODULE.bazel 中声明了bazel_dep(name llvm-raw)并通过git_override将llvm-raw固定到 llvm-project 的特定提交文件内注释写明我们 pin 到具体的上游 commit并尽量紧跟主干而不是 pin 到某个 release当前固定提交为a6b0af7536ef0ae8383ec729c5fbf23c73243776。下载后立即打上 Carbon 的补丁同一git_override中列出了 7 个补丁文件patch_cmds/patches字段它们就存放在仓库的 bazel/llvm_project/ 目录下由 bazel/llvm_project/BUILD 统一导出0001_Patch_for_mallinfo2_when_using_Bazel_build_system.patch0002_Added_Bazel_build_for_compiler_rt_fuzzer.patch0004_Introduce_basic_sources_exporting_for_libunwind.patch0005_Introduce_basic_sources_exporting_for_libcxx_and_libcxxabi.patch0006_Add_more_libc_math_excludes.patch0009_Introduce_starlark_exporting_compiler-rt_build_information.patch0011_Temporarily_remove_reference_to_hermetic_toolchain.patch生成可用的llvm-project仓库bazel/llvm_project/llvm_project.bzl 定义了llvm_project模块扩展它调用上游 LLVM 的llvm_configure对原始llvm-raw做 overlay生成名为llvm-project的仓库并只启用 Carbon 需要的目标架构AArch64、X86。MODULE.bazel 中的use_extension/use_repo负责把llvm-project暴露给整个构建。这种原始快照仓库llvm-raw overlay 配置llvm-project的两层结构正是提案中补丁从 Carbon 仓库应用到 LLVM 快照这一初始方案的实际形态。四、LLVM 打补丁移出仓库后如何继续改 LLVM提案在 Patching LLVM 一节给出了分阶段的策略初期采用最简单的方式——把一组补丁文件存放在 Carbon 仓库中构建时把它们应用到 LLVM 快照上。提案特别提到在 Carbon 仓库未公开的阶段这种方式非常方便。当前仓库中bazel/llvm_project/下的 7 个补丁文件就是这一策略的直接体现。长期可以切换为维护一个 LLVM 的 fork从 fork 做快照把需要的补丁直接开发在 fork 里。提案预判如果未来做 C/C 互操作支持时需要在 Clang 上进行较大规模开发一个更健壮的长期方案将变得很重要。4.1 本地开发补丁的捷径--override_repository提案还介绍了一个对补丁开发者非常实用的 Bazel 能力向构建传入--override_repositoryllvm-projectdirectory可以让 Bazel 直接改用本地的 LLVM 检出目录包括你正在测试的补丁而不去下载快照。如果要长期做大量开发可以把这行写进user.bazelrc从而持续使用你的本地开发目录。4.2 与 Carbon 源码的对接点从源码结构看Carbon 对 LLVM 的依赖是显式且收敛的third_party/llvm/BUILD 中的clang_cc1库直接依赖llvm-project//clang:basic、llvm-project//clang:codegen、llvm-project//clang:frontend、llvm-project//clang:frontend_tool、llvm-project//clang:serialization和llvm-project//llvm:Support等目标toolchain 各子模块如 toolchain/base/BUILD、toolchain/lower/BUILD 等也以llvm-project//...形式引用下游目标。这些依赖名完全由 Bazel 仓库规则提供因此无论 LLVM 是从仓库子树里来还是从 Bazel 快照里来构建语义不变——这正是提案所说building against LLVM will continue to work seamlessly的落地方式。另需注意 third_party/llvm/README.md 的说明该目录存放的是从 LLVM 项目中抽取出来的代码用于某些逻辑没有现成的、适合以库形式暴露的 API、或需要大量定制的场景。由于 Carbon 与 LLVM 使用相同许可证链接这些代码没有问题。这说明移出仓库的只是 LLVM 的完整源码与历史而少量抽取式复用的代码会保留在仓库中属于正常工程实践。五、fork 与 clone 的完整迁移步骤这是提案中最具实操价值的部分历史重写后每个 fork 和 clone 都需要手动更新。提案建议的流程分为四步。5.1 第一步归档现有的 fork 和 clone前往归档仓库carbon-language/archived-carbon-lang原文档给出的链接已失效以仓库名指代。像平常一样 fork 这个归档仓库fork 到你的个人空间下。提案强调一定要 forkcarbon-language组织托管的归档仓库这样才能继承其 ACL访问控制列表不要自己新建个人仓库。把每个 clone 的远端改指到归档仓库避免误与新仓库交互。原文档示例命令假设origin指向你的 fork、upstream指向主仓库你可能还需要把 SSH 风格 URL 调整为 HTTPS 以保持一致git remote set-url upstream gitgithub.com:carbon-language/archived-carbon-lang.git gitgithub.com:carbon-language/carbon-lang.git git remote set-url origin gitgithub.com:$USER/archived-carbon-lang.git gitgithub.com:$USER/carbon-lang.git对每个 clone把全部内容镜像推送到新的origin即你的归档 forkgit push --mirror origin如果你有多个包含不同但相互重叠分支的 clone镜像推送时可能需要额外步骤来避免冲突。走到这一步后你的所有 clone 都指向归档 fork可以正常地继续工作。这一阶段的目标只是归档确保不丢失任何东西。5.2 第二步创建全新的 fork 和 clone删除你现有的carbon-langfork注意不是archived-carbon-lang进入你 fork 的设置页即你的用户名下的carbon-lang仓库的 Settings务必确认这是你的fork而不是carbon-language组织的仓库滚动到底部点击 Delete this repository。删除后从已经过过滤filtered的主仓库carbon-language/carbon-lang重新发起一次 fork然后克隆这个新 fork——至此你就在新仓库结构下准备就绪。5.3 第三步从归档 clone 迁移进行中的分支对于归档 clone 中你想保留的分支提案给了两种做法。做法一最简单把分支导出为补丁文件再在新仓库中应用。原文档给出的完整命令序列假设旧 clone 在archived-carbon-lang目录、新 clone 在carbon-lang目录且两者当前都在trunk分支# If your old clone is in the archived-carbon-lang directory and your new # clone is in carbon-lang, both of them on the trunk branch: cd archived-carbon-lang git switch $BRANCH git diff upstream/trunk... ../$BRANCH.patch cd ../carbon-lang git switch -c $BRANCH patch -p1 ../$BRANCH.patch git commit这种方式会把该分支压平flatten为新 clone 中的单个 commit。做法二保留提交历史使用git format-patchgit am。提案提醒三个要点先在归档仓库中把该分支 rebase 到最新的 trunk 之上形成干净的提交序列用git format-patch把一系列补丁导出到某个目录详细用法见其帮助用git am在新 clone 的某个分支下应用这个补丁序列同样参见帮助。5.4 第四步处理未合并的 PR你在第二步删除 fork 后未合并的 PR 应该会被自动关闭如果没有最简单的办法就是手动关掉它们、重新创建新 PR。进行中的讨论线程会显得尴尬但也别无他法必要时可以重命名提案文档。提案当时还提到团队会探索能否从新 fork 做一次干净的 force push 后重新打开 PR但不确定能否成功而且这并不必要——当时只有 12 个 PR 处于打开状态所以直接重建即可。六、Review 评论可能受损提案还预见到一个副作用编辑仓库之后部分 PR 评论尤其是跟随新 fork 更新的未合并 PR可能丢失与其所针对代码行的关联有些评论甚至可能找不到。影响范围最多只涉及 PR 代码中的行内inline评论——而这恰恰是 PR 中最常见的评论形态大多数这类评论预计仍会以某种形式出现在 PR 的 conversation 视图中只是由于不再挂靠在某一行上而变得难以查找提案认为总体风险可控详见 7.4 节对手动存档评论方案的评估。七、备选方案与取舍Alternatives considered提案花了大量篇幅论证为什么其他路径不可取这部分是理解决策逻辑的关键。7.1 什么都不做Do nothing继续维持半坏的 Git subtree 方案。优点无需改变现有做法无需对仓库做需要全员更新的破坏性操作。缺点每次更新 LLVM 都依然是困难、手工且易错的——当时 Carbon 正携带着一个 LLVM 补丁而这个补丁在更新过程中**很容易丢失并且确实丢过会持续撞上 Git subtree 本身的 bug——subtree 既脆弱又无人把它当优先事项团队将不得不费力去理解、修复或绕过这些问题仓库依然庞大、缓慢并且包含大量永远不会用到的 LLVM 代码如果未来有用户想把 Carbon 仓库导入其他环境他们将不得不为 LLVM 买单或以某种方式把它剥离出去甚至可能出现两份 LLVM的情况。结论这个问题值得被解决。7.2 不重写历史Dont rewrite the repository history可以不重写历史来解决这个问题但提案强调一个推论如果不现在重写历史就应该预期自己永远都不会重写历史——因为重写历史的成本只增不减。优点无需为更新 fork、clone、进行中的补丁做手工操作commit hash 保持稳定所有与 commit hash 关联的代码评审评论信息都会保留。缺点必须永远为曾经导入过 LLVM付出代价。即使这个代价是增量的克隆带历史仓库时多出的仓库空间它也是一笔永远不会停止支付的成本。结论虽然眼前成本很高但为 LLVM 留在历史中而付出的代价在时间维度上是无界的最终会占主导地位所以应该重写历史而且越早越好。7.3 回到 submodulesGo back to submodules优点更接近 Git 原生方案如果将来添加非 Bazel 的构建系统不需要再发明一套方案。缺点subtree 时代看到的克隆成本和其他 Git 命令成本大部分依然存在让仓库的 Git 使用方式变得更难正确操作虽然比 subtree 更常规但它仍然暴露出 Git 中一个不太精修的接口面团队必须持续应对最关键的它会重新引入绊脚石初次接触 Carbon 的用户无法获得无缝体验。补充说明当初离开 submodules 的原始动机是需要携带本地补丁而 submodules 与本提案方案在这方面的选项其实相同补丁策略见第四节。结论应该尝试一个更简单的方案即便它不那么Git 原生优化新贡献者的首次体验尤为重要Bazel 方案预计更无缝。7.4 重命名旧仓库并新建仓库Rename the repository, and create a new one不做原地编辑而是把旧仓库挪到一边用编辑过历史的内容建一个新仓库。优点可以避免原地历史编辑的某些破坏性——由于 commit hash 得以保留PR 中与 commit hash 关联的部分如评论定位可能继续有效。缺点对大多数情况破坏性同等严重——fork 和 clone 按仓库名引用仓库它们需要做的更新与原地编辑方案完全相同要么需要构建一套系统来精确复现原有的 issue 编号和 PR 编号甚至可能做不到要么只能任由所有编号重排PR 编号重排尤其破坏性大因为commit log 通过 PR 编号把提交与产生该提交的代码评审关联起来而编号还是提案编号proposal numbers的基础很可能没有把 PR 搬回主仓库的办法浏览历史代码评审会变得有问题。结论这个方案实质上是用保留 PR 内的部分评论历史去换保留所有 PR/issue 的位置、编号和链接这不是正确的取舍。7.5 手动提取并归档部分 review 评论Manually extract and archive some review comments优点为可能的信息丢失提供纵深防御。缺点要把这件事做对需要相当的工作量可能并没有丢失多少信息——评论预计仍会在 conversation 视图中只是不太好找不清楚这些脱离评审上下文的评论在被提取出来后是否还有价值存在严肃的顾虑擅自抓取并搬走别人在 GitHub 这个系统内撰写的评论本身不太妥当。结论存在一定风险但并不算太高相对于所涉数据的总量试图缓解这一风险的成本显得不合理。八、设计动机Rationale提案把动机映射到了项目目标文档见 docs/project/goals.md的两条目标上社区与文化Community and culture这一变更会让克隆、构建甚至贡献变得更轻松顺畅克隆成本将显著降低尤其是那些不构建代码、只处理文档的克隆语言工具与生态Language tools and ecosystem随着团队扩展正在开发的语言工具和生态集合保持能够基于 LLVM 构建、能够开发 LLVM 补丁的能力将非常重要。九、小结一次越早做越便宜的历史级决策综合提案全文与当前仓库的实现现状可以得到三点结论决策逻辑submodule 与 subtree 各有致命伤而LLVM 历史永远留在仓库里是一笔无界成本因此选择一次性破坏性重写把一次性的高成本换成永久的低成本。工程落地Bazel 的git_override固定上游提交 仓库内补丁文件 llvm_configureoverlay见 MODULE.bazel 与 bazel/llvm_project/llvm_project.bzl加上--override_repository提供的本地 LLVM 覆盖能力构成了快照 可补丁的完整闭环使构建对 LLVM 的依赖方式保持不变。迁移成本被显式管理提案没有回避破坏性重写的副作用而是给出了 fork/clone 归档、重建、分支迁移、PR 重建的全套步骤并对 review 评论受损做了预期管理。这套迁移手册本身对任何需要做破坏性历史重写的开源项目都有参考价值。对读者而言如果你正在 Carbon 仓库中工作首次构建时 Bazel 会自动按 MODULE.bazel 的固定提交下载 llvm-project 并应用bazel/llvm_project/下的补丁如果你需要修改 LLVM 本身用--override_repositoryllvm-projectdirectory指向你的本地 LLVM 检出即可长期开发可写入user.bazelrc。仓库本身不再包含 LLVM 源码与历史克隆、git status等操作的开销因此大幅降低——这正是提案 p001344 兑现的收益。【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考