Skip to content

Instantly share code, notes, and snippets.

@baronfel
Created September 9, 2026 16:07
Show Gist options
  • Select an option

  • Save baronfel/112bdb2701ea0c83a16db7ff5caa6473 to your computer and use it in GitHub Desktop.

Select an option

Save baronfel/112bdb2701ea0c83a16db7ff5caa6473 to your computer and use it in GitHub Desktop.
Current state of MSBuild target input/output inference

Target input/output inference in MSBuild today

MSBuild has mature incremental execution from explicit target inputs and outputs, but almost no automatic discovery of those inputs and outputs. It is best described as shallow, declarative inference, not dependency inference comparable to Ninja or Bazel.

Capability Current support
Infer a target's inputs/outputs by inspecting its tasks No
Skip a target using declared Inputs/Outputs Yes, using file timestamps
Partially execute a target for changed items Yes, when a one-to-one item transform can be recognized
Reconstruct items/properties when a target is skipped Yes, when derivable from project XML
Infer arbitrary custom-task results No
Persist inferred target state between builds No
Detect changes to the input list since the previous build No
Content/hash-based dependency checking No

What MSBuild does infer

Given:

<Target Name="Compile"
        Inputs="@(Source)"
        Outputs="@(Source->'obj\%(Filename).obj')">
    ...
</Target>

MSBuild expands the explicitly declared expressions and compares timestamps:

  • All outputs current: skip and perform output inference.
  • Some mapped outputs stale: execute against only those input items.
  • All mapped outputs stale: full execution.
  • No recognizable mapping: compare every input against the oldest output and either fully execute or fully skip.

Partial builds require a structural correlation through the same item vector. Input transforms, unrelated output item lists, literal outputs, and mismatched list lengths generally degrade to all-or-nothing checking.

What "output inference" actually means

When a target is skipped, MSBuild walks its body without executing ordinary tasks:

  • <ItemGroup> and <PropertyGroup> operations are replayed.
  • Conditions and expressions are evaluated.
  • Task <Output> elements can be inferred only when their TaskParameter names a parameter explicitly supplied to that same task invocation.

For example, this is inferable:

<MyTask DestinationFiles="@(Source->'obj\%(Filename).obj')">
    <Output TaskParameter="DestinationFiles"
            ItemName="ProducedFiles" />
</MyTask>

DestinationFiles is already known from the XML. But this is not inferable if GeneratedFiles is computed inside the task:

<MyTask Sources="@(Source)">
    <Output TaskParameter="GeneratedFiles"
            ItemName="ProducedFiles" />
</MyTask>

MSBuild does not instantiate or inspect the task during inference, so it cannot know GeneratedFiles.

For a partial build, the engine effectively processes the target twice:

  1. Infer state for the up-to-date input subset.
  2. Execute tasks for the stale input subset.
  3. Merge both scopes, with actual execution results taking precedence.

Important boundaries

  • Inputs and Outputs are fundamentally file timestamp declarations, not a semantic dependency graph.
  • Returns controls what a target returns to callers; it does not participate in up-to-date analysis. Without Returns, Outputs also acts as the returned item expression.
  • Inferred items and properties are reconstructed in memory on every build; they are not cached on disk.
  • MSBuild only considers the currently evaluated input list. Adding or removing an item since the previous build is not inherently detected unless that affects a declared timestamp input, such as the project file or a manifest.
  • Task-level incrementality--Copy, Csc, ResolveAssemblyReference, custom task caches, IIncrementalTask, and so on--is separate and opaque to target-level inference.
  • If Inputs evaluates empty, or declared Outputs evaluates empty, the skip behavior can be surprising; authors must ensure those expressions form a reliable contract.

The practical assessment is: strong support for explicitly modeled file transforms, limited syntactic reconstruction of skipped-target state, and essentially zero automatic inference of the true dependency graph.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment