Carbon 语言工具链与语言版本管理方案:SemVer 驱动的MAJOR.MINOR.PATCH版本化设计全解析
【免费下载链接】carbon-langCarbon Language's 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 项目提案 Establish toolchain and language versioning 及其落地的 版本管理文档 为核心,系统讲解 Carbon 如何为语言、标准库、编译器、链接器等全套工具链设计一套**单一、统一、基于 Semantic Versioning(SemVer)**的版本号方案:MAJOR.MINOR.PATCH各段位的递增语义、破坏性变更的判定与豁免规则、rc.N/nightly/dev三类预发布版本的含义与排序机制,以及面向 1.0 之后的语言演化(类 Rust Edition 机制)、LTS 版本与标准化的前瞻规划。读者读完后既能完整理解 Carbon 版本号每一段的"何时变、为何变、怎么读",也能在仓库源码(bazel/version/与.bazelrc)中看到这套方案的真实工程落地方式。
为什么 Carbon 需要版本化方案
提案在 Problem 一节指出:Carbon 无论对语言本身还是实现该语言的参考工具链,都需要一套版本化方案。这套方案在尚未达到任何具体里程碑之前就应定义并落地——因为版本号既要服务于里程碑标记,也要在"它开始对用户有意义之前"把规则先定清楚,避免临场拍脑袋。
更深层的原因是:编程语言与其标准库对用户暴露的"API"面极其广阔且耦合紧密——几乎所有用该语言写出的源码都依赖这个"API"。因此 SemVer 通用标准必须被"翻译"成语言语境下的具体规则,明确什么是 Carbon 的"公共 API"、什么算变更,否则版本号将缺乏可判定的语义。
从项目目标看(docs/project/goals.md),这份版本化方案直接服务于两大目标:
- 语言工具与生态系统:Carbon 需要一个贯穿语言本身及生态的一致版本方案,尤其在语言高速演进期,让工具链与生态就语言特性"对表";
- 软件与语言演化:用户需要一个清晰的模型来理解、跟踪语言演化,并在大量不同形态的版本中不产生混乱。
提案核心:单一版本 + SemVer + 明确的预发布语义
提案的 Proposal 一节给出四点主张,随后在 Summary 中浓缩为可操作的版本格式:
| 主张 | 要点 |
|---|---|
| 单一版本 | 语言、标准库、编译器、链接器及主项目发布的全部开发工具共享同一版本号 |
| 基于 SemVer | 版本格式MAJOR.MINOR.PATCH,并明确 SemVer 标准在语言语境下的映射 |
| 明确的预发布语义 | 定义一套数量少、语义清晰的预发布版本,避免歧义 |
| 面向未来的方向性指引 | 为 1.0 之后的语言演化、LTS、标准化预留方向 |
汇总后的版本格式为:
MAJOR.MINOR.PATCH:正式版本号;MAJOR.MINOR.PATCH-rc.N:第 N 个"可能成为正式发布"的候选版本;MAJOR.MINOR.PATCH-0.nightly.YYYY.MM.DD:某天自动构建的夜间增量开发版;MAJOR.MINOR.PATCH-0.dev:开发者交互式增量构建的开发版。
下面结合 版本管理文档 的细化内容逐段拆解。
MAJOR:主版本递增与破坏性变更
与 SemVer 对齐,任何对语言或工具链任意部分的破坏性变更(breaking change)都必须递增主版本。文档 docs/project/versioning.md 对此有更细化的约定:
- 从
0到1的首次递增,基于达成某个功能完整性与质量里程碑(即声明语言进入稳定版,对应 docs/project/milestones.md 中的 1.0 里程碑); - 1.0 之后的递增采用基于时间的发布策略:已就绪的特性/破坏性变更随当前主版本发布,其余等待下一个主版本;具体节奏属于未来工作,需在临近 1.0 时与用户讨论确定;
- 递增主版本只是"可以"做破坏性变更,不代表"应该"做。破坏性语言变更因用户迁移成本(churn)极高而代价巨大;但参考 C++ 语言、编译器与标准库更新的经验,真正做到"零破坏"极其困难且过度约束。预期多数版本(尤其早期)会包含一定程度的破坏性变更,项目的工作方向是让升级尽可能廉价、变更尽可能小;
- 未来若 Carbon 稳定到一定程度,可能重新评估用次版本号承载某些发布,届时会再修订版本与发布策略。
什么算破坏性变更
在 docs/project/versioning.md 中,破坏性变更不仅限于传统意义上的 API 破坏(标准库 API 或工具 API 变化),还包括语言与工具链层面:任何导致正确、可用、非反射(non-reflective)的代码变得无效、被拒绝、结果不正确或行为被静默改变的变化,都算破坏性变更。
豁免项:什么不算破坏性变更
文档明确了两类豁免:
- 反射式代码(reflective code):以某种方式探测 Carbon 版本、或检测某特性存在与否的代码。它们可能因为"检测到了本该是非破坏性的新增"而被"破坏"。Carbon 不希望新增特性被算作破坏性变更,因此将这类专门检测新增内容的代码排除在外;
- 错误代码的破坏:除非某段错误代码尽管有 bug,但已被接受且在某种有用(且通常广泛)的方式下正常运行,否则对错误代码的破坏性修改不算破坏性变更。
MINOR:次版本递增的定位
当前阶段 Carbon 计划主要依靠主版本为0时的次版本递增来追踪通向 1.0 的进度。具体而言:
- 已定义
0.1与0.2两个里程碑(详见 docs/project/milestones.md),必要时可再增加细分步骤; - 提案 p004105 明确:
MINOR递增只在早期开发阶段、主版本为0时预期发生;若未来 Carbon 足够稳定,可能重新启用次版本号来标记向后兼容的发布; - 在 1.0 之后的世界里,项目预期多数重要特性会伴随少量可管理的、由废弃(deprecation)引起的破坏性变更,因此当前计划是 1.0 之后不做纯次版本发布,而是聚焦于让更新对语言用户"既容易又规模化"。
PATCH:补丁版本只修 bug
补丁版本仅在对先前发布版本进行根本性 bug 修复时递增,绝大多数应为严格向后兼容的修复。关键约定:
- 补丁发布由需求驱动,不必然存在;具体排程与流程将在准备 1.0 里程碑时确定,1.0 之前不承诺任何补丁发布流程或节奏(因为此前的任何发布都不应被视为稳定);
- 恢复某个版本的"预期公共 API"也被视为 bug 修复:即使这类修复理论上可能破坏按新写法编写的代码、通常需要递增主版本,只要它确实是在恢复该版本的预期行为,就可以仅用补丁版本承载。
由于这类修复仍可能造成破坏,SemVer 承诺被严肃对待,补丁修复需满足很高的门槛,docs/project/versioning.md 给出三项叠加判据:
- 必须是回归(regression)修复而非特性补齐:修复的是用户从先前版本遇到的回归,包括语言或工具整体凝聚力/可靠性的回归;任何补丁修复都应从"回归修复与稳定化"而非"向前修复"的视角论证;
- 破坏范围可证明很小:结合"含回归的发布持续时长很短"与"可能受影响的代码构造范围很窄"来判定;
- 回归影响大且难以绕过:例如动摇了相当规模用户群的核心优先目标,或使任何重要用户群采用该发布版本的工程成本不经济。
PATCH 判例:文档中的四个示例
| 场景 | 是否值得补丁发布 | 原因与替代处理 |
|---|---|---|
| 新特性含 bug 导致 unsoundness,可编译出运行时崩溃或 UB 的错误代码 | 值得 | 即使属新特性,也是语言整体可靠性的严重回归;除非发布数月后才被发现且修复代价同样巨大(安全性漏洞除外) |
| 小众新特性含 bug,使本应可用的代码模式被编译期拒绝或稳定崩溃 | 不值得 | 使用面窄,入侵式修改不划算;更适合对"可能触发崩溃的用法"甚至"使用该特性"发出警告或错误 |
| 通过 flag 开启的编译器可选特性不可靠 | 不适合向前修复 | 更宜禁用该 flag 或触发警告 |
| 编译器默认开启(opt-out)的特性不可靠 | 视影响面而定 | 影响极小则文档化退出方式;影响足够大、构成体验回归,则值得窄范围补丁修复或缓解 |
预发布版本:rc、nightly 与 dev 的语义与排序
SemVer 为预发布版本提供了非常开放的框架,但没有规定语义模型。Carbon 选择定义一套小而清晰的预发布版本,让它们用 SemVer 规则比较时能有序排列。文档规定的预发布版本(降序):
MAJOR.MINOR.PATCH-rc.N MAJOR.MINOR.PATCH-0.nightly.YYYY.MM.DD MAJOR.MINOR.PATCH-0.dev发布候选:-rc.N
当某个版本被认为完整、可发布并希望收集反馈时,创建"release candidate"(rc)预发布。判定标准:与预期正式版之间不应存在有意义或显著的差距(哪怕是已知差距);除非收到反对反馈,发布候选应能直接转正为正式版本。
每个预发布类别都使用该版本的顺序计数N,且必须以.0起始,才能保证同一版本、同一类别后续迭代版本在 SemVer 排序中排在第一个之后。
夜间构建:-0.nightly.YYYY.MM.DD
夜间版本是每晚自动化测试通过后自动构建的增量开发版:
- 不提供任何高于"当时代码库 + 通过自动化测试"的质量门槛;
- 其首要用途不是评估潜在发布,而是供 Carbon 开发者与贡献者跟踪增量开发进度;
- 机制上,在
nightly之前插入0组件,确保其排序在所有发布资格预发布版本之前;追加日期派生后缀YYYY.MM.DD,为版本号相同的夜间构建提供粗略的时间顺序。
开发构建:-0.dev
开发期间的交互式构建需要无歧义的版本标记,即dev预发布版本:
- 与夜间版本类似,只是开发活动的产物,从不参与任何发布流程;
- 它们不要求自动化、不要求可复现,可能包含未完成的在途编辑及各种变体;
- 机制上同样以
0前缀保证有效排序;不附加额外信息(例如无法轻易提取构建日期),以保持开发构建机制简单并最小化缓存影响。
源码落地:版本字符串在仓库中的真实生成链路
提案与文档并非纸上谈兵——仓库中已有一套完整的 Bazel 版本生成实现,可以直接观察到这套方案如何被工程化。
版本基线与预发布后缀的拼装
仓库根目录的 version_base.bzl 定义了当前活跃开发版本的基线:
version_base = "0.0.0"该文件注释明确说明:当前开发版本为0.0.0,因为尚未对 0.1 里程碑取得足够进展;且永远不会发布 0.0.0 的非开发预发布版本,只会出现 0.0.0 的夜间开发预发布。
真正的拼装逻辑在 bazel/version/compute_version.bzl 的compute_version函数中(对应提案中三类预发布后缀的编码):
- 若
_release_flag为假,则依据pre_release值追加后缀:rc:读取rc_number,要求非负,拼为-rc.N;nightly:读取nightly_date,并调用_validate_nightly_date严格校验YYYY.MM.DD三段格式(年 4 位、月/日各 2 位且均为数字),拼为-0.nightly.YYYY.MM.DD;dev:直接拼为-0.dev;- 其他取值直接
fail。
这与提案中rc、nightly、dev的形态一一对应,包括nightly与dev必须带0前缀的排序设计。
Bazel 构建标志与命令行别名
bazel/version/BUILD 定义了四个版本相关构建标志(均为默认值,供 Bazel 调用时覆盖):
| 标志 | 类型 | 默认值 | 说明 |
|---|---|---|---|
--release | bool | false | 启用正式发布版本;启用时必须是唯一使用的版本标志 |
--pre_release=KIND | string | "dev" | 取值仅限rc、nightly、dev三者之一 |
--rc_number=N | int | -1 | 设置发布候选编号,要求--pre_release=rc |
--nightly_date=YYYY.MM.DD | string | "" | 设置夜间预发布日期,要求--pre_release=nightly |
为方便命令行使用,.bazelrc 提供了等价的短别名,因此实际构建时可写为:
bazel build //... --release bazel build //... --pre_release=rc --rc_number=2 bazel build //... --pre_release=nightly --nightly_date=2024.06.17 bazel build //... --pre_release=dev(源码注释中以2024.06.17作为 nightly 日期示例,实际日期按需替换即可。)
从模板到最终版本字符串
bazel/version/rules.bzl 的expand_version_build_info规则负责把模板文件扩展为携带版本与构建信息的源码文件,支持的替换键包括VERSION、GIT_COMMIT_SHA、GIT_DIRTY_SUFFIX、BUILD_TIMESTAMP等。最终成品可在 common/version_stamp.tmpl.cpp 中看到版本字符串的实际形态:
constexpr llvm::StringLiteral Version::String = "$VERSION+$GIT_COMMIT_SHA$GIT_DIRTY_SUFFIX"; constexpr llvm::StringLiteral Version::ToolchainInfo = R"""( Carbon Language toolchain version: $VERSION+$GIT_COMMIT_SHA$GIT_DIRTY_SUFFIX )""";也就是说,Carbon 工具链实际展示的版本形如0.0.0-0.dev+<git短哈希>[.dirty],将提案中的版本方案与 git 提交信息组合为可追溯的构建标识,供carbon驱动等工具读取(版本头文件见 common/version.h)。
版本号与里程碑的联动
提案与里程碑文档 docs/project/milestones.md 明确挂钩:Carbon 为初始里程碑分配版本号,便于引用与纳入版本化方案——
- 0.1(MVP):供 C++ 用户与开发者开始认真评估的最小可用产品,覆盖核心语言特性(包、导入、命名空间、用户定义类型、泛型、控制流等)与最小标准库组件;
- 0.2(功能完整):达到足以"让用户完成评估"的功能完备度,内存安全、协程/async、Carbon 原生线程等显式推迟至此里程碑之后;
- 1.0(不再是实验):标志 Carbon 不再是实验、可用于生产。版本化文档将
0 → 1的首次递增定义为基于功能完整性与质量里程碑的判定。
这正是MINOR递增在0.x阶段扮演"进度刻度"、MAJOR递增承担"稳定性声明"的设计意图。
面向未来的方向性草图
提案的 Directional sketch for the future 明确指出:上述机制对 1.0 之前的版本足够,但 1.0 之后语言与项目的需求将扩大,需要更多细化的版本与演化工具。这些只是方向性指引,届时需要各自的独立提案。
语言演化与破坏性变更的解耦
长期来看,单一版本号不足以支撑语言的演化需求。提案建议至少把主版本映射为类似 Rust Edition 的源码内控制机制:
- 允许代码库在同一工具链版本下增量采纳破坏性变更的修复或新语言特性——即部分代码即使使用新编译器,仍可按先前主版本的语义编译;
- Rust 已在实践中验证、C++ 亦被提议采用"editions":源码显式选择版本语义,使编译器在增量迁移期间支持混用代码;
- Carbon 至少需要等价机制,并可能探索更细粒度的功能集 opt-in系统(类似 pragma 式扩展用法或 Circle 编译器);
- 无论具体形态如何,关键在于:破坏性变更的铺开不得被强制绑定到 Carbon 工具链升级,每一步都应增量推进。
LTS 版本与标准化
SemVer 足够"检测"问题,但不足以"解决"某些用户对语言稳定性的需求,方向是:
- LTS 版本:基于特定完整度、质量水平与用户需求指定 LTS;支持窗口可能比 Linux 发行版 LTS 更长,甚至需要数十年级别的支持窗口;窗口拉长后 LTS 频率应降低,避免维护过多版本造成不可持续;
- LTS 的指定方式留给未来工作,但预期不改变版本号模式与结构,只改变针对该发布版本的"支持策略";
- 标准化:部分环境要求语言标准化才可使用。提案将标准化类比为"把普通发布提升为 LTS"——选定某个有效的 LTS 走标准化流程形成标准,标准更新则跟踪为更新版本的 LTS。具体做法留给未来工作;
- 核心意图不是约束达成 LTS 或标准的路径,而是明确 Carbon应当开放并规划这些能力,以满足潜在用户的需求。
备选方案对比:为什么这样设计
提案的 Alternatives considered 记录了被否决的备选路径,可帮助理解最终取舍:
| 备选方案 | 否决原因 |
|---|---|
| 什么都不做 / 只谈最小 nightly 版本 | 项目各处(milestones、roadmap)已在讨论版本号;提前建立沟通机制更有效,也能更早向社区传递发布意图与节奏 |
| 1.0 之后不做任何破坏性变更 | 与"软件与语言演化"目标直接冲突;Carbon 为兼顾 C++ 互操作与增量内存安全注定较复杂,必须保留改进与修复问题的能力 |
| 各组件独立版本化 | 需仔细维护组件间兼容矩阵,而现代语言中编译器、语言、标准库、工具链深度耦合;Clang/GCC 大版本升级的经验表明工具链变更与语言变更同样具破坏性 |
| 使用自定义版本方案而非 SemVer | SemVer 本就是宽松框架、可容纳自定方案;套用 SemVer 只需极小的装饰性调整,还能直接复用脚本与大众心智模型,避免外部人员误读 |
| 增加更多预发布变体(如 alpha/beta) | 无证据表明需要;nightly + rc 的组合大概率已覆盖全部实际用例,维护少见用例的基础设施与文档是净负担 |
其中"什么都不做"一节还强调:这套详细方案不是锁死——Carbon 可以在任何时刻按需修改任何部分,会倾听潜在用户反馈并调整。
结语
Carbon 的版本化方案以"单一版本 + SemVer + 明确预发布语义"为骨架:MAJOR承载破坏性变更与里程碑声明、MINOR服务 0.x 阶段的进度追踪、PATCH只修 bug,rc.N、0.nightly.YYYY.MM.DD、0.dev三类预发布则把发布候选、自动夜间构建与交互开发构建各归其位。这一设计不仅写进了 docs/project/versioning.md,也已通过 version_base.bzl、bazel/version/compute_version.bzl 与 .bazelrc 等真实工程化实现落地,并经由 common/version_stamp.tmpl.cpp 进入每个工具链二进制。对关注 Carbon 演进的开发者而言,理解这套规则,就等于拿到了解读 Carbon 每一个发布、每一次升级意图的"版本号字典"。
【免费下载链接】carbon-langCarbon Language's 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),仅供参考