[Extensibility] SingleProject: Register neutral-TFM backends - #36654
Conversation
Extends the SingleProject MauiPlatformSpecificFolder registration contract so an external backend NuGet can identify and activate a backend for both recognized platform TFMs and a neutral TFM, keying compile selection off one resolved backend identity. Registration identity + compile activation only (Phase 1/2/3 scope of #35021). App-head manifest/resource hooks are intentionally out of scope for this PR. Changes: - MauiPlatformSpecificFolder gains additive metadata: BackendIdentity (stable backend key), ActivationProperty (neutral-TFM selector property name, defaults to the well-known MauiActiveBackend), and ActivationValue (defaults to BackendIdentity). Built-in platforms now also declare BackendIdentity so they describe themselves through the same registration shape. - _MauiNormalizePlatformSpecificFolders back-fills the plural TargetPlatformIdentifiers contract and resolves the activation property value (MSBuild dynamic property dereference), pre-normalizing whitespace/case so the collect condition stays parseable. - _MauiCollectPlatformSpecificCompileItems keeps a folder when its recognized TargetPlatformIdentifier(s) match, OR when its ActivationProperty value matches ActivationValue (neutral-TFM backend activation, e.g. net11.0 + GTK), OR when it is an unconditioned/condition-gated folder. Only the active backend's Platforms/<Backend> files compile. - Existing built-in, shared-folder (e.g. Platforms/Apple for ios;maccatalyst), singular-metadata backward-compat, and design-time ExcludeFromCurrentConfiguration flow are preserved exactly. - Adds focused MSBuild unit tests: recognized TPI + shared folders, neutral net11.0 GTK-style activation, non-matching backend exclusion, custom activation property/value, singular-metadata backward compatibility, trailing-slash sibling safety, and non-platform/condition-gated participation. Part of #35021 Part of #36650 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 66f84348-6476-4097-8b7f-f240338e85c3
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36654Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36654" |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Implements SingleProject compile-item activation for external backends when the inner build uses a neutral TFM (e.g., net11.0), by extending the existing MauiPlatformSpecificFolder contract with backend identity + activation metadata and updating compile filtering accordingly (part of #35021 / #36650).
Changes:
- Extend
MauiPlatformSpecificFoldernormalization to back-fillTargetPlatformIdentifiers, default activation metadata, and resolve activation-property values for neutral-TFM selection. - Add a new allow-list collection step so only the active platform/backend’s
Platforms/<Backend>/**compile items survive the blanketPlatforms/**removal, and un-grey kept items in the IDE. - Add MSBuild unit tests covering shared folder mappings, trailing-slash safety, non-platform builds, singular-metadata compatibility, and neutral-TFM activation (including custom activation property/value).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs | Adds focused MSBuild tests that validate recognized-TPI matching and neutral-TFM backend activation via MauiPlatformSpecificFolder metadata. |
| src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.targets | Introduces compile allow-list collection + IDE metadata unflip, and updates platform compile-item removal to preserve explicitly kept items. |
| src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.Before.targets | Normalizes MauiPlatformSpecificFolder metadata (plural TPI backfill, activation defaults, dynamic activation-property resolution, normalized compare values). |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
Prevent MauiActiveBackend from adding neutral backend files to recognized Android, iOS, and other platform inner builds, with focused regression coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 66f84348-6476-4097-8b7f-f240338e85c3
This comment has been minimized.
This comment has been minimized.
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs:783
- The dashed "---" banner comment is a section-separator style that the repo guidelines discourage (it’s effectively a fake region header). Consider replacing it with a simple single-line comment so the file stays consistent and avoids noise.
// --- SingleProject platform-folder registration & activation ---------------
//
Keep the SingleProject activation test overview as a normal explanatory comment per repository conventions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 66f84348-6476-4097-8b7f-f240338e85c3
|
Addressed the current-head repository-style note in |
This comment has been minimized.
This comment has been minimized.
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.targets:164
- The comment about cross-item-type batching is misleading: the blanket
<Compile Update>setsExcludeFromCurrentConfiguration=true, but the per-TPI updates at the top of this file then flip the active platform’s items back tofalse, so batching is typically two iterations (true/false), not a single iteration. This matters because it’s the reason theConditionmust remain in place and how it behaves when active items are alreadyfalse.
the blanket <Compile Update> at the top of this file always marks
every $(PlatformsProjectFolder)/** file as ExcludeFromCurrentConfiguration=true,
so this batches into a single iteration; do NOT "simplify" away the
Condition without first re-verifying that contract still holds, or
Describe the true and false ExcludeFromCurrentConfiguration batches accurately so the removal guard is not simplified incorrectly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 66f84348-6476-4097-8b7f-f240338e85c3
|
Addressed the current-head low-confidence batching-comment issue in |
This comment has been minimized.
This comment has been minimized.
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
PureWeen
left a comment
There was a problem hiding this comment.
Round-6 adversarial re-review — 3 independent reviewers with adversarial consensus
Re-fetched the PR at HEAD c73b7025 and reviewed the three new commits (dc4b4015 "Document bare backend activation values", e354686b "Remove fake region from activation tests", c73b7025 "Clarify SingleProject removal batching") from scratch. Each reviewer diffed executable-vs-comment content at the byte level and re-ran the full over-exclusion matrix with real dotnet msbuild builds. Multi-round self-correction applied: dc4b4015 was written in response to my round-5
Primary question — does dc4b4015 FIX the round-5 bare-AV ⚠️ , or only DOCUMENT it?
DOCUMENTED-NOT-FIXED (by design), and honestly so. 3/3 reviewers confirmed the Branch A condition expression is byte-identical between 83ab69d6 and c73b7025 — the executable allow-list logic did not change. A bare ActivationValue="$(UnsetProperty)" with no ActivationProperty/BackendIdentity still resolves to all-four-empty metadata (indistinguishable from a legacy shared folder) and still compiles everywhere — reproduced on neutral and -p:TargetPlatformIdentifier=ios. What the commit changes is the framing: it documents the limitation in the Branch A comment, states the contract ("conditional backends must also declare ActivationProperty or BackendIdentity"), and — via the new test — codifies the bare form as intended legacy-shared-folder behavior.
The new test SingleProject_BareUnresolvedActivationValueUsesLegacySharedFolderBehavior ([InlineData("")], [InlineData("ios")]) asserts the bare-AV folder DOES compile. All 3 reviewers verified it passes on HEAD and is a real gate in its own direction — tightening Branch A to exclude the bare form in a scratch copy turns the test RED. So it's a genuine compatibility lock, not a vacuous test.
⚠️ The residual is documented, not closed — and a behavioral fix is feasible (correcting the record)
The bare-AV form still fails open silently. This is a reproduced, documented, test-locked compatibility behavior — not a defect introduced by these commits, and not a merge blocker. But one point deserves correction, because it affects how the tradeoff should be described:
Two of three reviewers argued a behavioral fix is impossible in pure MSBuild (absent attribute and authored-but-resolves-empty both read as ""). I verified this claim independently and it is too strong — a behavioral distinction is achievable with an ItemDefinitionGroup default acting as a sentinel:
ItemDefinitionGroup ActivationValue = __ABSENT__ ⇒
absent attribute → %(ActivationValue) = __ABSENT__ (inherits default)
ActivationValue="$(unset)" → %(ActivationValue) = "" (authored value overrides default)
ActivationValue="gtk" → %(ActivationValue) = gtk
The default is applied only when the attribute is absent, so an authored-but-empty value is distinguishable and Branch A could fail closed on it. The cost is real, though: __ABSENT__ becomes observable public metadata on every folder that doesn't author ActivationValue (including the built-ins and legacy shared folders), so it would have to be threaded through the normalization / back-fill / Branch A+B conditions — non-trivial complexity and regression surface for an edge case the contract already declares unsupported.
Net: documenting + contract + a compatibility-locking test is a defensible engineering choice for round 6, but it is not the only possible answer. If the team later decides a stray ActivationValue="$(Foo)" (the most minimal authoring slip) warrants protection, the sentinel path is available.
c73b7025 "Clarify SingleProject removal batching" — comment-only
3/3 confirmed: the _MauiPlatformCompileToRemove Condition, Include glob, and Exclude are byte-identical to 83ab69d6. Only the explanatory comment changed (and it's now more accurate — it correctly describes the true-batch + active-platform-flipped-false-batch under a real global TPI). No behavioral change. The full empirical matrix re-passes on HEAD:
- ✅ ⭐ No-attribute legacy shared folder still compiles everywhere (neutral + all 5 recognized TFMs) — the top legacy-breakage risk, clean.
- ✅ Legit gtk activates in both forms (
ActivationValue="gtk"and bareBackendIdentity="gtk") withMauiActiveBackend=gtkon neutral; does not leak into recognized TFMs. - ✅ Each built-in (ios/android/maccatalyst/windows/tizen) selects only its own folder.
- ✅ Whitespace-only and unresolved-with-metadata
ActivationValuecompile nowhere on neutral AND ios (fail closed). - ✅
7ab5012aCompile item order (Before|Active|After) intact, no duplicate Compile items, design-timeExcludeFromCurrentConfiguration=falseunflip fires on kept folders.
e354686b "Remove fake region from activation tests" — scaffolding-only
3/3 confirmed the two removed lines are a decorative // --- … --- banner + blank comment. No assertion, test method, or [InlineData] was removed or weakened.
💡 Suggestion
The PR description/changelog should state the bare-AV behavior explicitly as "documented as intentional legacy-compat behavior, not closed" rather than implying the round-5 finding was fixed — so downstream readers understand a stray ActivationValue="$(unset)" compiles everywhere by design.
Bottom line
No ❌ blocking issues. The three new commits are comment / test / scaffolding only; the executable targets are byte-identical to the round-5-certified 83ab69d6, and the full fail-closed + over-exclusion + ordering matrix re-passes cleanly. The round-5
Review methodology: 3 independent reviewers with adversarial consensus. This is a COMMENT-only review — approval is a human decision.
|
Current-head validation evidence: improved review |
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial code review — no issues found
No correctness, regression, security, data-loss, ordering, shared-config, or test-adequacy issues survived review.
What looks right: the activation/removal pipeline preserves legacy shared folders and built-in platform selection while adding neutral-TFM backend activation; malformed metadata fails closed when backend intent is present, and the documented bare-value compatibility boundary is locked by a direction-sensitive regression test.
Test coverage: 3/3 reviewers independently exercised the exact PR-HEAD shipping targets with real MSBuild probes across neutral/shared folders, both GTK activation forms, all five built-ins, whitespace and unresolved activation cases, compile ordering, duplicate registration, wrapper import timing, and design-time metadata unflip. The added 37-case matrix imports the real shipping targets and includes discriminating positive, negative, adjacent, ordering, and metadata assertions.
Prior review status: current-head findings and author responses were reconciled; no previously reported blocking issue remains unresolved, and this fresh pass found no contradictory evidence.
Verdict: clean on code merit. No inline findings.
Methodology: 3 independent reviewers with adversarial consensus.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
ℹ️ The review agent did not produce a full summary on this run (an infrastructure issue on the CI agent), but the deep UI tests completed — their results are below. Re-comment
/reviewfor a fresh full review.
✅ Deep UI tests — 359 passed, 0 failed across 3 categories on platform-pool agent (replaces in-process counts above).
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
Button |
71/73 ✓ | 1 diff PNG |
Label |
96/98 ✓ | — |
Layout |
192/195 ✓ | — |
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs) |
AI Review Summary
🗂️ Review Sessions — click to expand🚦 Gate — Test Before & After FixGate Result: ✅ PASSEDPlatform: ANDROID · Base: net11.0 · Merge base:
🔴 Without fix — 📄 MSBuildTests: FAIL ✅ · 314sError-relevant lines (filtered from the build log): 🟢 With fix — 📄 MSBuildTests: PASS ✅ · 214s(no coded error found; showing last 1200 chars) 📁 Fix files reverted (2 files)
📱 UI Tests — Button,Label,LayoutDetected UI test categories: ✅ Deep UI tests — 359 passed, 0 failed across 3 categories on platform-pool agent (replaces in-process counts above). 🧪 UI Test Execution Results (deep, platform pool)
📋 Pre-Flight — Context & ValidationIssue: #36650 - SingleProject: Register and activate external backends for neutral TFMs Key Findings
Code Review SummaryVerdict: NEEDS_CHANGES Key code review findings:
Fix Candidates
🔬 Code Review — Deep AnalysisCode Review — PR #36654Independent AssessmentWhat this changes: Adds Reconciliation with PR NarrativeAuthor claims: Implements Phases 1–3 of #35021/#36650: registration identity, neutral-TFM compile activation, fail-closed malformed metadata handling, design-time metadata fixes, and tests. Prior Review Reconciliation
Blast Radius Assessment
CI Status
External Output Contract
Findings❌ Error —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | expert/code-review loop | Add a compiled MSBuild task that projects the full @(Compile) list in order and flips only kept items. |
❌ FAIL | 3 files | Failed with MSB4062 because the task assembly was not available from the source buildTransitive import path used by the MSBuild tests. |
| 2 | expert/code-review loop | Pure-MSBuild ordered projection of the full @(Compile) list; flip only exact kept-item matches. |
✅ PASS | 2 files | Passed focused unflip tests (3/3) and full MSBuildTests.SingleProject_* regression matrix (40/40). Addresses the code-review ordering finding without a compiled task. |
| PR | PR #36654 | Current remove/re-add unflip plus backend identity/activation metadata normalization. | ✅ PASSED (Gate) | 3 files | Original PR. Gate was already completed before this run and was not re-run or overwritten. Code review found an additional compile-order risk for kept shared/custom items. |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| gpt-5.5 / maui-expert-reviewer | 1 | Yes | Proposed compiled task ordered projection (try-fix-1). |
| gpt-5.5 / maui-expert-reviewer | 2 | Yes | After MSB4062 failure, proposed pure-MSBuild ordered projection (try-fix-2). |
Exhausted: No — stopped because candidate #2 passed the focused and regression tests and is demonstrably better than the PR's current unflip implementation for the reviewed ordering issue.
Selected Fix: Candidate #2 — preserves @(Compile) order for kept shared/custom items that need unflipping, avoids a compiled task dependency, preserves unrelated metadata, and passed the relevant SingleProject matrix.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the current metadata is strong, but the winning fix adds an order-preserving _MauiUnflipKeptCompileItemMetadata change and regression test that the current description does not mention.
Recommended title
[Extensibility] SingleProject: Register neutral-TFM backends
Recommended description
## Description of Change
This is **one focused part** of the external-backend extensibility effort. It implements **registration identity + compile activation** for SingleProject — Phases 1–3 of #35021, scoped to the child issue #36650 (*SingleProject: Register and activate external backends for neutral TFMs*). It deliberately does **not** add app-head manifest/entitlement/resource hooks (those are later phases of #35021).
PR #35045 landed the data-driven folder-selection machinery only in `inflight/current`; this PR ports that machinery to `net11.0` and extends it with backend identity and neutral-TFM activation.
It builds directly on the `MauiPlatformSpecificFolder` registration item (from #35045) rather than introducing a competing registration item, and extends it so an external backend NuGet can identify and activate a backend for **both** activation paths described in #35021:
1. **Recognized platform TFM** — `TargetPlatformIdentifier(s)` resolves (e.g. `ios`, `maccatalyst`, `android`).
2. **Neutral TFM fallback** — the inner build is a plain `net11.0` and the backend opts in via a selector property (default `MauiActiveBackend`), e.g. the GTK / Linux backend:
```xml
<TargetFrameworks>net11.0-ios;net11.0</TargetFrameworks>
<MauiActiveBackend Condition="'$(TargetFramework)' == 'net11.0'">gtk</MauiActiveBackend>
<PackageReference Include="Platform.Maui.Linux.Gtk4" Version="..." />
What changed
- Additive
MauiPlatformSpecificFoldermetadata:BackendIdentity— stable backend key (e.g.gtk). Built-in platforms now declare it too, so they describe themselves through the same registration shape.ActivationProperty— the property that selects this backend on a neutral TFM (defaults to the well-knownMauiActiveBackend).ActivationValue— the value that activates this backend (defaults toBackendIdentityfor non-TPI backend registrations).
_MauiNormalizePlatformSpecificFoldersback-fills the pluralTargetPlatformIdentifierscontract, defaults the activation metadata, and resolves the activation property's value (via MSBuild dynamic property dereference), pre-normalizing whitespace/case so the collect condition stays parseable._MauiCollectPlatformSpecificCompileItemskeeps a folder'sPlatforms/<Backend>/**/*.cswhen any of: the recognizedTargetPlatformIdentifier(s)match; the resolved activation value matchesActivationValue(neutral-TFM activation); or it is an unconditioned/condition-gated folder. Only the active backend's compile items survive the blanketPlatforms/**removal._MauiUnflipKeptCompileItemMetadataun-greys neutral-activated/shared backend folders in design-time builds while preserving the original@(Compile)order. It updates only exact kept-item matches and avoids the remove/re-add ordering regression for shared/custom backend files.- Exact preservation of existing built-in, shared-folder (e.g.
Platforms/Apple→ios;maccatalyst), singular-metadata backward-compat, design-timeExcludeFromCurrentConfigurationflow, trailing-slash sibling safety, and condition-gated folder behavior.
Tests
Adds focused MSBuild unit tests in Controls.Xaml.UnitTests (they import the real shipping SingleProject targets):
- recognized TPI + shared folders (
ios/maccatalyst/android, whitespace/tab tolerant); - neutral
net11.0GTK-style activation (only the activated backend compiles; a recognized built-in folder stays excluded); - non-matching backend exclusion (wrong
MauiActiveBackend, and unset); - recognized platform TFMs ignore a neutral backend selector, preventing GTK files from leaking into Android/iOS builds;
- custom
ActivationProperty/ActivationValue; - singular-metadata backward compatibility;
- trailing-slash sibling-folder safety and non-platform shared-folder participation;
- genuine item-
Conditiongating; - design-time
ExcludeFromCurrentConfiguration=falserestoration for kept shared/backend files; - preservation of
@(Compile)order and unrelated metadata when kept shared/custom backend items are unflipped.
Issues Fixed
Fixes #36650
Part of #35021
Builds on #35045
</details>
---
<details>
<summary><strong>🏁 Report — Final Recommendation</strong></summary>
<br/>
## Comparative report — PR #36654
### Candidates
| Rank | Candidate | Regression tests | Assessment |
|---:|---|---|---|
| 1 | `pr-plus-reviewer` | ✅ Passed by equivalence to `try-fix-2` (`SingleProject_UnflipKeptCompileItemMetadata` 3/3 and `MSBuildTests.SingleProject_` 40/40); sandbox patch applied cleanly and `git diff --check` passed | Best candidate. Keeps the PR's backend identity/neutral-TFM activation design and applies the expert review fix so `_MauiUnflipKeptCompileItemMetadata` no longer reorders kept shared/custom backend compile items. |
| 2 | `try-fix-2` | ✅ Passed focused unflip tests 3/3 and full SingleProject matrix 40/40 | Technically equivalent to `pr-plus-reviewer` for the actionable reviewer fix. Ranked below `pr-plus-reviewer` only because the requested Phase 1 candidate is the PR fix plus expert feedback, while this is the same patch represented as an independent try-fix candidate. |
| 3 | `pr` | ✅ Gate passed, but expert review found a missed regression | The core backend activation behavior is sound and gate passed, but the current remove/re-add unflip implementation can perturb `@(Compile)` order for kept shared/custom backend items. This must rank below passing candidates that fix that regression. |
| 4 | `try-fix-1` | ❌ Failed | The compiled-task approach could preserve order conceptually, but failed with MSB4062 because the task assembly is not available from the source `buildTransitive` import path used by the MSBuild tests. Per the ranking rule, failed candidates rank below passing candidates. |
### Winning candidate
**Winner:** `pr-plus-reviewer`
`pr-plus-reviewer` preserves the PR's intended SingleProject external-backend extensibility model and incorporates the expert review's actionable ordering fix without introducing a compiled task dependency. It is effectively the already-passing `try-fix-2` patch applied as reviewer feedback to the PR, so it avoids the raw PR's compile-order regression while retaining the evidence from the full SingleProject test matrix.
### Recommendation
Proceed with the PR only after applying the `pr-plus-reviewer` ordered-projection change and the new shared-folder order regression test. The raw PR should not be taken as-is because it leaves a concrete build item ordering regression in `_MauiUnflipKeptCompileItemMetadata`.
</details>
</details>
<!-- SESSION:c73b702 END -->
---
<details>
<summary><strong>🧭 Next Steps</strong> — review latest findings</summary>
<br/>
No alternative fix was selected for this run. Review the session findings and CI results before merging.
</details>
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 of Change
This is one focused part of the external-backend extensibility effort. It implements registration identity + compile activation for SingleProject — Phases 1–3 of #35021, scoped to the child issue #36650 (SingleProject: Register and activate external backends for neutral TFMs). It deliberately does not add app-head manifest/entitlement/resource hooks (those are later phases of #35021).
PR #35045 landed the data-driven folder-selection machinery only in
inflight/current; this PR ports that machinery tonet11.0and extends it with backend identity and neutral-TFM activation.It builds directly on the
MauiPlatformSpecificFolderregistration item (from #35045) rather than introducing a competing registration item, and extends it so an external backend NuGet can identify and activate a backend for both activation paths described in #35021:Recognized platform TFM —
TargetPlatformIdentifier(s)resolves (e.g.ios,maccatalyst,android).Neutral TFM fallback — the inner build is a plain
net11.0and the backend opts in via a selector property (defaultMauiActiveBackend), e.g. the GTK / Linux backend:What changed
MauiPlatformSpecificFoldermetadata:BackendIdentity— stable backend key (e.g.gtk). Built-in platforms now declare it too, so they describe themselves through the same registration shape.ActivationProperty— the property that selects this backend on a neutral TFM (defaults to the well-knownMauiActiveBackend).ActivationValue— the value that activates this backend (defaults toBackendIdentity)._MauiNormalizePlatformSpecificFoldersback-fills the pluralTargetPlatformIdentifierscontract, defaults the activation metadata, and resolves the activation property's value (via MSBuild dynamic property dereference), pre-normalizing whitespace/case so the collect condition stays parseable._MauiCollectPlatformSpecificCompileItemskeeps a folder'sPlatforms/<Backend>/**/*.cswhen any of: the recognizedTargetPlatformIdentifier(s)match; the resolved activation value matchesActivationValue(neutral-TFM activation); or it is an unconditioned/condition-gated folder. Only the active backend's compile items survive the blanketPlatforms/**removal.Platforms/Apple→ios;maccatalyst), singular-metadata backward-compat, and design-timeExcludeFromCurrentConfigurationflow (_MauiUnflipKeptCompileItemMetadataalso un-greys neutral-activated backend folders in the IDE).Tests
Adds focused MSBuild unit tests in
Controls.Xaml.UnitTests(they import the real shipping SingleProject targets):ios/maccatalyst/android, whitespace/tab tolerant);net11.0GTK-style activation (only the activated backend compiles; a recognized built-in folder stays excluded);MauiActiveBackend, and unset);ActivationProperty/ActivationValue;Conditiongating (aMauiPlatformSpecificFoldermapping that carries its ownConditionparticipates only when it evaluates true, and is stripped when false);_MauiUnflipKeptCompileItemMetadataflipsExcludeFromCurrentConfigurationback tofalseon a kept shared folder'sCompileitem (asserted directly via a diagnosticMessagetarget on a non-platform build);Compileitems keep their existing order while shared/custom kept items still receive the active-configuration metadata flip.Targeted local validation: the complete 37-case
MSBuildTests.SingleProject_*matrix was exercised against the actual shipping SingleProject targets — see the validation note below.Scope / relationship to #35021
Issues Fixed
Part of #35021
Fixes #36650
Current-head review fix (
c2bd01e9df)ActivationValuethat normalizes to empty is treated as unset and follows the legacy always-include path instead of silently excluding the folder.MSBuildTests.SingleProject_*matrix passes 32/32.Final fail-closed activation fix (
d99d5e6c1d)ActivationValuethat normalizes to empty is excluded on both neutral and recognized TFMs instead of being reclassified as a shared always-include folder.MSBuildTests.SingleProject_*matrix passes 34/34.Compile-order preservation (
7ab5012a5f)_MauiUnflipKeptCompileItemMetadataskips built-in platform items that are already active, avoiding an unnecessary remove/re-add while continuing to flip kept shared/custom folders toExcludeFromCurrentConfiguration=false.SingleProject_UnflipKeptCompileItemMetadata_PreservesAlreadyActiveCompileItemOrderandSingleProject_UnflipKeptCompileItemMetadata_SetsExcludeFromCurrentConfigurationFalseForKeptSharedFolder.Unresolved activation fail-closed fix (
83ab69d62ec)ActivationPropertywith anActivationValuethat expands to empty cannot leak backend source into every TFM.Bare activation-value compatibility boundary (
dc4b4015966)ActivationValue="$(UnsetProperty)"expands to empty during MSBuild project evaluation and is indistinguishable from an intentional legacy shared-folder mapping by normalization time. Conditional backends must also declareActivationPropertyorBackendIdentityso malformed metadata remains visible and fails closed.14820391and CI builds1533543 / 1533545 / 1533547are active; prior-head reviews were canceled after the documentation-only follow-ups. Auto-merge remains armed with the merge-commit method.