Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
In any given build, files that get generated during the build behave differently from static files (such as source files). For this reason, it's important to understand How MSBuild Builds Projects. The two phases are the evaluation phase and the execution phase. During the evaluation phase, MSBuild reads your project, imports everything, creates properties, expands globs for items, and sets up the build process. During the execution phase, MSBuild performs the build by running targets and tasks with the data it parsed during the evaluation phase.
Files generated during execution don't exist during the evaluation phase, therefore they aren't included in the build process. To solve this problem, you must manually add the generated files into the build process. The recommended way to do this is by adding the new file to the Content
or None
items before the BeforeBuild
target, as in the following example:
<Target Name="MyTarget" BeforeTargets="BeforeBuild">
<!-- Some logic that generates your file goes here -->
<!-- Generated files should be placed in $(IntermediateOutputPath) -->
<WriteLinesToFile
File="$(IntermediateOutputPath)GeneratedFile.cs"
Lines='enum MyEnum { A, B }'
Overwrite="true" />
<ItemGroup>
<Compile Include="$(IntermediateOutputPath)GeneratedFile.cs" />
</ItemGroup>
<ItemGroup>
<!-- If your generated file was placed in `obj\` -->
<None Include="$(IntermediateOutputPath)GeneratedFile.cs" TargetPath="GeneratedFile.cs" CopyToOutputDirectory="PreserveNewest"/>
<!-- If you know exactly where that file is going to be, you can hard code the path. -->
<None Include="some\specific\path\my-generatedfile" CopyToOutputDirectory="PreserveNewest"/>
<!-- If you want to capture "all files of a certain type", you can glob like so. -->
<None Include="some\specific\path\*.xyz" CopyToOutputDirectory="PreserveNewest"/>
<None Include="some\specific\path\*.*" CopyToOutputDirectory="PreserveNewest"/>
</ItemGroup>
</Target>
<Target Name="CleanGeneratedCode" AfterTargets="CoreClean">
<Delete Files="$(IntermediateOutputPath)GeneratedFile.cs" />
</Target>
Adding your generated file to None
or Content
is sufficient for the build process to see it. You also want to ensure it gets added at the right time. Ideally, your target runs before BeforeBuild
. AssignTargetPaths
is another possible target, as it is the final opportunity to modify None
and Content
items (among others) before they are transformed into new items. See Common Item Types.
Copy the above, paste it into a file, and call it buildcodegen.targets
. Then, run dotnet new console
, import the file, and build it to see how it works.
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="buildcodegen.targets"/>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Run msbuild.exe and look at the output to verify that your file was generated and copied to the output folder. You can use ildasm.exe to confirm that your output binaries include the generated code MyEnum
:
ildasm CodeGen.dll
Next steps
This example could be improved to support more realistic use cases. For example, to support incremental builds when the generated code depends on an input file, Inputs
and Outputs
should be provided to the target. Such a target would only regenerate the file if the date of the input file or files is more recent than the output file. Often when customizing for code generation, it's recommended to create a custom task. See Create a custom task for code generation.