Skip to content

[fix] fix: guard GenerateProgramFile target against UseWinUI/UseUwpTools evaluation order - #16072

Merged
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
fix/issue-16071-winui-generateprogramfile-e1f557fbbec8e669
Jun 1, 2026
Merged

[fix] fix: guard GenerateProgramFile target against UseWinUI/UseUwpTools evaluation order#16072
Amaury Levé (Evangelink) merged 1 commit into
mainfrom
fix/issue-16071-winui-generateprogramfile-e1f557fbbec8e669

Conversation

@nohwnd

Copy link
Copy Markdown
Member

🤖 This is an automated fix generated by the Issue Triage agent.

Fixes #16071

Root Cause

Microsoft.NET.Test.Sdk.targets uses InitialTargets="GenerateProgramFile" so the GenerateProgramFile target runs before any user targets. The PropertyGroup at the top of the file tries to set GenerateProgramFile=false when UseWinUI=true:

<GenerateProgramFile Condition="'$(GenerateProgramFile)' == '' AND ('$(UseWinUI)' == 'true' OR '$(UseUwpTools)' == 'true')">false</GenerateProgramFile>
<GenerateProgramFile Condition="'$(GenerateProgramFile)' == ''">true</GenerateProgramFile>

However, when UseWinUI is not set explicitly in the project file but is instead set by the Windows App SDK's own NuGet package targets, the import order matters. NuGet package targets are auto-imported alphabetically; Microsoft.NET.Test.Sdk sorts before WindowsAppSDK, so the Test SDK's PropertyGroup is evaluated when UseWinUI is still empty. As a result, GenerateProgramFile ends up set to true, and the GenerateProgramFile target generates a Program.cs with a Main method — conflicting with the entry point that the WinUI XAML compiler generates, causing CS0017: Program has more than one entry point defined.

Fix

Add the same UseWinUI/UseUwpTools guard directly to the target's Condition, so even if GenerateProgramFile was incorrectly set to true during static property evaluation, the target will never execute for WinUI or UWP test apps:

<Target Name="GenerateProgramFile"
        Condition="'$(GenerateProgramFile)' == 'true' AND '$(UseWinUI)' != 'true' AND '$(UseUwpTools)' != 'true'">

Target conditions are re-evaluated at execution time, by which point all package targets have been imported and UseWinUI is correctly set.

Testing

The fix is a one-line change in a .targets file. The change makes the GenerateProgramFile target's behaviour consistent with the comment in the file ("We always skip the generated entry point for UWP apps... We also do the same for WinUI apps").

A targeted acceptance test for this scenario would require a WinUI test project on Windows with the Windows App SDK — that environment is not available in this CI setup. The logic change mirrors the existing OutputType guard on line 21 of the same file.

🔍 Triaged by Issue Repro Triage & Auto-Fix 🔍

When UseWinUI or UseUwpTools is set by the Windows App SDK package targets,
it may be set AFTER Microsoft.NET.Test.Sdk.targets property evaluation runs
(due to NuGet import order). This causes the static PropertyGroup check on
line 33 to miss UseWinUI=true, leaving GenerateProgramFile=true and triggering
CS0017 (multiple entry points).

The fix adds the same UseWinUI/UseUwpTools guard to the target's Condition,
so the entry-point file is never generated for WinUI or UWP test apps
regardless of property evaluation order.

Fixes #16071

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 29, 2026 01:34

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.

Pull request overview

This PR fixes Microsoft.NET.Test.Sdk.targets so WinUI/UWP test apps do not generate an extra SDK Main entry point when UseWinUI or UseUwpTools is evaluated later in the MSBuild import order.

Changes:

  • Adds UseWinUI/UseUwpTools guards directly to the GenerateProgramFile target condition.
  • Keeps target execution consistent with the existing comments and property defaults for WinUI/UWP apps.

@nohwnd Jakub Jareš (nohwnd) left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

🧠 Expert Review — PR #16072

Scope: 1 file changed, 1 line modified (Microsoft.NET.Test.Sdk.targets)

Analysis

The fix is correct and well-reasoned. The root cause — MSBuild InitialTargets running before NuGet package targets (alphabetically sorted) set UseWinUI — is a real evaluation-order hazard, and the solution is the right one: move the guard to the target's own Condition, which is re-evaluated at execution time after all imports are processed.

Checked dimensions:

  • Algorithmic correctness: The redundant guard ('$(GenerateProgramFile)' == 'true' AND UseWinUI != true) is safe — if GenerateProgramFile was correctly set to false by the PropertyGroup, the target would already be skipped. The new conditions add defense-in-depth for the race window. No logic inversion.
  • MSBuild contract: Target/@Condition`` is evaluated at target execution time, not import time — this is the correct MSBuild mechanism for this scenario.
  • Consistency: The new condition mirrors the existing OutputType guard on line 21 ('$(UseWinUI)' != 'true' AND '$(UseUwpTools)' != 'true'), keeping the file internally consistent.
  • Description alignment: PR title and body accurately describe the change, root cause, and fix rationale.
  • No API surface changes, no binding redirect implications, no nupkg file count changes.

No issues found. This is a clean, minimal fix.


🧠 Reviewed by Expert Code Reviewer

🧠 Reviewed by Expert Code Reviewer 🧠

@Evangelink

Copy link
Copy Markdown
Member

🤖 Expert vstest review (automated)

Small, surgical one-line change to Microsoft.NET.Test.Sdk.targets that guards the GenerateProgramFile target with UseWinUI/UseUwpTools so the target condition is re-evaluated at execution time (after all NuGet auto-imports have settled). The fix is consistent with the intent already documented in the file comment ("We also do the same for WinUI apps"), is strictly an improvement over the prior CS0017 break, and carries no compat risk for users who were already explicitly setting <GenerateProgramFile>false</GenerateProgramFile>. Overall: looks good — a couple of minor follow-ups worth considering, none of which block this fix.


🛑 Blockers

None.

⚠️ Issues

[Cross-TFM & Framework Resolution / RunSettings Validation] OutputType has the exact same import-ordering bug — not addressed here
src/package/Microsoft.NET.Test.Sdk/netcoreapp/Microsoft.NET.Test.Sdk.targets:21

<OutputType Condition="'$(UseWinUI)' != 'true' AND '$(UseUwpTools)' != 'true'">Exe</OutputType>

If the PR's root-cause analysis is correct (NuGet alphabetical import means UseWinUI is empty when the Test.Sdk PropertyGroup is evaluated, because WindowsAppSDK sorts after it), then this line will also incorrectly clobber OutputType to Exe on WinUI/UWP projects whose UseWinUI is set transitively by WindowsAppSDK package targets — the exact scenario the PR description describes. Unlike the GenerateProgramFile case, this one isn't fixable by simply re-conditioning a target (the property is consumed by built-in targets), but at minimum it deserves a comment / TODO calling out the gap, or a wrapper target that overrides OutputType later in the build. Worth at least filing a follow-up.

💡 Suggestions

[Acceptance Test Coverage / Backward Compatibility] No automated test — even a minimal MSBuild-only test is feasible
PR description acknowledges no test was added because a real WinUI project requires the Windows App SDK. That's true for an end-to-end repro, but the logic change is purely an MSBuild target-condition tweak and can be regressed cheaply via:

  • A throwaway acceptance project that imports Microsoft.NET.Test.Sdk.targets directly with <UseWinUI>true</UseWinUI> set in a <PropertyGroup> after the import, then asserts (via a custom probe target or msbuild -t:GenerateProgramFile -v:diag grep) that GenerateProgramFile does not execute.
  • Or a parallel case verifying <UseUwpTools>true</UseUwpTools> is honored.

Without coverage, the next refactor of this file can silently re-break #16071 and we'll find out months later. This is the highest-value follow-up imho.

[Error Reporting & Diagnostic Clarity] Silent skip when user explicitly opts in is surprising
src/package/Microsoft.NET.Test.Sdk/netcoreapp/Microsoft.NET.Test.Sdk.targets:44

With the new condition, a project that sets both <GenerateProgramFile>true</GenerateProgramFile> and <UseWinUI>true</UseWinUI> (e.g. a misconfigured project, or one migrating off a manual workaround) will silently get no generated program file, with no indication why. Pre-PR they got CS0017 — louder, but the underlying property wasn't actually being respected anyway. Consider adding a low-severity <Warning> (or at least <Message Importance="high">) before the target skip when both are true, e.g.:

<Warning Condition="'$(GenerateProgramFile)' == 'true' AND ('$(UseWinUI)' == 'true' OR '$(UseUwpTools)' == 'true')"
         Text="GenerateProgramFile was set to 'true' but is being skipped because UseWinUI/UseUwpTools is 'true' — WinUI/UWP apps must use their own entry point." />

Optional, but it would help the next user hitting an oddball entry-point issue.

[Backward Compatibility & Rollback Safety] No rollback escape hatch
There is no env var / property to force the pre-PR behavior. Given how surgical this change is and the fact that the old behavior was just-plain-broken for the affected scenario, that's probably fine — but if a downstream WinUI configuration relied on the generated Main being there (unlikely, but possible for some non-XAML WinUI test harness), the only recovery is to pin to the prior package version. A quick way to opt back in would be to make the new clause look at an internal property:

Condition="'$(GenerateProgramFile)' == 'true' AND ('$(_ForceGenerateProgramFile)' == 'true' OR ('$(UseWinUI)' != 'true' AND '$(UseUwpTools)' != 'true'))"

Not strictly required for a fix this targeted, just a thought.

✅ Notes

  • netfx/Microsoft.NET.Test.Sdk.targets is unaffected (no GenerateProgramFile target there) — no parallel fix needed.
  • ✅ The fix correctly relies on MSBuild's well-defined behavior that Target/@Condition is re-evaluated at execution time, after all InitialTargets evaluation completes — UseWinUI set by a transitively-imported package target will be observed.
  • ✅ Users who already worked around this with <GenerateProgramFile>false</GenerateProgramFile> in their project are unaffected — line 33 still short-circuits before line 34 in the PropertyGroup.
  • ✅ No public API, IPC, or wire-protocol surface affected.
  • ✅ No package-content / TFM matrix impact.

Verdict: ✅ Approve — ship it. The OutputType parity gap and missing test are worth follow-up issues; the diagnostic warning is a nice-to-have. None of these block the fix for #16071.

@nohwnd

Copy link
Copy Markdown
Member Author

Thanks for the thorough review @Evangelink!

Acknowledging the follow-up items:

  1. OutputType parity gap — good catch. That line has the same import-ordering exposure. Since it's a PropertyGroup property (not a target), it can't be fixed with a target condition the same way. I'll file a follow-up issue to track it.

  2. Diagnostic warning — the suggestion to emit a <Warning> when GenerateProgramFile=true AND UseWinUI/UseUwpTools=true is a nice UX improvement. Filed as a follow-up.

  3. Test coverage — agreed a minimal MSBuild-only test would protect this. The WinUI acceptance scenario requires Windows App SDK, but a unit-level probe is feasible. Will track as a follow-up.

  4. Rollback escape hatch — given how surgical the fix is and that the old behavior was broken for the affected scenario, I'll leave this as-is for now.

CI is green across all jobs. The fix is ready for merge when you are.

🔧 Iterated by PR Iteration Agent 🔧

@Evangelink
Amaury Levé (Evangelink) merged commit 35ef66b into main Jun 1, 2026
23 checks passed
@Evangelink
Amaury Levé (Evangelink) deleted the fix/issue-16071-winui-generateprogramfile-e1f557fbbec8e669 branch June 1, 2026 11:32
This was referenced Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error CS0017 raised when trying to build an app with <UseWinUI>true</UseWinUI> set (without GenerateProgramFiles set to false in the project file)

3 participants