ASP.NET Core API Baselines:PublicAPI 分析器与 PublicAPI.Shipped/Unshipped.txt 工作流详解
在大型 .NET 框架仓库中,每一次公共 API 的新增、修改或删除都可能悄悄破坏下游用户的二进制兼容性。ASP.NET Core 仓库通过 Roslyn 的 Public API Analyzers 配合 PublicAPI.Shipped.txt / PublicAPI.Unshipped.txt 两个基线文件,把"API 变更"从隐性风险变成了 PR 中一目了然、必须显式记录的显式条目。读完本文,你将能够理解这两个基线文件各自的职责、如何在项目中正确启用或禁用该检查、如何为新项目初始化基线文件,并掌握新增、移除、更新 API 时逐条维护基线的完整操作步骤。
基线机制概述
本文内容基于仓库官方文档 API Baselines。该文档说明:API 基线文件本身由 Microsoft.CodeAnalysis.PublicApiAnalyzers 这一 Roslyn 分析器包实现,分析器的完整文档以 .NET 官方的 roslyn-analyzers 仓库中 PublicApiAnalyzers.Help.md 与 Microsoft.CodeAnalysis.PublicApiAnalyzers.md 为准(见 docs/APIBaselines.md 中的外部引用)。仓库内维护基线文件的惯例如下:
PublicAPI.Shipped.txt:记录上一个主版本中已经发布(shipped)的全部公共 API。该文件只允许由构建团队在主版本发布之后通过脚本修改,其他任何情况下都不应手动修改。PublicAPI.Unshipped.txt:记录自上一个主版本以来新增、修改、删除的 API 条目。日常开发中所有 API 变更都落在这个文件里。
这种"已发布/未发布"分文件的设计,使得 PR diff 中出现的每一条基线变更都代表一次真实的 API 表面变动,审阅者可以逐条审查兼容性影响。
仓库中的启用机制:默认开启 + 属性开关
从源码结构看,仓库并不是靠每个项目逐个声明来开启基线检查的。在 eng/targets/CSharp.Common.targets 中可以看到默认行为:
<!-- Ensure API changes show up clearly in PRs. -->
<AddPublicApiAnalyzers Condition=" '$(AddPublicApiAnalyzers)' == '' AND
'$(IsImplementationProject)' == 'true' AND
! $(RepoRelativeProjectDir.Contains('Tools')) ">true</AddPublicApiAnalyzers>
<AddPublicApiAnalyzers Condition=" '$(AddPublicApiAnalyzers)' == '' ">false</AddPublicApiAnalyzers>
也就是说:只要项目被标记为实现项目(IsImplementationProject == true,通常即 src 目录下的实现工程)且不在 Tools 目录下,基线检查就默认启用——这正是 docs/APIBaselines.md 中"API baseline are enabled by default"这句话的工程来源。分析器引用也在这里注入:
<ItemGroup Condition=" '$(DotNetBuildSourceOnly)' != 'true' AND $(AddPublicApiAnalyzers) ">
<Reference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" ExcludeAssets="Compile" PrivateAssets="All" />
</ItemGroup>
所引入的分析器版本由 eng/Versions.props 中的 MicrosoftCodeAnalysisPublicApiAnalyzersVersion(当前仓库为 3.3.3)统一管理。
文档给出的禁用方式与上述属性一一对应:如果新项目是非发布(non-shipping)或纯测试项目,在项目文件中加入:
<AddPublicApiAnalyzers>false</AddPublicApiAnalyzers>
即可关闭检查。另外还有一个值得注意的保护逻辑:若某个项目目录下存在 PublicAPI.Shipped.txt 但 AddPublicApiAnalyzers 又被关闭,构建会输出 "Public API baseline files ignored." 的警告(同样是 eng/targets/CSharp.Common.targets 中 _CheckIgnoredPublicApiFiles 目标),提示你基线文件被静默忽略了。
为新项目添加基线文件
创建新的 src 实现项目后,必须手动添加基线文件。仓库提供了空模板 eng/PublicAPI.empty.txt,其内容只有一行 #nullable enable(开启基线文件的可空性感知,使后续条目可以携带 !、? 等可空性标注)。按 docs/APIBaselines.md 的步骤操作(文档中使用的是 Windows PowerShell 命令):
cp .\eng\PublicAPI.empty.txt {new folder}\PublicAPI.Shipped.txt
cp .\eng\PublicAPI.empty.txt {new folder}\PublicAPI.Unshipped.txt
在 Linux/macOS 环境下等价写法为:
cp eng/PublicAPI.empty.txt {new folder}/PublicAPI.Shipped.txt
cp eng/PublicAPI.empty.txt {new folder}/PublicAPI.Unshipped.txt
创建完成后,即可按后文"添加和更新 API 的步骤"把新 API 补进 PublicAPI.Unshipped.txt。
一个容易被忽略的细节:在 eng/targets/CSharp.Common.targets 中,AddPublicApiAnalyzers 只对实现项目生效,而 _IsSrcProject 的定义还纳入了分析器项目与规范测试项目,同时会把这些项目默认置为 Nullable enable——基线条目中常见的 string!、Task<T!>! 写法正是由此而来。
基线条目的实际形态:以 OpenApi 为例
docs/APIBaselines.md 中的示例比较抽象,对照仓库真实文件会更直观。例如 src/OpenApi/src/PublicAPI.Shipped.txt 中已发布的 API 条目形如:
#nullable enable
Microsoft.AspNetCore.OpenApi.IOpenApiDocumentProvider
Microsoft.AspNetCore.OpenApi.IOpenApiDocumentProvider.GetOpenApiDocumentAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<Microsoft.OpenApi.OpenApiDocument!>!
static Microsoft.Extensions.DependencyInjection.OpenApiServiceCollectionExtensions.AddOpenApi(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, string! documentName, System.Action<Microsoft.AspNetCore.OpenApi.OpenApiOptions!>! configureOptions) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
而 src/OpenApi/src/PublicAPI.Unshipped.txt 则展示了"未发布变更"的真实样例——主版本开发期间新加入的 API 就停在这里:
#nullable enable
Microsoft.AspNetCore.OpenApi.IAdditionalOpenApiDocumentNameResolver
Microsoft.AspNetCore.OpenApi.IAdditionalOpenApiDocumentNameResolver.ResolveDocumentNames() -> System.Collections.Generic.IEnumerable<string!>!
static Microsoft.Extensions.DependencyInjection.OpenApiServiceCollectionExtensions.AddOpenApiCore(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
此外,对多目标框架(multi-targeting)项目,分析器支持按 TFM 分目录存放基线。例如 src/DataProtection/DataProtection/src/PublicAPI/net11.0/PublicAPI.Shipped.txt 与同目录下的 netstandard2.0、net462 子目录各自维护一套基线,说明当不同目标框架的 API 表面不同时(如平台条件特性),可以按 TFM 独立管理。src/StaticAssets/src/PublicAPI.Shipped.txt 等单目标项目则直接使用项目根目录下的两个文件。
PublicAPI.Unshipped.txt 的三种变更写法
按 docs/APIBaselines.md,任何 API 变更都要在 PublicAPI.Unshipped.txt 中新增条目(注意:不是修改旧条目,因为旧条目可能已在 Shipped 文件中或属于本版本内更早的 Unshipped 条目)。
新增 API(New APIs)
为每个新 API 增加一条目。文档示例:
#nullable enable
Microsoft.AspNetCore.Builder.NewApplicationBuilder.New() -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
移除 API(Removed APIs)
被移除的 API 不能直接删掉旧条目,而要新增一条以 *REMOVED* 为前缀的条目来"抵消"原基线:
#nullable enable
*REMOVED*Microsoft.Builder.OldApplicationBuilder.New() -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
更新 API(Updated APIs)
API 签名变化(包括从源码结构看属于同一类型的"变为可空感知"变化)需要两条新条目:一条 *REMOVED* 删除旧签名,一条写入新签名。文档示例展示了一个接口属性从非空变为可空的情形:
#nullable enable
*REMOVED*Microsoft.AspNetCore.DataProtection.Infrastructure.IApplicationDiscriminator.Discriminator.get -> string!
Microsoft.AspNetCore.DataProtection.Infrastructure.IApplicationDiscriminator.Discriminator.get -> string?
添加和更新 API 的完整操作步骤
docs/APIBaselines.md 给出的标准流程是"让分析器报错、用快速修复补条目",而非手写文本:
- 如需要,把新项目加入
AspNetCore.sln及相关的*.slnf解决方案筛选文件(仓库根目录为 AspNetCore.slnx,各模块目录如 src/OpenApi/OpenApi.slnf 提供过滤入口)。 - 运行所在模块目录的
startvs.cmd,例如src/OpenApi/startvs.cmd(其内容即调用仓库根startvs.cmd并加载本模块的OpenApi.slnf)。 - 按 F6(或你习惯的构建方式)编译。
- 点击出现的 RS0016 等 API 基线诊断错误。
- 在编辑器中对下划线的符号右键,或点击其左侧的"快速修复"图标(快捷键
Ctrl+.亦可)。 - 选择 "Add Blah to public API" / "Fix all occurrences in … Solution"。
- 点击 Apply。
- 再次 F6,确认修复器没有漏项,或是否暴露出其他 RS00xx 错误(这并不罕见)。
- 如有其他问题,按需要修复或抑制;文档特别要求:抑制必须使用特性(attributes)而非全局
NoWarn或#pragma,以便在代码现场看清抑制理由,例如针对常见且无法修复的错误:
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
// 或
[SuppressMessage("ApiDesign", "RS0027:Public API with optional parameter(s) should have the most parameters amongst its public overloads.", Justification = "Required to maintain compatibility")]
从源码结构看,这一流程与仓库配置自洽:分析器以 Analyzer 身份参与编译,而 eng/targets/CSharp.Common.targets 中还专门设有 _RemovePublicApiAnalyzer / _RestorePublicApiAnalyzer 目标,在 Razor 编译前后临时移除/恢复该分析器,避免 Razor 源生成物干扰 API 基线的比对。
主版本发布后的基线更新
按 docs/APIBaselines.md,主版本发布后的基线"转正"工作由构建团队使用 .NET 仓库(dotnet/roslyn)scripts/PublicApi 目录下的脚本(或其 Arcade 后续方案)执行,核心动作是把 PublicAPI.Unshipped.txt 的内容**搬移(move)**进 PublicAPI.Shipped.txt。普通贡献者不需要也不应该手工做这一步——这正是"Shipped 文件除主版本发布后由构建团队修改外永不可改"规则的意义所在:保证 Shipped 文件忠实反映已发布版本,任何手工改动都可能掩盖真实发生的 API 断裂。
小结
PublicAPI.Shipped.txt只读(对日常开发而言),记录上一主版本已发布 API;PublicAPI.Unshipped.txt承载本版本全部新增(含*REMOVED*前缀的删除与更新抵消条目)。- 实现项目默认启用检查(见 eng/targets/CSharp.Common.targets),测试/非发布项目可用
<AddPublicApiAnalyzers>false</AddPublicApiAnalyzers>关闭;分析器版本由 eng/Versions.props 统一锁定。 - 新项目从 eng/PublicAPI.empty.txt 复制出两个基线文件,之后通过"编译报错 → 快速修复 Add to public API → 再次编译"的循环维护条目,抑制一律用带 Justification 的特性。
- 可参考的真实样例:src/OpenApi/src/PublicAPI.Shipped.txt、src/OpenApi/src/PublicAPI.Unshipped.txt,以及按 TFM 分目录的 src/DataProtection/DataProtection/src/PublicAPI/net11.0/PublicAPI.Shipped.txt。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00