mcpField
I
including-generated-files

including-generated-files

github.com/dotnet开发5.8k3.5k 次安装1 天前更新
代码仓库

关于 including-generated-files

一项面向 MSBuild 项目的开发技能,介绍如何将构建期间生成的文件加入构建流程,确保其参与输出或编译,并在清理时移除。

主要功能

  1. 01将生成文件注册到 MSBuild
  2. 02在编译前加入源文件
  3. 03为 Clean 目标跟踪文件
  4. 04按文件类型选择目标时机
  5. 05使用中间输出路径

适用场景

  1. 01在构建时编译生成的 C# 文件
  2. 02复制生成的配置或数据文件
  3. 03避免构建间残留过期生成文件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dotnet/skills --skill including-generated-files

安装后在 Claude Code 中输入/including-generated-files确认已加载。

详细介绍

将生成的文件包含到生成过程中

概述

生成过程中生成的文件通常会被生成过程忽略。这会导致一些令人困惑的结果,例如:

  • 生成的文件未包含在输出目录中
  • 生成的源文件未编译
  • Glob 无法捕获生成过程中创建的文件

这是由 MSBuild 的生成阶段工作方式导致的。

简要结论

对于生成过程中生成的代码文件,需要在生成这些文件的目标中,将其添加到 Compile 和 FileWrites 项组:

  <ItemGroup>
    <Compile Include="$(GeneratedFilePath)" />
    <FileWrites Include="$(GeneratedFilePath)" />
  </ItemGroup>

应将生成这些文件的目标挂接到 CoreCompile 和 BeforeCompile 目标之前:BeforeTargets="CoreCompile;BeforeCompile"

为什么生成的文件会被忽略

详细说明请参阅 How MSBuild Builds Projects。

评估阶段

MSBuild 读取项目,导入所有内容,创建属性,展开 Targets 之外 的项的 Glob,并设置生成过程。

执行阶段

MSBuild 使用提供的属性和项运行目标与任务来执行生成。

关键结论: 执行阶段生成的文件在评估阶段尚不存在,因此无法被找到。这对默认使用 Glob 匹配的文件影响尤其明显,例如源文件(.cs)。

解决方案:手动添加生成的文件

在生成过程中生成文件时,需要手动将其添加到生成过程中。具体方法取决于所生成文件的类型。

使用 $(IntermediateOutputPath) 作为生成文件的位置

始终使用 $(IntermediateOutputPath) 作为生成文件的基目录。不要对 obj\ 进行硬编码,也不要手动构造中间路径(例如 obj\$(Configuration)\$(TargetFramework)\)。在某些生成配置中,中间输出路径可能会被重定向到其他位置(例如共享输出目录、CI 环境)。使用 $(IntermediateOutputPath) 可确保目标无论实际路径如何都能正常工作。

始终将生成的文件添加到 FileWrites

每个生成的文件都应添加到 FileWrites 项组中。这样可确保 MSBuild 的 Clean 目标正确删除生成的文件。否则,生成的文件会在多次生成后不断累积,成为过期产物。

<ItemGroup>
  <FileWrites Include="$(IntermediateOutputPath)my-generated-file.xyz" />
</ItemGroup>
基本模式(非代码文件)

对于需要复制到输出目录的生成文件(配置文件、数据文件等),请在 BeforeBuild 之前将其添加到 Content 或 None 项中:

<Target Name="IncludeGeneratedFiles" BeforeTargets="BeforeBuild">
  
  

  <ItemGroup>
    <None Include="$(IntermediateOutputPath)my-generated-file.xyz" CopyToOutputDirectory="PreserveNewest"/>
    
    
    <None Include="$(IntermediateOutputPath)generated\*.xyz" CopyToOutputDirectory="PreserveNewest"/>

    
    <FileWrites Include="$(IntermediateOutputPath)my-generated-file.xyz" />
    <FileWrites Include="$(IntermediateOutputPath)generated\*.xyz" />
  </ItemGroup>
</Target>
对于生成的源文件(需要编译的代码)

如果生成的是需要编译的 .cs 文件,请使用 BeforeTargets="CoreCompile;BeforeCompile"。这是添加 Compile 项的正确时机——此时文件已生成,但编译器尚未运行。对于某些场景,使用 BeforeBuild 为时过早,而且可能无法与所有 SDK 功能可靠地配合使用。

<Target Name="IncludeGeneratedSourceFiles" BeforeTargets="CoreCompile;BeforeCompile">
  <PropertyGroup>
    <GeneratedCodeDir>$(IntermediateOutputPath)Generated\</GeneratedCodeDir>
    <GeneratedFilePath>$(GeneratedCodeDir)MyGeneratedFile.cs</GeneratedFilePath>
  </PropertyGroup>

  <MakeDir Directories="$(GeneratedCodeDir)" />

  

  <ItemGroup>
    <Compile Include="$(GeneratedFilePath)" />
    <FileWrites Include="$(GeneratedFilePath)" />
  </ItemGroup>
</Target>

注意:同时指定 CoreCompile 和 BeforeCompile,可确保目标在其中先执行的目标之前运行,从而无论生成过程如何自定义,都能保证可靠的执行顺序。

目标时机

请根据生成文件的类型选择 BeforeTargets 的值:

  • BeforeTargets="BeforeBuild" —— 对于添加到 None 或 Content 的非代码文件。适用于复制到输出目录的场景。
  • BeforeTargets="CoreCompile;BeforeCompile" —— 对于添加到 Compile 的生成源文件。确保文件在编译器运行之前包含在内。
  • BeforeTargets="AssignTargetPaths" —— 在 None 和 Content 项(以及其他项)转换为新项之前的“最后关口”。如果 BeforeBuild 时机过早,可将此作为备用方案。

Glob 匹配行为

Glob 的行为取决于 执行 Glob 的时间:

Glob 位置捕获的文件
目标外部仅在评估阶段(构建开始前)可见的文件
目标内部目标运行时可见的文件(如果时机合适,也可以捕获生成的文件)

这就是为什么该解决方案将 <ItemGroup> 放在 <Target> 内部——Glob 会在执行期间运行,此时生成的文件已经存在。

相关链接

评论

支持 Markdown