Loading...
Loading...
Guide for optimizing MSBuild incremental builds. Only activate in MSBuild/.NET build context. Use when builds are slower than expected on subsequent runs, when 'nothing changed but it rebuilds anyway', or when diagnosing why incremental builds are broken. Covers Inputs/Outputs on targets, FileWrites tracking, up-to-date checks, and diagnosing unnecessary rebuilds via binlog analysis.
npx skill4agent add dotnet/skills incremental-buildInputsOutputsInputsOutputsInputsOutputsIncrementalIncremental="false"InputsOutputs<!-- This target is incremental: skipped if Output is newer than all Inputs -->
<Target Name="Transform"
Inputs="@(TransformFiles)"
Outputs="@(TransformFiles->'$(OutputPath)%(Filename).out')">
<!-- work here -->
</Target>
<!-- This target always runs because it has no Inputs/Outputs -->
<Target Name="PrintMessage">
<Message Text="This runs every build" />
</Target>OutputsFileWritesdotnet clean@(Compile)InputsInputsOutputs$(Configuration)$(TargetFramework)project.assets.jsonResolveAssemblyReferencesCoreCompileVBCSCompilerdotnet build /bl:first.binlog
dotnet build /bl:second.binlogsecond.binlogdotnet msbuild second.binlog -noconlog -fl -flp:v=diag;logfile=second-full.log;performancesummarygrep 'Building target\|Target.*was not skipped' second-full.log"Building target 'X' completely""Building target 'X' incrementally""Skipping target 'X' because all output files are up-to-date"grep "is newer than output" second-full.logfirst.binlogsecond.binloggrep 'Target Performance Summary' -A 30 second-full.logFileWritesdotnet cleanFileWritesdotnet cleanFileWritesShareabledotnet cleanFileWrites<Target Name="MyGenerator" Inputs="..." Outputs="$(IntermediateOutputPath)generated.cs">
<!-- Generate the file -->
<WriteLinesToFile File="$(IntermediateOutputPath)generated.cs" Lines="@(GeneratedLines)" />
<!-- Register for clean -->
<ItemGroup>
<FileWrites Include="$(IntermediateOutputPath)generated.cs" />
</ItemGroup>
</Target>InputsOutputs<PropertyGroup>
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
</PropertyGroup>CopyToOutputDirectoryContentNoneCopyToOutputDirectory="PreserveNewest"<Target Name="GenerateConfig"
Inputs="$(MSBuildProjectFile);@(ConfigInput)"
Outputs="$(IntermediateOutputPath)config.generated.cs"
BeforeTargets="CoreCompile">
<!-- Generate file only if inputs changed -->
<WriteLinesToFile File="$(IntermediateOutputPath)config.generated.cs" Lines="..." />
<ItemGroup>
<FileWrites Include="$(IntermediateOutputPath)config.generated.cs" />
<Compile Include="$(IntermediateOutputPath)config.generated.cs" />
</ItemGroup>
</Target>Inputs$(MSBuildProjectFile)Inputs@(ConfigInput)Outputs$(IntermediateOutputPath)obj/BeforeTargets="CoreCompile"FileWritesdotnet cleanCompile<!-- BAD: No Inputs/Outputs — runs every build -->
<Target Name="BadTarget" BeforeTargets="CoreCompile">
<Exec Command="generate-code.exe" />
</Target>
<!-- BAD: Volatile output path — never finds previous output -->
<Target Name="BadTarget2"
Inputs="@(Compile)"
Outputs="$(OutputPath)gen_$([System.DateTime]::Now.Ticks).cs">
<Exec Command="generate-code.exe" />
</Target>
<!-- GOOD: Stable paths, registered outputs -->
<Target Name="GoodTarget"
Inputs="@(Compile)"
Outputs="$(IntermediateOutputPath)generated.cs"
BeforeTargets="CoreCompile">
<Exec Command="generate-code.exe -o $(IntermediateOutputPath)generated.cs" />
<ItemGroup>
<FileWrites Include="$(IntermediateOutputPath)generated.cs" />
<Compile Include="$(IntermediateOutputPath)generated.cs" />
</ItemGroup>
</Target>/clp:PerformanceSummarydotnet build /clp:PerformanceSummary/pp:preprocess.xmldotnet msbuild /pp:preprocess.xmlInputsOutputsPerformanceSummary/ppInputsOutputs$(IntermediateOutputPath)obj/FileWritesdotnet cleanReturnsOutputsOutputsReturns<!-- Outputs: affects incremental check AND return value -->
<Target Name="GetFiles" Outputs="@(DiscoveredFiles)">...</Target>
<!-- Returns: only affects return value, no incremental check -->
<Target Name="GetFiles" Returns="@(DiscoveredFiles)">...</Target>