其他
MSBuild 构建出现文件占用/已存在/输出被覆盖时…
当多项目或多目标框架构建出现间歇性失败、文件被占用、NuGet 还原报 'Cannot create a file when that file already exists' 或输出丢失/被覆盖时,用 binlog 分析各项目评估的 OutputPath/IntermediateOutputPath 是否冲突并修复。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 47/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 47/100 ×0.15
适用场景
MSBuild 构建出现文件占用/已存在通用
解决思路
- 出现 'Cannot create a file when that file already exists'(NuGet 还原)、'The process cannot access the file because it is being used by another process'、重试即成功的间歇性失败、输出文件丢失或被覆盖时,立即启用本流程。
- 先用 binlog-generation 技能生成符合命名规范的二进制日志(.binlog)。
- 不要用 cat、head、strings 或直接读取 .binlog(二进制格式),只通过 binlog MCP 工具查询;若不确定可用工具,先调用 tools/list。
- 边查边总结结论,证据足够就停止调查并给出结论,不要耗尽全部时间。
- 用 MCP 的 overview 和 projects 工具了解构建全貌并列出所有参与项目。
- 用 MCP 的 evaluations 和 evaluation_global_properties 工具找出每个项目的所有评估,关注同一项目的多次评估(多目标或多配置)以及评估间不同的全局属性(TargetFramework、Configuration、RuntimeIdentifier、SolutionFileName、PublishReadyToRun 等)。
- 用 MCP 的 properties 工具查询每个项目评估的 OutputPath、IntermediateOutputPath、BaseOutputPath、BaseIntermediateOutputPath。
- 若可用,用 MCP 的 double_writes 工具直接检测被多个项目实例写入的文件。
- 对比所有评估的 OutputPath 与 IntermediateOutputPath:1) 归一化为绝对路径并统一分隔符;2) 按路径分组;3) 排除 BuildProjectReferences=false 的 P2P 查询实例(仅对 OutputPath 还要排除 MSBuildRestoreSessionId 的还原评估);4) 任何剩余评估数大于 1 的分组即为冲突并报告。
- MCP 不可用时回退到文本日志:运行 dotnet msbuild build.binlog -noconlog -fl -flp:v=diag;logfile=full.log。
- 在文本日志中统计每个项目的评估次数:grep 'Evaluation started' full.log | grep -oiE '"[^"]+\.[a-z]+proj"' | sort | uniq -c(用带引号的完整路径区分同名项目并容忍空格;计数 ≥2 表示被多次评估)。
- 在文本日志中提取输出路径:grep -iE 'OutputPath[[:space:]]*=|IntermediateOutputPath[[:space:]]*=|BaseOutputPath[[:space:]]*=|BaseIntermediateOutputPath[[:space:]]*=' full.log | sort -u,或直接查询:dotnet msbuild MyProject.csproj -getProperty:OutputPath(以及 IntermediateOutputPath、BaseIntermediateOutputPath)。
- 在文本日志中区分全局属性:grep -iE 'TargetFramework|Configuration|Platform|RuntimeIdentifier|SolutionFileName|PublishReadyToRun' full.log,并对照全局属性表判断哪些影响路径、哪些只是分叉出冗余实例。
- 可选佐证:grep 'Target "CopyFilesToOutputDirectory"' full.log 与 grep 'SkipUnchangedFiles' full.log 可显示第二个实例写入(或跳过被掩盖的写入)同一路径;CoreCompile 耗时很长 vs 约 0 ms 可区分真实构建与冗余实例。
- 修复:多目标但路径不含 TFM 时,将 OutputPath 改为 bin\$(Configuration)\$(TargetFramework)\,或启用 AppendTargetFrameworkToOutputPath=true 与 AppendTargetFrameworkToIntermediateOutputPath=true。
- 修复:多项目显式共享 BaseOutputPath/BaseIntermediateOutputPath 时(AppendTargetFramework 也无效,因为 project.assets.json 等 NuGet 文件直接写入中间路径),必须让每个项目有唯一的 BaseIntermediateOutputPath(如 ..\obj\ProjectA\、..\obj\ProjectB\),或直接用 SDK 默认(obj 在各项目目录内)。
- 修复:多 RuntimeIdentifier 构建冲突时,设置 AppendRuntimeIdentifierToOutputPath=true。
- 修复:同一构建中多个解决方案构建同一项目时,对比各评估的 SolutionFileName 与 CurrentSolutionConfigurationContents 确认;可选方案:1) 合并解决方案使每个项目只被一个解决方案构建;2) 用不同 Configuration 使输出路径不同;3) 用解决方案筛选器或条件包含排除重复项目。
- 修复:额外全局属性(如 PublishReadyToRun=false)造成冗余实例时,1) 找到注入该属性的父目标/任务并阻止其传给不需要的项目;2) 在 ProjectReference 上用 RemoveGlobalProperties="PublishReadyToRun" 剥离;3) 仅对真正使用该属性的项目(如可执行项目而非类库)设置该属性。
- 修复:显式 <MSBuild> Build/Publish 带额外全局属性时——同项目内(publish-on-build)无法用 RemoveGlobalProperties,应把标志设为静态(非全局)属性并通过 DependsOnTargets/CallTarget 在同一实例内运行,并用防循环守卫(如 _PublishWasInvokedDirectly 条件);跨项目时消费者不应以路径中性全局属性分叉生产者,应让生产者自身构建时发布,消费者只做排序并读取其输出(ProjectReference 加 ReferenceOutputAssembly="false",不再调用 Publish)。
- 修复:ProjectReference 上 SetTargetFramework 对单目标项目重复注入其自身 TFM(路径中性、导致同一项目被构建两次)时,直接删除该冗余 SetTargetFramework。
- SetTargetFramework 的合法用法(非冲突):引用多目标项目并指定某个 TFM;把单目标项目覆盖为不同 TFM(路径随之改变);框架不兼容引用时设置 SkipGetTargetFrameworkProperties="true" 与 ReferenceOutputAssembly="false",并额外用 SetTargetFramework="TargetFramework=<tfm>" 或 UndefineProperties="TargetFramework" 之一(不可同时用)防止继承的 TargetFramework 全局属性流入。
- 修复后清理并重新构建验证;清理仓库时保留 binlog 文件(参见 binlog-generation 技能的 Cleaning the Repository 一节)。
适用边界 · 注意事项
- 与 MSBuild 无关的文件访问错误(操作系统级文件锁)。
- 单项目、单目标框架(TFM)的构建。
- 非 MSBuild 的构建系统。