关于 eliminate-redundant-restores
Eliminate Redundant Restores 是用于查找构建管线和脚本中重复 .NET 与 MSBuild 包还原的技能。它会检查项目覆盖范围、还原输入和可用资产,再判断哪些后续步骤可以安全跳过还原。
主要功能
- 01盘点各调用中的还原行为
- 02检查项目与解决方案覆盖范围
- 03比较运行时和包还原输入
- 04核查响应文件中的还原标志
- 05在干净副本中验证变更
适用场景
- 01减少 CI 管线中的重复还原
- 02诊断多个构建步骤的还原输出
- 03检查 --no-restore 的安全用法
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill eliminate-redundant-restores安装后在 Claude Code 中输入/eliminate-redundant-restores确认已加载。
详细介绍
消除冗余还原
即使还原操作不执行任何实际更改,仍会评估每个项目并检查还原状态,因此在多步构建中,每次额外还原都会让大型代码库耗费数秒到数十秒。解决方法很容易出错:如果某一步添加了 --no-restore,但此前的还原并未涵盖该步骤,就会把缓慢的构建变成失败的构建。此技能会找出哪些调用执行了还原,确认哪些调用已由之前的还原覆盖,并且只修改这些调用。
适用场景
- 流水线或脚本运行多个
dotnet/msbuild步骤,日志显示其中不止一个步骤输出了还原信息 - 存在还原步骤,但后续步骤仍输出
Determining projects to restore... - 仓库中的
Directory.Build.rsp或当前 MSBuild 安装目录下的MSBuild.rsp包含-restore - 有人询问可以在哪些位置安全地添加
--no-restore或-restore:false - 添加
--no-restore后出现了NETSDK1004或NETSDK1047
不适用场景
- 首次还原速度慢(源延迟、包缓存缺失):这是缓存问题,而非冗余还原
- 所有调用中只有一次还原:无需去重
- 还原错误涉及版本、锁文件或审核:应修复还原本身
- 未经分析就问“为什么构建很慢”:先从
build-perf-baseline开始,再使用build-perf-diagnostics
输入
在工作目录中查找仓库输入;不要向用户询问路径。
- CI 定义(
*.yml、.github/workflows、azure-pipelines*.yml、Jenkinsfile)和构建脚本(build.cmd、build.sh、build.ps1、eng/) Directory.Build.rsp、Directory.Build.props/.targets、NuGet.config- 当前 MSBuild 可执行文件旁自动加载的
MSBuild.rsp,或通过@file显式传入的响应文件。仓库本地的MSBuild.rsp不会自动加载 - 如有,还原证据:步骤日志或二进制日志
工作流
步骤 1:确认还原重复发生,且值得移除
- 日志证据:每次调用都出现一次
Determining projects to restore...或All projects are up-to-date for restore. - 二进制日志证据:还原评估包含全局属性
MSBuildRestoreSessionId和ExcludeRestorePackageImports=true;目标性能摘要列出Restore,并显示 N 次调用。 - 量化每次无操作还原的耗时。如果只有一次还原,或重复还原的总耗时约为一秒,则报告“没有值得移除的内容”并停止,不做任何修改。
步骤 2:按执行顺序检查每次调用
| 调用 | 是否还原? |
|---|---|
dotnet build、test、publish、pack | 隐式还原,除非指定 --no-restore;同时检查继承的 rsp 标志 |
dotnet run | 在构建前隐式还原;在较旧的 SDK 上,即使指定 --no-restore,仍可能继承 rsp 中的还原设置。指定 --no-build 时,不会运行构建或还原 |
使用 --no-build 的 dotnet test、publish、pack | 不会隐式还原,但 rsp 中的 -restore 仍可能启用还原;添加 --no-restore -restore:false |
dotnet msbuild、msbuild.exe、CI MSBuild 任务 | 指定 -restore 或 -t:Restore 时会还原,包括从仓库的 Directory.Build.rsp 或安装级别的 MSBuild.rsp 继承的标志,即使某个步骤看起来只执行构建也是如此 |
dotnet restore、nuget restore、msbuild -t:Restore | 显式还原 |
| 启用了“还原包”选项的 CI 构建任务 | 检查其默认设置 |
从 .NET SDK 9.0.1xx 开始,--no-restore 会传递 -restore:false,以覆盖 rsp 标志;较旧的 SDK 即使跳过 CLI 的隐式还原,仍可能继承 -restore。只要 rsp 启用了还原,就应在 dotnet build、test、publish 或 pack 中使用与版本无关的 --no-restore -restore:false,即使同时指定了 --no-build 也是如此。dotnet msbuild 和 msbuild.exe 接受的是 -restore:false,而不是 --no-restore。
dotnet run 不支持 MSBuild 的 -restore:false 覆盖选项。在较旧的 SDK 上使用 rsp 时,应先使用 dotnet build --no-restore -restore:false 构建,然后使用 dotnet run --no-build 运行,并传入相同的项目、配置、框架和运行时参数(或直接执行生成的 DLL)。向 dotnet run 传递 -restore:false 并不能阻止还原。
步骤 3:逐次调用判断
保留首次还原。只有以下四项检查全部通过时,才跳过后续还原:
| 检查项 | 未通过时 |
|---|---|
项目集合相同。 还原会覆盖入口项目或解决方案,以及该项目的 ProjectReference 闭包。它不会覆盖另一个解决方案或 .slnf 中的项目、从其他路径构建的工具,或由协调 .proj 通过 MSBuild 任务构建的项目(对协调项目使用 -restore 不会还原这些项目,它们会因 NETSDK1004 而失败) | 在该步骤保留还原,或将该项目加入显式还原步骤 |
还原输入相同。 -r/--runtime、会生成 RID 的 -a/--arch、--os、--ucr/--use-current-runtime、TargetFramework(s)、RuntimeIdentifier(s)、PublishAot、包源(--source、--configfile、RestoreSources)、RestorePackagesPath,以及 PackageReference 条件会读取的任何 -p: 值。体系结构/OS 选项会与 SDK 的当前 RID 组合;--use-current-runtime 会选择该 RID。缺少 RID 目标会导致 NETSDK1047(Assets file ... doesn't have a target for 'net10.0/linux-x64') | 在显式还原中匹配有效 RID(例如,publish --os linux --arch x64 需要 restore -r linux-x64),或在后续步骤保留还原。即使没有 -r,通用还原也不会覆盖这些别名。不要仅仅因为 publish 是自包含的,就要求还原时设置 SelfContained=true:仅指定 RID 的还原足以满足普通 SDK 项目的需求。只有当仓库条件会改变包输入时,才匹配 Configuration 或 SelfContained |
可用的还原状态相同。 后续调用查找的位置必须存在较早生成的 project.assets.json 文件。匹配 --artifacts-path、UseArtifactsOutput/ArtifactsPath 和 MSBuildProjectExtensionsPath/BaseIntermediateOutputPath;其他作业或工作区不会自动共享这些文件 | 让显式还原的输出布局与 build/test 保持一致,或在后续工作区保留还原。对于 build --artifacts-path artifacts --no-restore,先使用 restore --artifacts-path artifacts;普通 obj/ 目录中的资产不适用于该布局 |
期间没有更改包。 生成或编辑项目、运行 dotnet add package、重写 Directory.Packages.props 或 NuGet.config 的步骤会使先前的还原失效 | 将显式还原移到该步骤之后 |
步骤 4:实施最小改动
- 执行一次包含所有还原输入的显式还原,然后在后续步骤使用
--no-restore、-restore:false或--no-build。 - 对同一解决方案执行多个
dotnet test步骤:先构建一次,然后对每个步骤执行dotnet test --no-build。如果 rsp 文件启用了还原,则添加--no-restore -restore:false,包括在 .NET 8 及更早版本中。单独使用--no-restore仍会重新构建。 - 保留自包含发布设置。
publish --no-build仍需要先前使用匹配的配置、框架、RID 和自包含输出完成构建;还原兼容并不能证明构建输出已存在。 - 将改动限制在 CI 和脚本中。在
Directory.Build.rsp中保留-restore,因为开发者依赖它;改为在每次调用时传入-restore:false。 - 切勿移除首次还原。如果它是
--locked-mode门禁,就保留它。 - 不要将静态图还原或包缓存纳入此次改动。
# One restore carries every input later steps use
dotnet restore MyApp.sln -r linux-x64
# Explicit overrides also work when an rsp enables restore on older SDKs
dotnet build MyApp.sln -c Release --no-restore -restore:false
dotnet test MyApp.sln -c Release --no-build --no-restore -restore:false
dotnet publish src/Api -c Release -r linux-x64 --no-restore -restore:false
# msbuild invocations need the explicit switch if an rsp file adds -restore
dotnet msbuild Packaging.proj -t:Pack -restore:false
步骤 5:验证并如实报告
- 在干净的临时副本中端到端运行已更改的管道或脚本(不含
obj/、不含project.assets.json)。从未还原过的项目只会在此处失败。YAML 语法或标志存在性检查无法验证还原覆盖范围或测试执行情况。如果无法运行,请说明“未验证”,并列出需要确认的步骤和项目。 - 成功证据:后续每个步骤均已通过;
Restore调用次数(binlog)或Determining projects to restore行数从 N 降至预期数量;如需声称耗时有所变化,则至少提供三次运行的前后中位数。 - 将还原失败(NETSDK1004、NETSDK1047)与生成失败分开报告,发生其中任一情况后,绝不能声称成功。
输出
说明哪些步骤执行了还原、由哪个更早的还原步骤覆盖、有哪些改动,以及哪些内容仍未验证。说明是否移除了冗余还原,或无需更改,同时保留必需的首次还原。可使用叙述或问题发现表,无需固定的表头或结论格式。
常见陷阱
| 陷阱 | 解决方案 |
|---|---|
为每条命令都添加 --no-restore | 按步骤 3 的检查逐步处理 |
未发现 rsp 会为“仅生成” msbuild 步骤添加 -restore | 得出某步骤不会执行还原的结论前,先搜索 *.rsp |
从 Directory.Build.rsp 中删除 -restore | 按 CI 调用覆盖;该 rsp 供开发者使用 |
编排器 .proj 已还原,但子项目未还原 | 显式还原每个子项目,或保留执行生成步骤时的还原 |
在通用还原后,使用 -r、体系结构/操作系统选项或 --use-current-runtime 发布 | 使用相同的有效运行时标识符还原,包括别名使用的 SDK 主机默认值 |
| 生成器步骤在还原后修改了 PackageReferences | 在生成器运行后还原 |