including-generated-files

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Including Generated Files Into Your Build

将生成的文件纳入构建流程

Overview

概述

Files generated during the build are generally ignored by the build process. This leads to confusing results such as:
  • Generated files not being included in the output directory
  • Generated source files not being compiled
  • Globs not capturing files created during the build
This happens because of how MSBuild's build phases work.
构建过程中生成的文件通常会被构建流程忽略,这会导致一些令人困惑的结果:
  • 生成的文件未被复制到输出目录
  • 生成的源文件未被编译
  • 通配符无法捕获构建过程中创建的文件
这是由MSBuild的构建阶段工作方式导致的。

Quick Takeaway

核心要点

For code files generated during the build - we need to add those to
Compile
and
FileWrites
item groups within the target generating the file(s):
xml
  <ItemGroup>
    <Compile Include="$(GeneratedFilePath)" />
    <FileWrites Include="$(GeneratedFilePath)" />
  </ItemGroup>
The target generating the file(s) should be hooked before CoreCompile and BeforeCompile targets -
BeforeTargets="CoreCompile;BeforeCompile"
对于构建过程中生成的代码文件,我们需要在生成文件的Target内将其添加到
Compile
FileWrites
项组中:
xml
  <ItemGroup>
    <Compile Include="$(GeneratedFilePath)" />
    <FileWrites Include="$(GeneratedFilePath)" />
  </ItemGroup>
生成文件的Target应设置为在CoreCompile和BeforeCompile目标之前执行 -
BeforeTargets="CoreCompile;BeforeCompile"

Why Generated Files Are Ignored

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

For detailed explanation, see How MSBuild Builds Projects.
如需详细解释,请参阅MSBuild项目构建方式

Evaluation Phase

评估阶段

MSBuild reads your project, imports everything, creates Properties, expands globs for Items outside of Targets, and sets up the build process.
MSBuild会读取项目、导入所有内容、创建Properties、展开Target外部的Items通配符,并设置构建流程。

Execution Phase

执行阶段

MSBuild runs Targets & Tasks with the provided Properties & Items to perform the build.
Key Takeaway: Files generated during execution don't exist during evaluation, therefore they aren't found. This particularly affects files that are globbed by default, such as source files (
.cs
).
MSBuild使用提供的Properties和Items运行Targets与Tasks以执行构建操作。
核心结论: 执行阶段生成的文件在评估阶段并不存在,因此无法被检测到。这尤其会影响那些默认使用通配符匹配的文件,例如源文件(
.cs
)。

Solution: Manually Add Generated Files

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

When files are generated during the build, manually add them into the build process. The approach depends on the type of file being generated.
当文件在构建过程中生成时,需要手动将其纳入构建流程。具体方法取决于生成文件的类型。

Use
$(IntermediateOutputPath)
for Generated File Location

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

Always use
$(IntermediateOutputPath)
as the base directory for generated files. Do not hardcode
obj\
or construct the intermediary path manually (e.g.,
obj\$(Configuration)\$(TargetFramework)\
). The intermediate output path can be redirected to a different location in some build configurations (e.g., shared output directories, CI environments). Using
$(IntermediateOutputPath)
ensures your target works correctly regardless of the actual path.
始终使用
$(IntermediateOutputPath)
作为生成文件的基础目录。请勿硬编码
obj\
或手动构造中间路径(例如
obj\$(Configuration)\$(TargetFramework)\
)。在某些构建配置中(例如共享输出目录、CI环境),中间输出路径可能会被重定向到其他位置。使用
$(IntermediateOutputPath)
可确保你的Target在任何实际路径下都能正常工作。

Always Add Generated Files to
FileWrites

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

Every generated file should be added to the
FileWrites
item group. This ensures that MSBuild's
Clean
target properly removes your generated files. Without this, generated files will accumulate as stale artifacts across builds.
xml
<ItemGroup>
  <FileWrites Include="$(IntermediateOutputPath)my-generated-file.xyz" />
</ItemGroup>
每个生成的文件都应添加到
FileWrites
项组中。这可确保MSBuild的
Clean
Target能正确删除生成的文件。如果不这样做,生成的文件会作为陈旧工件在多次构建中累积。
xml
<ItemGroup>
  <FileWrites Include="$(IntermediateOutputPath)my-generated-file.xyz" />
</ItemGroup>

Basic Pattern (Non-Code Files)

基础模式(非代码文件)

For generated files that need to be copied to output (config files, data files, etc.), add them to
Content
or
None
items before
BeforeBuild
:
xml
<Target Name="IncludeGeneratedFiles" BeforeTargets="BeforeBuild">
  
  <!-- Your logic that generates files goes here -->

  <ItemGroup>
    <None Include="$(IntermediateOutputPath)my-generated-file.xyz" CopyToOutputDirectory="PreserveNewest"/>
    
    <!-- Capture all files of a certain type with a glob -->
    <None Include="$(IntermediateOutputPath)generated\*.xyz" CopyToOutputDirectory="PreserveNewest"/>

    <!-- Register generated files for proper cleanup -->
    <FileWrites Include="$(IntermediateOutputPath)my-generated-file.xyz" />
    <FileWrites Include="$(IntermediateOutputPath)generated\*.xyz" />
  </ItemGroup>
</Target>
对于需要复制到输出目录的生成文件(配置文件、数据文件等),请在
BeforeBuild
之前将其添加到
Content
None
项中:
xml
<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>

For Generated Source Files (Code That Needs Compilation)

针对生成的源文件(需要编译的代码)

If you're generating
.cs
files that need to be compiled, use
BeforeTargets="CoreCompile;BeforeCompile"
. This is the correct timing for adding
Compile
items — it runs late enough that the file generation has occurred, but before the compiler runs. Using
BeforeBuild
is too early for some scenarios and may not work reliably with all SDK features.
xml
<Target Name="IncludeGeneratedSourceFiles" BeforeTargets="CoreCompile;BeforeCompile">
  <PropertyGroup>
    <GeneratedCodeDir>$(IntermediateOutputPath)Generated\</GeneratedCodeDir>
    <GeneratedFilePath>$(GeneratedCodeDir)MyGeneratedFile.cs</GeneratedFilePath>
  </PropertyGroup>

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

  <!-- Your logic that generates the .cs file goes here -->

  <ItemGroup>
    <Compile Include="$(GeneratedFilePath)" />
    <FileWrites Include="$(GeneratedFilePath)" />
  </ItemGroup>
</Target>
Note: Specifying both
CoreCompile
and
BeforeCompile
ensures the target runs before whichever target comes first, providing robust ordering regardless of customizations in the build.
如果你生成的
.cs
文件需要被编译,请使用**
BeforeTargets="CoreCompile;BeforeCompile"
**。这是添加
Compile
项的正确时机——它的执行时间足够晚,确保文件已生成,但又在编译器运行之前。在某些场景下使用
BeforeBuild
时机过早,可能无法与所有SDK功能可靠兼容。
xml
<Target Name="IncludeGeneratedSourceFiles" BeforeTargets="CoreCompile;BeforeCompile">
  <PropertyGroup>
    <GeneratedCodeDir>$(IntermediateOutputPath)Generated\</GeneratedCodeDir>
    <GeneratedFilePath>$(GeneratedCodeDir)MyGeneratedFile.cs</GeneratedFilePath>
  </PropertyGroup>

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

  <!-- 此处添加生成.cs文件的逻辑 -->

  <ItemGroup>
    <Compile Include="$(GeneratedFilePath)" />
    <FileWrites Include="$(GeneratedFilePath)" />
  </ItemGroup>
</Target>
注意:同时指定
CoreCompile
BeforeCompile
可确保Target在最先执行的那个目标之前运行,无论构建中的自定义配置如何,都能提供可靠的执行顺序。

Target Timing

Target时机选择

Choose the
BeforeTargets
value based on the type of file being generated:
  • BeforeTargets="BeforeBuild"
    — For non-code files added to
    None
    or
    Content
    . Runs early enough for copy-to-output scenarios.
  • BeforeTargets="CoreCompile;BeforeCompile"
    — For generated source files added to
    Compile
    . Ensures the file is included before the compiler runs.
  • BeforeTargets="AssignTargetPaths"
    — The "final stop" before
    None
    and
    Content
    items (among others) are transformed into new items. Use as a fallback if
    BeforeBuild
    is too early.
根据生成文件的类型选择
BeforeTargets
的值:
  • BeforeTargets="BeforeBuild"
    — 适用于添加到
    None
    Content
    的非代码文件。执行时机足够早,可满足复制到输出目录的场景。
  • BeforeTargets="CoreCompile;BeforeCompile"
    — 适用于添加到
    Compile
    的生成源文件。确保文件在编译器运行之前被纳入构建。
  • BeforeTargets="AssignTargetPaths"
    — 这是
    None
    Content
    等项被转换为新项之前的"最后时机"。如果
    BeforeBuild
    时机过早,可将此作为备选。

Globbing Behavior

通配符行为

Globs behave according to when the glob took place:
Glob LocationFiles Captured
Outside of a targetOnly files visible during Evaluation phase (before build starts)
Inside of a targetFiles visible when the target runs (can capture generated files if timed correctly)
This is why the solution places the
<ItemGroup>
inside a
<Target>
- the glob runs during execution when the generated files exist.
通配符的行为取决于通配符执行的时机
通配符位置捕获的文件
Target外部仅捕获评估阶段(构建开始前)可见的文件
Target内部捕获Target运行时可见的文件(如果时机正确,可捕获生成的文件)
这就是为什么解决方案将
<ItemGroup>
放在
<Target>
内部的原因——通配符在执行阶段运行,此时生成的文件已经存在。

Relevant Links

相关链接