Skip to content

Fix XamlC MSBuild target incrementality for Debug builds#34742

Closed
davidnguyen-tech wants to merge 3 commits into
dotnet:net11.0from
davidnguyen-tech:fix/xamlc-incrementality
Closed

Fix XamlC MSBuild target incrementality for Debug builds#34742
davidnguyen-tech wants to merge 3 commits into
dotnet:net11.0from
davidnguyen-tech:fix/xamlc-incrementality

Conversation

@davidnguyen-tech
Copy link
Copy Markdown
Member

@davidnguyen-tech davidnguyen-tech commented Mar 30, 2026

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description

Fixes XamlC MSBuild target incrementality to avoid unnecessary re-runs on non-XAML C# changes, improving MAUI Android inner loop build performance.

Problem

The XamlC target uses Inputs="$(IntermediateOutputPath)$(TargetFileName)" (the compiled assembly DLL). Since any C# change rebuilds the assembly, XamlC re-runs even when no XAML files changed. In Debug mode (ValidateOnly=True), this wastes ~0.56s scanning and validating all XAML on every incremental build.

Solution

Split the XamlC target into two mutually exclusive targets based on _MauiXamlCValidateOnly:

Target Mode Inputs When it runs
XamlC Release Assembly + @(MauiXaml) When assembly or XAML changes
_XamlCValidateOnly Debug @(MauiXaml) only Only when XAML changes

The key insight: in Debug/ValidateOnly mode, XamlC only reads the assembly for validation — it never writes to it. So the assembly does not need to be an Input. When only C# files change (no XAML changes), the validate-only target correctly reports up-to-date and skips.

In Release mode, XamlC compiles XAML into IL and writes it back into the assembly, so the assembly must remain an Input to ensure correctness.

Tradeoff

In Debug mode, if a C# type referenced by XAML is removed/renamed without editing the XAML file, validation will not catch this until the XAML file is next modified or a clean build is done. This is an acceptable tradeoff because:

  • The C# compiler catches many related errors independently
  • Source generators provide additional compile-time checking
  • A clean build or any XAML edit will trigger full validation

Testing

  • Added CSharpOnlyChange_DoesNotTriggerXamlCInDebug test verifying the core fix
  • Added CSharpOnlyChange_DoesTriggerXamlCInRelease test verifying Release correctness
  • Updated NoXamlFiles test to verify both targets are skipped when no XAML files exist
  • All existing MSBuild incrementality tests continue to pass (20 tests: 18 passed, 2 pre-existing skips)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 30, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34742

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34742"

davidnguyen-tech and others added 3 commits March 30, 2026 19:38
Move the _MauiXamlCValidateOnly property computation from inside
the XamlC target body to a static PropertyGroup. This makes it
available at evaluation time for use in target Conditions.

The property depends only on static properties (Configuration,
_MauiForceXamlCForDebug, BuildingForLiveUnitTesting) so hoisting
is safe. No behavioral change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Split XamlC into two mutually exclusive targets:

- XamlC (Release): Inputs include both the assembly and @(MauiXaml).
  Runs when either changes. Required because Release mode compiles
  XAML into IL and writes it back into the assembly.

- _XamlCValidateOnly (Debug): Inputs include only @(MauiXaml).
  Skips when only C# files change. Safe because validate-only mode
  reads the assembly but never writes to it.

This saves ~0.5s on incremental Debug builds when only non-XAML
C# files change, improving Android inner loop performance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CSharpOnlyChange_DoesNotTriggerXamlCInDebug: verifies the core
  fix — adding a C# file in Debug mode does not re-run XamlC
- CSharpOnlyChange_DoesTriggerXamlCInRelease: verifies Release
  correctness — C#-only changes still trigger XamlC since the
  assembly is in Inputs
- Update NoXamlFiles to also verify _XamlCValidateOnly is skipped

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant