Skip to content

Fix design-time XAML compile items and defer to WinUI's own target - #826

Merged
Nikola Metulev (nmetulev) merged 3 commits into
mainfrom
azchohfi-xaml-designtime-target-guard
Sep 9, 2026
Merged

Nikola Metulev (nmetulev) merged 3 commits into
mainfrom
azchohfi-xaml-designtime-target-guard

Conversation

@azchohfi

@azchohfi Alexandre Zollinger Chohfi (azchohfi) commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Outside Visual Studio (VS Code / C# Dev Kit) the XAML markup compiler never runs at design time, so the C# language service cannot see the generated code-behind and reports false errors on InitializeComponent and every x:Name field. This package worked around that by globbing every .cs file under obj\$(Configuration)\$(TargetFramework).

That glob has two problems:

  • It omits $(Platform). Real WinUI apps set <Platforms>x86;x64;ARM64</Platforms> and build x64, so their intermediates land in obj\x64\Debug\.... The workaround surfaced nothing at all there, which means it has been silently doing nothing for the most common WinUI configuration.
  • Where it did match, it added duplicates. It re-added the SDK's AssemblyInfo.cs by absolute path alongside the SDK's own relative entry. Roslyn dedupes that by path, so it is invisible in the IDE, but csc would not.

Separately, WinUI is shipping this same fix as its own IncludeGeneratedXamlCompileItemsForDesignTime target. It is already in Microsoft.WindowsAppSDK.WinUI 3.0.0-experimental.260818.21, with a 2.x servicing cherry-pick. We need to not fight it.

Adopt the platform's approach. The workaround becomes a target that mirrors WinUI's: a narrow *.g.cs / *.g.i.cs glob rooted at $(IntermediateOutputPath) (which already accounts for $(Platform) and any custom BaseIntermediateOutputPath), the same exclude set for source-generator output, intermediate XAML, and the SDK's AssemblyInfo / AssemblyAttributes / GlobalUsings files, and the same BuildingInsideVisualStudio and UsingMicrosoftNETSdk guards.

Stand down when the platform already has it. MSBuild has no "is this target defined" test, so the check probes the file that ships the target, via $(XamlCompilerPropsAndTargetsDirectory) (set by WinUI's own Microsoft.WinUI.props, so it resolves for direct and transitive references alike). This needs no version comparison, which matters because a servicing cherry-pick has to be detected exactly like a newer feature release.

Worth flagging for review: detection is an optimization, not the safety net. Our package sorts before Microsoft.WindowsAppSDK.WinUI in NuGet import order, so our target often runs first and detection cannot help. What actually makes the two safe together is that both excludes end with @(Compile), so whichever runs second contributes nothing. Verified in both import orders. If detection ever silently breaks (say WinUI renames or relocates the target), the result is a redundant no-op, not a duplicate.

Usage Example

EnableWinUIDesignTimeGeneratedXamlCompileItems=false turns off the target added here:

<PropertyGroup>
  <EnableWinUIDesignTimeGeneratedXamlCompileItems>false</EnableWinUIDesignTimeGeneratedXamlCompileItems>
</PropertyGroup>

The name is WinUI's, deliberately. Their 2.x servicing build gates their target on the same switch, so on that build one setting disables both. Their 3.0 build ships the target ungated, so there this only turns off the winapp fallback and the platform still contributes its items. Suppressing the platform's target is deliberately not attempted.

Related Issue

N/A. The underlying language-service behavior is tracked upstream at microsoft/vscode-dotnettools#1018, which the targets file already links.

Type of Change

  • 🐛 Bug fix
  • 🧪 Test update

Checklist

  • New tests added for new functionality (if applicable)
  • Tested locally on Windows

Screenshots / Demo

Measured in VS Code with C# Dev Kit against a real WinUI 3 app (WindowsAppSDK 1.8) consuming this package built by dotnet pack. "Platform target" means WinUI's IncludeGeneratedXamlCompileItemsForDesignTime, injected verbatim from the shipped 3.0.0-experimental package to simulate a newer WinUI.

Platform target winapp fix Errors reported
no none 7 false errors (InitializeComponent, AppTitleBar, RichInputBox, ...)
no this PR 0
no current shipped 0
yes this PR 0, no conflict
yes current shipped 0

So there is no user-visible conflict either way today. The regression this PR actually fixes shows up when you ask for the design-time @(Compile) set at Platform=x64:

winapp fix Generated files surfaced at Platform=x64
current shipped 0
this PR 5 (App.g.cs, MainWindow.g.cs, XamlTypeInfo.g.cs, App.g.i.cs, MainWindow.g.i.cs)

Additional Notes

13 new Pester tests in src/winapp-NuGet/tests/NuGet.Tests.ps1 cover the behavior, including the Platform=x64 regression, each individual exclude, the stand-down path against a stubbed newer WinUI, and the "never contributes a file already in @(Compile)" property that keeps the two targets compatible. They are hermetic and need no build output.

Validation: 54/54 in that suite, plus a clean scripts/build-cli.ps1 run. No user-facing docs cover design-time or IntelliSense behavior today, so none needed updating.

The one failing CI test, Mp4SinkWriterEncoderTests.Mp4SinkWriterEncoder_RealEncoderCoversValidationAndSuccessfulComplete, is unrelated. It drives the real Media Foundation H.264 sink writer and failed with MF_E_SINK_NO_SAMPLES_PROCESSED (0xC00D4A44), meaning the encoder accepted its single 64x64 frame but the sink emitted nothing before Complete(). This branch changes only a packaged .targets file and a Pester script, neither of which is part of WinApp.Cli.Tests.dll.

AI Description

This section is auto-generated by AI when the PR is opened or updated. To opt out, delete this entire section including the marker comments.

Outside Visual Studio the XAML markup compiler never runs at design time, so
the C# language service cannot see the generated code-behind and reports false
errors on InitializeComponent and every x:Name field. This package worked
around that by globbing every .cs file under obj\$(Configuration)\$(TargetFramework).

That glob omits $(Platform), so it surfaced nothing at all for the x64 builds
real WinUI apps use, and where it did match it re-added the SDK's AssemblyInfo
file by absolute path alongside the SDK's own relative entry.

Replace it with a target that mirrors the IncludeGeneratedXamlCompileItemsForDesignTime
target WinUI is shipping: a narrow *.g.cs / *.g.i.cs glob rooted at
$(IntermediateOutputPath), the same exclude set, and the same non-VS and
SDK-style guards. Because the excludes end with @(Compile), whichever of the
two targets runs second contributes nothing, so the two cannot conflict in
either import order.

Also stand down entirely when the referenced WinUI already provides the target.
MSBuild has no "is this target defined" test, so probe the file that ships it
via $(XamlCompilerPropsAndTargetsDirectory). That needs no version comparison,
so a servicing branch that cherry-picks the target is detected the same as a
newer feature release. EnableWinUIDesignTimeGeneratedXamlCompileItems=false is
honored so one setting disables both.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f27fcd4e-9c6e-4583-88a6-c563c8a9f492
Copilot AI balanced review requested due to automatic review settings September 8, 2026 23:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The documented shared opt-out does not disable WinUI’s current upstream target.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Fixes design-time XAML code-behind discovery for platform-specific WinUI builds while avoiding duplicate compile items and deferring to WinUI’s target.

Changes:

  • Uses IntermediateOutputPath and narrow generated-code globs.
  • Adds compatibility guards and 13 regression tests.
File summaries
File Description
src/winapp-NuGet/build/Microsoft.Windows.SDK.BuildTools.WinApp.targets Implements guarded design-time compile-item discovery.
src/winapp-NuGet/tests/NuGet.Tests.ps1 Tests platform paths, exclusions, guards, and deduplication.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/winapp-NuGet/build/Microsoft.Windows.SDK.BuildTools.WinApp.targets Outdated
@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

⏳ Build in progress — metrics below are from a previous commit and will update when the current build finishes.

Build Metrics Report

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 43.75 MB 43.75 MB ✅ 0.0 KB (0.00%)
CLI (x64) 43.74 MB 43.74 MB ✅ 0.0 KB (0.00%)
MSIX (ARM64) 18.11 MB 18.11 MB 📈 +0.1 KB (+0.00%)
MSIX (x64) 19.21 MB 19.21 MB 📉 -0.5 KB (-0.00%)
NPM Package 37.69 MB 37.69 MB 📈 +0.9 KB (+0.00%)
NuGet Package 37.82 MB 37.82 MB 📈 +3.1 KB (+0.01%)

Test Results

✅ 4786 passed, 5 skipped out of 4791 tests in 932.5s (+129.4s vs. baseline)

Test Coverage

✅ 89% line coverage, 82.3% branch coverage · ✅ no change vs. baseline

CLI Startup Time

61ms median (x64, winapp --version) · ✅ no change vs. baseline

Try This Build

Installs the MSIX for your architecture, replacing any previously installed build. Needs the GitHub CLI — the command offers to install it and sign you in if it is missing.

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) 826
Switching between builds often?

Put the tool on your PATH once:

& ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) -AddToPath

Then this build is just:

winapp-pr 826

Run winapp-pr with no arguments to pick from a list of open PRs.


Updated 2026-09-09 01:31:03 UTC · commit 87b6654 · workflow run

WinUI's 3.0 build ships IncludeGeneratedXamlCompileItemsForDesignTime without an
EnableWinUIDesignTimeGeneratedXamlCompileItems condition, so setting that switch
to false does not stop the platform from adding the generated code-behind. Only
the 2.x servicing cherry-pick carries the gate.

Say so instead of promising the switch disables both targets. Suppressing the
platform's target is deliberately not attempted.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f27fcd4e-9c6e-4583-88a6-c563c8a9f492

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The implementation matches WinUI’s target behavior and all 13 relevant regression tests pass.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

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.

3 participants