Skip to content

Don't unpack netstandard workload packs (fix nightly vs-workload.props MSB4019) - #36089

Merged
PureWeen merged 3 commits into
inflight/currentfrom
pureween-fix-nightly-vs-workload-props
Jun 25, 2026
Merged

Don't unpack netstandard workload packs (fix nightly vs-workload.props MSB4019)#36089
PureWeen merged 3 commits into
inflight/currentfrom
pureween-fix-nightly-vs-workload-props

Conversation

@PureWeen

@PureWeen PureWeen commented Jun 24, 2026

Copy link
Copy Markdown
Member

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

Fixes the nightly official signed build (dotnet-maui, definition 1095, dnceng/internal), which has
failed 8 consecutive nights on refs/heads/inflight/current. The Windows Pack job → step
"Build Workloads, Sign & Publish" fails at restore/evaluation:

src\Workload\workloads.csproj(30,3): error MSB4019: The imported project
"...\artifacts\packages\Release\Shipping\vs-workload.props" was not found.

Root cause

The workload packs — Microsoft.NET.Sdk.Maui.Manifest and Microsoft.Maui.Sdk — both target
netstandard2.0 (via src/Workload/Shared/Common.targets) and both explicitly set
<IsPackable>true</IsPackable> because they are the shipping workload packages.

PR #32203 ("Don't pack .NET Standard") added a blanket rule to Directory.Build.targets:

<IsPackable Condition="$(TargetFramework.Contains('netstandard'))">false</IsPackable>

Directory.Build.targets is auto-imported at the end of every project, so this assignment runs
after the project body and overrides the explicit <IsPackable>true</IsPackable> on the workload
packs. The net effect: the manifest project evaluates to IsPackable=false.

The chain that breaks from there:

  1. With IsPackable=false, NuGet's Pack target no-ops.
  2. The official "Pack, Sign" step (eng/pipelines/arcade/stage-pack.yml) runs -restore -pack -sign
    without -build, so the Build target never runs for the manifest project.
  3. _GenerateVSWorkloadProps (in Microsoft.NET.Sdk.Maui.Manifest.csproj) is hooked
    AfterTargets="Build", so it never runs → vs-workload.props is never written to
    $(ArtifactsShippingPackagesDir).
  4. The next step (-build … -projects src/Workload/workloads.csproj) hits workloads.csproj line 30,
    whose <Import Project="$(WorkloadMsiGenProps)" /> is unconditional and fails at evaluation → MSB4019.

The blanket rule is also over-broad: it clobbers the explicit opt-in on every netstandard-only
project, not just the workload packs (see Scope below). The workload/MSI leg simply failed loudest
because its missing-file import is fatal at evaluation (MSB4019); the others would silently stop shipping.

Why main is green: PR #32203 is on inflight/current only — it is not an ancestor of main or
net10.0 (verified with git merge-base --is-ancestor). main and inflight/current otherwise share
identical global.json (dotnet 10.0.108, arcade 25555.106), identical src/Workload, and identical
Microsoft.Build.NoTargets 3.7.0. The only relevant difference is the #32203 block, which is why this
break is specific to inflight/current.

Fix

Guard the #32203 rule so it only applies when a project has not explicitly opted into packing:

<IsPackable Condition="$(TargetFramework.Contains('netstandard')) and '$(IsPackable)' == ''">false</IsPackable>
  • Projects that explicitly set <IsPackable>true</IsPackable> (the workload packs) are respected and
    pack again — restoring vs-workload.props generation.
  • Unmarked netstandard projects still default to not-packable, preserving Don't pack .NET Standard #32203's intent.

This restores the exact condition (IsPackable=true for the manifest project) that the passing main
build exhibits, where _GenerateVSWorkloadProps runs and vs-workload.props is produced.

Scope / blast radius

Because #32203's blanket rule was over-broad, this guard restores packability for 6 netstandard-only
projects that explicitly opt in — not just the 2 workload packs:

Project TFM
Microsoft.NET.Sdk.Maui.Manifest netstandard2.0
Microsoft.Maui.Sdk netstandard2.0
Resizetizer netstandard2.0
Controls.Build.Tasks netstandard2.0
Controls.SourceGen netstandard2.0
Graphics.Text.Markdig netstandard2.0

All 6 pack on main (which never had the #32203 rule), so this is a main-parity restore, not new
shipping behavior. Multi-target libraries (e.g. Core, Controls.Core) are unaffected: dotnet pack
evaluates IsPackable at the outer level where TargetFramework="", so the netstandard rule never fires
for them.

How validated

Root cause confirmed from the actual failing/​passing build binlogs (not guesswork):

failing inflight/current build 3006161 passing main build 3006214
Microsoft.NET.Sdk.Maui.Manifest IsPackable false true
_GenerateVSWorkloadProps ran? no (Build never ran) yes → vs-workload.props produced

Verified locally on the inflight/current tree with
dotnet msbuild …Manifest.csproj -getProperty:IsPackable:

  • Before fix: manifest IsPackable=false (reproduces the break).
  • After fix: manifest IsPackable=true, Microsoft.Maui.Sdk IsPackable=true.
  • Microsoft.Maui.Core (multi-target) at pack time is unchanged (IsPackable=true, TargetFramework="";
    the netstandard rule only fires for single-TFM netstandard evaluations, so multi-target library
    packaging is unaffected).
  • A netstandard project with no explicit opt-in (Controls.CustomAttributes) still resolves
    IsPackable=falseDon't pack .NET Standard #32203's intent preserved.

The full signed Windows MSI leg only runs in the official pipeline; this change restores the property
state that gates the entire pack → vs-workload.props → workloads handoff.

PR CI confirms the fix end-to-end: on base inflight/current the Pack Windows leg fails at the
root-cause spot; on this PR the same leg is green. The remaining red maui-pr legs are pre-existing on
inflight/current (Windows Helix unit-test flakiness identical on base, and integration legs that were
skipped on base because Pack was blocked and only run now that the fix unblocked Pack).

Follow-up note

When PR #32203's Directory.Build.targets change is forward-ported to net10.0/main, it must carry
this '$(IsPackable)' == '' guard, otherwise the same break will reappear on those branches.

…s MSB4019)

PR #32203 added a blanket '<IsPackable Condition="$(TargetFramework.Contains('netstandard'))">false'
to Directory.Build.targets. Because that import runs after each project body, it overrode the explicit
<IsPackable>true</IsPackable> on the netstandard-only workload packs (Microsoft.NET.Sdk.Maui.Manifest
and Microsoft.Maui.Sdk).

With IsPackable=false the manifest project's Pack target no-ops, so during the official 'Pack, Sign'
step (-restore -pack -sign, no -build) the Build target never runs and _GenerateVSWorkloadProps
(AfterTargets=Build) never generates artifacts/.../Shipping/vs-workload.props. The subsequent
'Build Workloads' step then fails workloads.csproj's unconditional import at line 30 with MSB4019.

Guard the rule with "and '$(IsPackable)' == ''" so projects that explicitly opt in to packing a
netstandard target are respected, while still defaulting unmarked netstandard projects to not-packable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen added a commit that referenced this pull request Jun 24, 2026
The child investigation (PR #36089) binlog-proved the Jun 2026
inflight/current nightly outage: PR #32203's blanket netstandard
IsPackable=false in Directory.Build.targets clobbered the workload packs'
explicit IsPackable=true, so the no--build 'Pack, Sign' step never ran
Build → _GenerateVSWorkloadProps never generated vs-workload.props →
MSB4019. Record the specific cause, the fix, the inflight-only scope, and
the forward-port warning so the breadcrumb teaches the real failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the area-infrastructure CI, Maestro / Coherency, upstream dependencies/versions label Jun 24, 2026

@kubaflo kubaflo 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.

Note

🤖 This review was generated by AI — an automated multi-model code review (Claude Opus 4.8 · GPT-5.5 · Gemini 3.1 Pro), independent reviews reconciled via cross-pollination.


🔍 AI Multi-Model Code Review — PR #36089

1-line build fix (Directory.Build.targets, base inflight/current) for the nightly vs-workload.props MSB4019 break.

🟡 Verdict: NEEDS DISCUSSION (approve-adjacent)

Confidence: Medium — unanimous after cross-pollination (Gemini moved LGTM → NEEDS_DISCUSSION; all 3 agree). CI classification: pre-existing-on-base (conclusive).

The fix is correct, minimal, and demonstrably works. The only thing keeping this from a clean LGTM is the procedural rule that a human — not the bot — makes the "merge while the maui-pr rollup is red" call when every red leg is pre-existing.

✅ The fix works — and it's visible in PR CI

Cross-pollination verified this against live AzDO data (correcting an initial reviewer assumption that the fix was "invisible to PR CI"):

  • Base inflight/current build 1477456: Pack Windows FAILED at Copy Metadata: Not found …Shipping\metadata — the exact root cause (the workload manifest was unpacked because the blanket IsPackable=false overrode its explicit true).
  • PR build 1478479: Pack Windows GREEN, zero metadata errors.

So the guard restores IsPackable=true for the workload packs, the manifest packs again, _GenerateVSWorkloadProps (AfterTargets=Build) runs, and vs-workload.props is written. The fix's own leg goes red → green publicly.

🔴→🟢 The red maui-pr rollup is pre-existing base flakiness (not PR-caused)

Every red leg was positively classified as pre-existing on inflight/current:

  • Windows Helix Unit Tests (Debug+Release): fail identically on base and PR — unrelated to packaging.
  • Integration Tests (Blazor/Build/MultiProject macOS, RunOniOS): the integration stage was SKIPPED on base (Pack-blocked) and only runs now because the fix unblocked Pack — its failures are pre-existing branch regressions, not caused by this change.
  • OS-asymmetric flake signature (macOS fails / Windows passes on the same suite) is infrastructure flakiness, not a deterministic packability regression.
  • The change moves inflight/current packability back toward main's state, and main is green.

⚖️ Why NEEDS_DISCUSSION (not LGTM)

Per the code-review skill Rule #6, the bot does not post LGTM while the required maui-pr rollup is red — even when every red leg is confidently pre-existing. This is a deliberate human-judgment gate for "merge while red." This is the sole blocker — the verdict is borderline/approve-adjacent, and a maintainer can reasonably merge this as a pre-existing-red-only, root-cause-fixing change.

💬 Discussion points (see inline)

  1. Wider blast radius than described — the guard restores packability for ~6 netstandard opt-in projects (not 2): the 2 workload packs plus Resizetizer, Controls.Build.Tasks, Controls.SourceGen, Graphics.Text.Markdig. Matches main, so almost certainly intended — worth an explicit confirmation.
  2. Forward-port — when #32203 (the blanket rule, 543b1ebeb7, currently inflight/current-only) lands on net10.0/main, this guard must travel with it or the MSB4019 break reappears.
  3. (Minor) The truest end-to-end validation is the next official nightly (def 1095, dnceng/internal) going green — now low-risk given Pack Windows is already green on the PR.

🏁 Recommendation

NEEDS_DISCUSSION → approve-adjacent. No code changes requested — the fix is correct and resolves the 8-night nightly outage. Human call needed only to merge over the pre-existing red maui-pr rollup. Confirm the blast radius is intended and remember the forward-port.


Reviewed with the code-review skill: independence-first, full-file reading, live AzDO base-vs-PR build comparison to classify CI, and cross-model reconciliation (one reviewer retracted an incorrect "invisible to CI" claim after verification).

Comment thread Directory.Build.targets
<!-- Don't ship .NET Standard packages by default, EXCEPT when a project explicitly opts in via
<IsPackable>true</IsPackable> (e.g. the workload manifest and SDK packs, which are netstandard-only
but must ship). The '$(IsPackable)' == '' guard preserves that explicit opt-in. -->
<IsPackable Condition="$(TargetFramework.Contains('netstandard')) and '$(IsPackable)' == ''">false</IsPackable>

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.

💡 💡 Discussion (non-blocking) — two follow-ups on this guard

The guard itself is correct: Directory.Build.targets is auto-imported at the end of every project, so $(IsPackable) is already populated when this condition evaluates — an explicit <IsPackable>true</IsPackable> set earlier survives, while netstandard libraries that never set it still default to non-packable. 👍

Two things worth confirming for the record:

  1. Blast radius is wider than the description's "2 workload packs." A repo-wide grep for explicit <IsPackable>true</IsPackable> on single-TFM netstandard projects shows this guard restores packability for ~6 projects — Microsoft.Maui.Sdk and Microsoft.NET.Sdk.Maui.Manifest (described), plus Resizetizer, Controls.Build.Tasks, Controls.SourceGen, and Graphics.Text.Markdig. This matches main's behavior (which is green), so it's almost certainly intended — but worth an explicit confirmation since they'll now ship again.

  2. Forward-port. PR Don't pack .NET Standard #32203 (the blanket netstandard → IsPackable=false rule, commit 543b1ebeb7) is on inflight/current only and is not an ancestor of main/net10.0. When Don't pack .NET Standard #32203 forward-ports, this and '$(IsPackable)' == '' guard must travel with it, or the same MSB4019 nightly break reappears.

— 3-model consensus (Opus 4.8 / GPT-5.5 / Gemini 3.1 Pro).

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.

Confirmed — the wider blast radius is intended. The guard restores packability for the 6 netstandard-only opt-in projects (the 2 workload packs + Resizetizer, Controls.Build.Tasks, Controls.SourceGen, Graphics.Text.Markdig), which exactly matches main, where #32203's blanket rule never existed. So this is a main-parity restore, not new shipping behavior.

Good catch that #32203 was over-broad: the workload/MSI leg just failed loudest (the missing-file import is fatal at evaluation → MSB4019), while the other four would have silently stopped packing on inflight/current.

Updated the PR description with a Scope / blast radius table reflecting all 6 projects. Forward-port acknowledged — the '$(IsPackable)' == '' guard must travel with #32203 when it lands on net10.0/main, or the break reappears.

Make the vs-workload.props import conditional on Exists(...) and add a
scoped _EnsureWorkloadMsiGenProps guard (a dependency of _GenerateAndSignMsis)
that fails fast with an actionable message naming the root cause, instead of
the cryptic evaluation-time MSB4019 from the unconditional import.

The guard only runs on the build/MSI path (via _GenerateAndSignMsis), so
restore and unrelated evaluations never trip it on clean checkouts where the
props legitimately has not been generated yet. The success path is unchanged
when the file exists.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@kubaflo kubaflo 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.

Note

🤖 This review was generated by AI — an automated multi-model code review (Claude Opus 4.8 · GPT-5.5 · Gemini 3.1 Pro), reconciled across models.


🔍 AI Multi-Model Code Review — PR #36089 · Round 2

HEAD: 98a13b38 (base inflight/current) — adds a fail-fast guard on top of the R1 IsPackable fix.

🟡 Verdict: NEEDS_DISCUSSION (approve-adjacent)

Confidence: High — unanimous (all 3 models). The new fail-fast is correct (failfast_correct: true) with no real spurious-fire risk (spurious_fire_risk: false).

✅ The Round 2 fail-fast is a good defensive addition

The new commit turns the cryptic MSB4019 into an actionable, self-routing error:

  • <Import Project="$(WorkloadMsiGenProps)" Condition="Exists(...)" /> — when the file is present, behaves exactly as before; when absent, the project still evaluates (the default-value PropertyGroup below applies) instead of throwing at the import line.
  • _EnsureWorkloadMsiGenProps raises a clear <Error> that names the root cause (the manifest pack ran with IsPackable=false; the official Pack, Sign step runs -pack without -build, so _GenerateVSWorkloadProps AfterTargets=Build never ran) and routes to #36089/#36088.
  • It's wired only into the MSI/sign build path (_GenerateAndSignMsisSignFiles); restore, design-time, and unrelated evaluations don't trigger it — verified by all 3 models, no real-build breakage.

⚖️ Why NEEDS_DISCUSSION (not LGTM)

maui-pr is currently pending (fresh build 1479553 running on this commit). Per the code-review skill Rule #6, the bot does not post LGTM on pending required CI. This remains the approve-adjacent situation from R1: the fix is correct and resolves the 8-night nightly outage (Pack Windows went red→green on the PR build); the merge decision is a human call.

💡 Non-blocking notes

  1. Comment accuracy (inline @ workloads.csproj:123): "cannot fire spuriously on clean checkouts" is slightly overstated — a direct dotnet build of workloads.csproj via BuildDependsOn would hit the fail-fast on a clean checkout. Not a regression (it failed harder before); just narrow the wording.
  2. Defense-in-depth (inline): evaluation-time conditional import vs target-time Exists check — a theoretical mid-invocation gap, not exercised by the real build.
  3. Forward-port (carried from R1, see the open thread on Directory.Build.targets) — both the IsPackable guard and this fail-fast must travel to main/netN.0/release branches that run Pack/Sign, or the break recurs. The wider-blast-radius note (~6 netstandard opt-in packs, matches main) also still stands.

🏁 Recommendation

NEEDS_DISCUSSION → approve-adjacent. No code changes requested — the R1 fix is correct and the R2 fail-fast is a clean, well-scoped improvement that makes the failure mode self-diagnosing. Once the pending maui-pr build resolves (and given every prior red leg was pre-existing base flakiness), a maintainer can merge. Remember the forward-port.


Reviewed with the code-review skill: independence-first, full-file reading, MSBuild target-graph tracing (BuildDependsOn/_GenerateAndSignMsis), and CI verification.

Comment thread src/Workload/workloads.csproj Outdated
Address review feedback: the previous comment overstated that the guard
'cannot fire spuriously on clean checkouts'. A full Build of workloads.csproj
does require vs-workload.props by design; restore, design-time builds, and
unrelated target invocations are what never reach the guard. Comment-only
change - no behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@kubaflo kubaflo 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.

Note

🤖 AI-generated update (multi-model review automation).

CI update — the maui-pr build that was pending at my earlier rounds has now completed, and it confirms the earlier classification:

✅ Everything the fix touches is GREEN

  • Build .NET MAUI Windows + macOS (Debug & Release): pass
  • Pack .NET MAUI Windows + macOS: pass ← this is the leg the IsPackable/vs-workload.props fix exists to repair, and it's green. The fix works.

❌ The red legs are the pre-existing inflight/current base flakiness (not this PR)

The failing legs are Helix Unit Tests Windows (Debug/Release) and a subset of Integration Tests (Build macOS/windows, MultiProject macOS, several RunOniOS_Blazor/MauiRelease, Samples, WindowsTemplates). These match the pre-existing base failures identified in the round-1 review (the Helix unit-test failures reproduce on already-merged PRs on this branch). Critically, the pattern is not a packability regression: a real IsPackable break would take down the Build/Pack legs first — but those are all green, and the failures are scattered across test legs (with many integration legs, e.g. AOT, RunOnAndroid, MauiDebug/NativeAOT/TrimFull, also passing).

Net

The fix is correct and verified (Build + Pack green). The remaining red is pre-existing base-branch flakiness, not caused by this 1-line IsPackable guard + the fail-fast. Per review policy the bot still doesn't auto-LGTM over a red required maui-pr rollup — but this is the approve-adjacent case: a maintainer can reasonably merge it as a pre-existing-red-only, nightly-outage fix. (Forward-port reminder still stands — carry the Directory.Build.targets guard + the workloads.csproj fail-fast to main/netN.0 when #32203 flows.)

@kubaflo kubaflo 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.

Note

🤖 AI-generated approval (multi-model review automation, approving on @kubaflo's behalf).

Approving — the IsPackable guard + workloads.csproj fail-fast are correct and verified: the maui-pr Build (Windows/macOS) and Pack (Windows/macOS) legs are green — and Pack Windows is exactly the leg that was red on the base and that this PR repairs (the 8-night vs-workload.props MSB4019 nightly outage).

The remaining red maui-pr legs (Helix Unit Tests Windows + a subset of Integration Tests) are pre-existing inflight/current base flakiness — they reproduce on already-merged PRs on this branch and are not caused by this change (a real packability regression would take down Build/Pack first; those are green). Approving over the known-pre-existing red rollup.

Forward-port reminder: carry both the Directory.Build.targets IsPackable guard and the workloads.csproj fail-fast to main/netN.0 when #32203 flows, or the MSB4019 break recurs there.

@PureWeen
PureWeen merged commit ea54f42 into inflight/current Jun 25, 2026
21 of 38 checks passed
@PureWeen
PureWeen deleted the pureween-fix-nightly-vs-workload-props branch June 25, 2026 16:19
@PureWeen

Copy link
Copy Markdown
Member Author

/rebase

@github-actions github-actions Bot added this to the .NET 10 SR9 milestone Jun 25, 2026
kubaflo pushed a commit that referenced this pull request Jun 25, 2026
…s MSB4019) (#36089)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Fixes the **nightly official signed build** (`dotnet-maui`, definition
1095, dnceng/internal), which has
failed **8 consecutive nights** on `refs/heads/inflight/current`. The
Windows `Pack` job → step
**"Build Workloads, Sign & Publish"** fails at restore/evaluation:

```
src\Workload\workloads.csproj(30,3): error MSB4019: The imported project
"...\artifacts\packages\Release\Shipping\vs-workload.props" was not found.
```

### Root cause

The workload packs — `Microsoft.NET.Sdk.Maui.Manifest` and
`Microsoft.Maui.Sdk` — both target
**`netstandard2.0`** (via `src/Workload/Shared/Common.targets`) and both
**explicitly** set
`<IsPackable>true</IsPackable>` because they *are* the shipping workload
packages.

PR #32203 ("Don't pack .NET Standard") added a blanket rule to
`Directory.Build.targets`:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard'))">false</IsPackable>
```

`Directory.Build.targets` is auto-imported at the **end** of every
project, so this assignment runs
*after* the project body and **overrides** the explicit
`<IsPackable>true</IsPackable>` on the workload
packs. The net effect: the manifest project evaluates to
`IsPackable=false`.

The chain that breaks from there:

1. With `IsPackable=false`, NuGet's `Pack` target no-ops.
2. The official **"Pack, Sign"** step
(`eng/pipelines/arcade/stage-pack.yml`) runs `-restore -pack -sign`
**without `-build`**, so the `Build` target never runs for the manifest
project.
3. `_GenerateVSWorkloadProps` (in
`Microsoft.NET.Sdk.Maui.Manifest.csproj`) is hooked
`AfterTargets="Build"`, so it never runs → `vs-workload.props` is never
written to
   `$(ArtifactsShippingPackagesDir)`.
4. The next step (`-build … -projects src/Workload/workloads.csproj`)
hits `workloads.csproj` line 30,
whose `<Import Project="$(WorkloadMsiGenProps)" />` is unconditional and
fails at evaluation → **MSB4019**.

The blanket rule is also **over-broad**: it clobbers the explicit opt-in
on *every* netstandard-only
project, not just the workload packs (see **Scope** below). The
workload/MSI leg simply failed loudest
because its missing-file import is fatal at evaluation (MSB4019); the
others would silently stop shipping.

**Why `main` is green:** PR #32203 is on `inflight/current` only — it is
*not* an ancestor of `main` or
`net10.0` (verified with `git merge-base --is-ancestor`). `main` and
`inflight/current` otherwise share
identical `global.json` (dotnet `10.0.108`, arcade `25555.106`),
identical `src/Workload`, and identical
`Microsoft.Build.NoTargets 3.7.0`. The only relevant difference is the
#32203 block, which is why this
break is specific to `inflight/current`.

### Fix

Guard the #32203 rule so it only applies when a project has **not**
explicitly opted into packing:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard')) and '$(IsPackable)' == ''">false</IsPackable>
```

- Projects that explicitly set `<IsPackable>true</IsPackable>` (the
workload packs) are respected and
  pack again — restoring `vs-workload.props` generation.
- Unmarked netstandard projects still default to not-packable,
**preserving #32203's intent**.

This restores the exact condition (`IsPackable=true` for the manifest
project) that the passing `main`
build exhibits, where `_GenerateVSWorkloadProps` runs and
`vs-workload.props` is produced.

### Scope / blast radius

Because #32203's blanket rule was over-broad, this guard restores
packability for **6** netstandard-only
projects that explicitly opt in — not just the 2 workload packs:

| Project | TFM |
|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` | `netstandard2.0` |
| `Microsoft.Maui.Sdk` | `netstandard2.0` |
| `Resizetizer` | `netstandard2.0` |
| `Controls.Build.Tasks` | `netstandard2.0` |
| `Controls.SourceGen` | `netstandard2.0` |
| `Graphics.Text.Markdig` | `netstandard2.0` |

All 6 pack on `main` (which never had the #32203 rule), so this is a
**`main`-parity restore**, not new
shipping behavior. Multi-target libraries (e.g. `Core`, `Controls.Core`)
are unaffected: `dotnet pack`
evaluates `IsPackable` at the outer level where `TargetFramework=""`, so
the netstandard rule never fires
for them.

### How validated

Root cause confirmed from the actual failing/​passing build binlogs (not
guesswork):

| | failing `inflight/current` build `3006161` | passing `main` build
`3006214` |
|---|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` `IsPackable` | **`false`** |
**`true`** |
| `_GenerateVSWorkloadProps` ran? | no (Build never ran) | yes →
`vs-workload.props` produced |

Verified locally on the `inflight/current` tree with
`dotnet msbuild …Manifest.csproj -getProperty:IsPackable`:

- **Before** fix: manifest `IsPackable=false` (reproduces the break).
- **After** fix: manifest `IsPackable=true`, `Microsoft.Maui.Sdk`
`IsPackable=true`.
- `Microsoft.Maui.Core` (multi-target) at pack time is unchanged
(`IsPackable=true`, `TargetFramework=""`;
the netstandard rule only fires for single-TFM netstandard evaluations,
so multi-target library
  packaging is unaffected).
- A netstandard project with no explicit opt-in
(`Controls.CustomAttributes`) still resolves
  `IsPackable=false` — #32203's intent preserved.

The full signed Windows MSI leg only runs in the official pipeline; this
change restores the property
state that gates the entire pack → `vs-workload.props` → workloads
handoff.

**PR CI confirms the fix end-to-end:** on base `inflight/current` the
`Pack Windows` leg fails at the
root-cause spot; on this PR the same leg is green. The remaining red
`maui-pr` legs are pre-existing on
`inflight/current` (Windows Helix unit-test flakiness identical on base,
and integration legs that were
*skipped on base* because Pack was blocked and only run now that the fix
unblocked Pack).

### Follow-up note

When PR #32203's `Directory.Build.targets` change is forward-ported to
`net10.0`/`main`, it must carry
this `'$(IsPackable)' == ''` guard, otherwise the same break will
reappear on those branches.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 3, 2026
…s MSB4019) (#36089)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Fixes the **nightly official signed build** (`dotnet-maui`, definition
1095, dnceng/internal), which has
failed **8 consecutive nights** on `refs/heads/inflight/current`. The
Windows `Pack` job → step
**"Build Workloads, Sign & Publish"** fails at restore/evaluation:

```
src\Workload\workloads.csproj(30,3): error MSB4019: The imported project
"...\artifacts\packages\Release\Shipping\vs-workload.props" was not found.
```

### Root cause

The workload packs — `Microsoft.NET.Sdk.Maui.Manifest` and
`Microsoft.Maui.Sdk` — both target
**`netstandard2.0`** (via `src/Workload/Shared/Common.targets`) and both
**explicitly** set
`<IsPackable>true</IsPackable>` because they *are* the shipping workload
packages.

PR #32203 ("Don't pack .NET Standard") added a blanket rule to
`Directory.Build.targets`:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard'))">false</IsPackable>
```

`Directory.Build.targets` is auto-imported at the **end** of every
project, so this assignment runs
*after* the project body and **overrides** the explicit
`<IsPackable>true</IsPackable>` on the workload
packs. The net effect: the manifest project evaluates to
`IsPackable=false`.

The chain that breaks from there:

1. With `IsPackable=false`, NuGet's `Pack` target no-ops.
2. The official **"Pack, Sign"** step
(`eng/pipelines/arcade/stage-pack.yml`) runs `-restore -pack -sign`
**without `-build`**, so the `Build` target never runs for the manifest
project.
3. `_GenerateVSWorkloadProps` (in
`Microsoft.NET.Sdk.Maui.Manifest.csproj`) is hooked
`AfterTargets="Build"`, so it never runs → `vs-workload.props` is never
written to
   `$(ArtifactsShippingPackagesDir)`.
4. The next step (`-build … -projects src/Workload/workloads.csproj`)
hits `workloads.csproj` line 30,
whose `<Import Project="$(WorkloadMsiGenProps)" />` is unconditional and
fails at evaluation → **MSB4019**.

The blanket rule is also **over-broad**: it clobbers the explicit opt-in
on *every* netstandard-only
project, not just the workload packs (see **Scope** below). The
workload/MSI leg simply failed loudest
because its missing-file import is fatal at evaluation (MSB4019); the
others would silently stop shipping.

**Why `main` is green:** PR #32203 is on `inflight/current` only — it is
*not* an ancestor of `main` or
`net10.0` (verified with `git merge-base --is-ancestor`). `main` and
`inflight/current` otherwise share
identical `global.json` (dotnet `10.0.108`, arcade `25555.106`),
identical `src/Workload`, and identical
`Microsoft.Build.NoTargets 3.7.0`. The only relevant difference is the
#32203 block, which is why this
break is specific to `inflight/current`.

### Fix

Guard the #32203 rule so it only applies when a project has **not**
explicitly opted into packing:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard')) and '$(IsPackable)' == ''">false</IsPackable>
```

- Projects that explicitly set `<IsPackable>true</IsPackable>` (the
workload packs) are respected and
  pack again — restoring `vs-workload.props` generation.
- Unmarked netstandard projects still default to not-packable,
**preserving #32203's intent**.

This restores the exact condition (`IsPackable=true` for the manifest
project) that the passing `main`
build exhibits, where `_GenerateVSWorkloadProps` runs and
`vs-workload.props` is produced.

### Scope / blast radius

Because #32203's blanket rule was over-broad, this guard restores
packability for **6** netstandard-only
projects that explicitly opt in — not just the 2 workload packs:

| Project | TFM |
|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` | `netstandard2.0` |
| `Microsoft.Maui.Sdk` | `netstandard2.0` |
| `Resizetizer` | `netstandard2.0` |
| `Controls.Build.Tasks` | `netstandard2.0` |
| `Controls.SourceGen` | `netstandard2.0` |
| `Graphics.Text.Markdig` | `netstandard2.0` |

All 6 pack on `main` (which never had the #32203 rule), so this is a
**`main`-parity restore**, not new
shipping behavior. Multi-target libraries (e.g. `Core`, `Controls.Core`)
are unaffected: `dotnet pack`
evaluates `IsPackable` at the outer level where `TargetFramework=""`, so
the netstandard rule never fires
for them.

### How validated

Root cause confirmed from the actual failing/​passing build binlogs (not
guesswork):

| | failing `inflight/current` build `3006161` | passing `main` build
`3006214` |
|---|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` `IsPackable` | **`false`** |
**`true`** |
| `_GenerateVSWorkloadProps` ran? | no (Build never ran) | yes →
`vs-workload.props` produced |

Verified locally on the `inflight/current` tree with
`dotnet msbuild …Manifest.csproj -getProperty:IsPackable`:

- **Before** fix: manifest `IsPackable=false` (reproduces the break).
- **After** fix: manifest `IsPackable=true`, `Microsoft.Maui.Sdk`
`IsPackable=true`.
- `Microsoft.Maui.Core` (multi-target) at pack time is unchanged
(`IsPackable=true`, `TargetFramework=""`;
the netstandard rule only fires for single-TFM netstandard evaluations,
so multi-target library
  packaging is unaffected).
- A netstandard project with no explicit opt-in
(`Controls.CustomAttributes`) still resolves
  `IsPackable=false` — #32203's intent preserved.

The full signed Windows MSI leg only runs in the official pipeline; this
change restores the property
state that gates the entire pack → `vs-workload.props` → workloads
handoff.

**PR CI confirms the fix end-to-end:** on base `inflight/current` the
`Pack Windows` leg fails at the
root-cause spot; on this PR the same leg is green. The remaining red
`maui-pr` legs are pre-existing on
`inflight/current` (Windows Helix unit-test flakiness identical on base,
and integration legs that were
*skipped on base* because Pack was blocked and only run now that the fix
unblocked Pack).

### Follow-up note

When PR #32203's `Directory.Build.targets` change is forward-ported to
`net10.0`/`main`, it must carry
this `'$(IsPackable)' == ''` guard, otherwise the same break will
reappear on those branches.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen added a commit that referenced this pull request Jul 6, 2026
<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description

Docs/skill-only change. Adds a deterministic breadcrumb so a bare
request like **"nightly is broken please fix"** routes an agent (or a
human) straight to the right pipeline and the known failure surface,
instead of guessing.

Today nothing in the repo maps **"nightly" / "inflight feed" / "dogfood
feed"** to the official signed build. `copilot-instructions.md`
documents only the **PR** pipelines (`maui-pr`, `-devicetests`,
`-uitests`), and `azdo-build-investigator` mentioned `ci-official.yml`
only tangentially (CFSClean/Gradle). So an agent told the dogfood feed
is stale had no way to know the AzDO definition id (1095) or org
(`dnceng/internal`) to even query the builds.

This matters now because we just shipped the **nightly dogfood feed
staleness banner** (#36066) — when that banner turns ❌, the natural next
question is *"which pipeline do I go fix?"* and the repo couldn't answer
it.

### What changed

- **`.github/skills/azdo-build-investigator/SKILL.md`**
- Added `nightly` / `inflight feed` / `dogfood feed` / `official build`
trigger phrases to the skill `description` (fixes routing).
- New **"Nightly / Official Signed Build (inflight dogfood feed)"**
section: the pipeline identity (`dotnet-maui`, definition **1095**,
`dnceng`/`internal`, defined by `eng/pipelines/ci-official.yml`, cron
`05:00 UTC` on `inflight/current`), the AzDO investigation recipe
(`azdo_builds` by `definitionId`+branch → `azdo_search_timeline` →
`azdo_search_log`), and the recurring **`vs-workload.props`
(`MSB4019`)** failure in the `Pack Windows` → "Build Workloads, Sign &
Publish" step. Plus a note that the macOS leg can pass while Windows
fails, and that the release pipelines are distinct.
  - Added a matching row to the failure-pattern table.
- **`.github/skills/release-readiness/SKILL.md`**
- Cross-linked the ❌ staleness banner to the investigator section, so
the banner's existing "check the nightly pipeline" hint has a documented
target.

### Grounding

Every fact was confirmed against the live failing run — definition
`1095` / `dnceng-internal`, the `ci-official.yml` schedule,
`stage-pack.yml:150` ("Build Workloads, Sign & Publish"), and the
`MSB4019: vs-workload.props was not found` signature on
`inflight/current` (a multi-day streak as of this writing). No
product/runtime code is touched.

### Risk

Documentation only. No code paths, no tests affected. `ci-official.yml`
excludes `.github/**`, and `maui-pr` skips `.github/**`-only PRs by
design.

### Update — confirmed root cause folded in

A companion investigation (fix PR #36089, base `inflight/current`)
binlog-proved the actual cause of the current outage, now documented in
the breadcrumb:

- The workload packs (`Microsoft.NET.Sdk.Maui.Manifest`,
`Microsoft.Maui.Sdk`) target netstandard but ship, so they set
`<IsPackable>true</IsPackable>` explicitly.
- PR #32203's blanket `<IsPackable
Condition="$(TargetFramework.Contains('netstandard'))">false</IsPackable>`
in `Directory.Build.targets` (auto-imported last) overrode that →
`IsPackable=false`.
- The "Pack, Sign" step runs `-pack` **without** `-build`, so `Build`
never ran → `_GenerateVSWorkloadProps` (`AfterTargets="Build"`) never
wrote `vs-workload.props` → the line-30 import threw `MSB4019`.
- Regression is **`inflight/current`-only** (the #32203 commit isn't on
`main`/`net10.0` yet) — forward-port must carry the `and '$(IsPackable)'
== ''` guard or the break reappears.

The breadcrumb now teaches the real failure mechanism, not just the
symptom, and links the fix.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kubaflo kubaflo mentioned this pull request Jul 6, 2026
kubaflo pushed a commit that referenced this pull request Jul 6, 2026
…s MSB4019) (#36089)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Fixes the **nightly official signed build** (`dotnet-maui`, definition
1095, dnceng/internal), which has
failed **8 consecutive nights** on `refs/heads/inflight/current`. The
Windows `Pack` job → step
**"Build Workloads, Sign & Publish"** fails at restore/evaluation:

```
src\Workload\workloads.csproj(30,3): error MSB4019: The imported project
"...\artifacts\packages\Release\Shipping\vs-workload.props" was not found.
```

### Root cause

The workload packs — `Microsoft.NET.Sdk.Maui.Manifest` and
`Microsoft.Maui.Sdk` — both target
**`netstandard2.0`** (via `src/Workload/Shared/Common.targets`) and both
**explicitly** set
`<IsPackable>true</IsPackable>` because they *are* the shipping workload
packages.

PR #32203 ("Don't pack .NET Standard") added a blanket rule to
`Directory.Build.targets`:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard'))">false</IsPackable>
```

`Directory.Build.targets` is auto-imported at the **end** of every
project, so this assignment runs
*after* the project body and **overrides** the explicit
`<IsPackable>true</IsPackable>` on the workload
packs. The net effect: the manifest project evaluates to
`IsPackable=false`.

The chain that breaks from there:

1. With `IsPackable=false`, NuGet's `Pack` target no-ops.
2. The official **"Pack, Sign"** step
(`eng/pipelines/arcade/stage-pack.yml`) runs `-restore -pack -sign`
**without `-build`**, so the `Build` target never runs for the manifest
project.
3. `_GenerateVSWorkloadProps` (in
`Microsoft.NET.Sdk.Maui.Manifest.csproj`) is hooked
`AfterTargets="Build"`, so it never runs → `vs-workload.props` is never
written to
   `$(ArtifactsShippingPackagesDir)`.
4. The next step (`-build … -projects src/Workload/workloads.csproj`)
hits `workloads.csproj` line 30,
whose `<Import Project="$(WorkloadMsiGenProps)" />` is unconditional and
fails at evaluation → **MSB4019**.

The blanket rule is also **over-broad**: it clobbers the explicit opt-in
on *every* netstandard-only
project, not just the workload packs (see **Scope** below). The
workload/MSI leg simply failed loudest
because its missing-file import is fatal at evaluation (MSB4019); the
others would silently stop shipping.

**Why `main` is green:** PR #32203 is on `inflight/current` only — it is
*not* an ancestor of `main` or
`net10.0` (verified with `git merge-base --is-ancestor`). `main` and
`inflight/current` otherwise share
identical `global.json` (dotnet `10.0.108`, arcade `25555.106`),
identical `src/Workload`, and identical
`Microsoft.Build.NoTargets 3.7.0`. The only relevant difference is the
#32203 block, which is why this
break is specific to `inflight/current`.

### Fix

Guard the #32203 rule so it only applies when a project has **not**
explicitly opted into packing:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard')) and '$(IsPackable)' == ''">false</IsPackable>
```

- Projects that explicitly set `<IsPackable>true</IsPackable>` (the
workload packs) are respected and
  pack again — restoring `vs-workload.props` generation.
- Unmarked netstandard projects still default to not-packable,
**preserving #32203's intent**.

This restores the exact condition (`IsPackable=true` for the manifest
project) that the passing `main`
build exhibits, where `_GenerateVSWorkloadProps` runs and
`vs-workload.props` is produced.

### Scope / blast radius

Because #32203's blanket rule was over-broad, this guard restores
packability for **6** netstandard-only
projects that explicitly opt in — not just the 2 workload packs:

| Project | TFM |
|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` | `netstandard2.0` |
| `Microsoft.Maui.Sdk` | `netstandard2.0` |
| `Resizetizer` | `netstandard2.0` |
| `Controls.Build.Tasks` | `netstandard2.0` |
| `Controls.SourceGen` | `netstandard2.0` |
| `Graphics.Text.Markdig` | `netstandard2.0` |

All 6 pack on `main` (which never had the #32203 rule), so this is a
**`main`-parity restore**, not new
shipping behavior. Multi-target libraries (e.g. `Core`, `Controls.Core`)
are unaffected: `dotnet pack`
evaluates `IsPackable` at the outer level where `TargetFramework=""`, so
the netstandard rule never fires
for them.

### How validated

Root cause confirmed from the actual failing/​passing build binlogs (not
guesswork):

| | failing `inflight/current` build `3006161` | passing `main` build
`3006214` |
|---|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` `IsPackable` | **`false`** |
**`true`** |
| `_GenerateVSWorkloadProps` ran? | no (Build never ran) | yes →
`vs-workload.props` produced |

Verified locally on the `inflight/current` tree with
`dotnet msbuild …Manifest.csproj -getProperty:IsPackable`:

- **Before** fix: manifest `IsPackable=false` (reproduces the break).
- **After** fix: manifest `IsPackable=true`, `Microsoft.Maui.Sdk`
`IsPackable=true`.
- `Microsoft.Maui.Core` (multi-target) at pack time is unchanged
(`IsPackable=true`, `TargetFramework=""`;
the netstandard rule only fires for single-TFM netstandard evaluations,
so multi-target library
  packaging is unaffected).
- A netstandard project with no explicit opt-in
(`Controls.CustomAttributes`) still resolves
  `IsPackable=false` — #32203's intent preserved.

The full signed Windows MSI leg only runs in the official pipeline; this
change restores the property
state that gates the entire pack → `vs-workload.props` → workloads
handoff.

**PR CI confirms the fix end-to-end:** on base `inflight/current` the
`Pack Windows` leg fails at the
root-cause spot; on this PR the same leg is green. The remaining red
`maui-pr` legs are pre-existing on
`inflight/current` (Windows Helix unit-test flakiness identical on base,
and integration legs that were
*skipped on base* because Pack was blocked and only run now that the fix
unblocked Pack).

### Follow-up note

When PR #32203's `Directory.Build.targets` change is forward-ported to
`net10.0`/`main`, it must carry
this `'$(IsPackable)' == ''` guard, otherwise the same break will
reappear on those branches.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen added a commit that referenced this pull request Jul 7, 2026
…s MSB4019) (#36089)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Fixes the **nightly official signed build** (`dotnet-maui`, definition
1095, dnceng/internal), which has
failed **8 consecutive nights** on `refs/heads/inflight/current`. The
Windows `Pack` job → step
**"Build Workloads, Sign & Publish"** fails at restore/evaluation:

```
src\Workload\workloads.csproj(30,3): error MSB4019: The imported project
"...\artifacts\packages\Release\Shipping\vs-workload.props" was not found.
```

### Root cause

The workload packs — `Microsoft.NET.Sdk.Maui.Manifest` and
`Microsoft.Maui.Sdk` — both target
**`netstandard2.0`** (via `src/Workload/Shared/Common.targets`) and both
**explicitly** set
`<IsPackable>true</IsPackable>` because they *are* the shipping workload
packages.

PR #32203 ("Don't pack .NET Standard") added a blanket rule to
`Directory.Build.targets`:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard'))">false</IsPackable>
```

`Directory.Build.targets` is auto-imported at the **end** of every
project, so this assignment runs
*after* the project body and **overrides** the explicit
`<IsPackable>true</IsPackable>` on the workload
packs. The net effect: the manifest project evaluates to
`IsPackable=false`.

The chain that breaks from there:

1. With `IsPackable=false`, NuGet's `Pack` target no-ops.
2. The official **"Pack, Sign"** step
(`eng/pipelines/arcade/stage-pack.yml`) runs `-restore -pack -sign`
**without `-build`**, so the `Build` target never runs for the manifest
project.
3. `_GenerateVSWorkloadProps` (in
`Microsoft.NET.Sdk.Maui.Manifest.csproj`) is hooked
`AfterTargets="Build"`, so it never runs → `vs-workload.props` is never
written to
   `$(ArtifactsShippingPackagesDir)`.
4. The next step (`-build … -projects src/Workload/workloads.csproj`)
hits `workloads.csproj` line 30,
whose `<Import Project="$(WorkloadMsiGenProps)" />` is unconditional and
fails at evaluation → **MSB4019**.

The blanket rule is also **over-broad**: it clobbers the explicit opt-in
on *every* netstandard-only
project, not just the workload packs (see **Scope** below). The
workload/MSI leg simply failed loudest
because its missing-file import is fatal at evaluation (MSB4019); the
others would silently stop shipping.

**Why `main` is green:** PR #32203 is on `inflight/current` only — it is
*not* an ancestor of `main` or
`net10.0` (verified with `git merge-base --is-ancestor`). `main` and
`inflight/current` otherwise share
identical `global.json` (dotnet `10.0.108`, arcade `25555.106`),
identical `src/Workload`, and identical
`Microsoft.Build.NoTargets 3.7.0`. The only relevant difference is the
#32203 block, which is why this
break is specific to `inflight/current`.

### Fix

Guard the #32203 rule so it only applies when a project has **not**
explicitly opted into packing:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard')) and '$(IsPackable)' == ''">false</IsPackable>
```

- Projects that explicitly set `<IsPackable>true</IsPackable>` (the
workload packs) are respected and
  pack again — restoring `vs-workload.props` generation.
- Unmarked netstandard projects still default to not-packable,
**preserving #32203's intent**.

This restores the exact condition (`IsPackable=true` for the manifest
project) that the passing `main`
build exhibits, where `_GenerateVSWorkloadProps` runs and
`vs-workload.props` is produced.

### Scope / blast radius

Because #32203's blanket rule was over-broad, this guard restores
packability for **6** netstandard-only
projects that explicitly opt in — not just the 2 workload packs:

| Project | TFM |
|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` | `netstandard2.0` |
| `Microsoft.Maui.Sdk` | `netstandard2.0` |
| `Resizetizer` | `netstandard2.0` |
| `Controls.Build.Tasks` | `netstandard2.0` |
| `Controls.SourceGen` | `netstandard2.0` |
| `Graphics.Text.Markdig` | `netstandard2.0` |

All 6 pack on `main` (which never had the #32203 rule), so this is a
**`main`-parity restore**, not new
shipping behavior. Multi-target libraries (e.g. `Core`, `Controls.Core`)
are unaffected: `dotnet pack`
evaluates `IsPackable` at the outer level where `TargetFramework=""`, so
the netstandard rule never fires
for them.

### How validated

Root cause confirmed from the actual failing/​passing build binlogs (not
guesswork):

| | failing `inflight/current` build `3006161` | passing `main` build
`3006214` |
|---|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` `IsPackable` | **`false`** |
**`true`** |
| `_GenerateVSWorkloadProps` ran? | no (Build never ran) | yes →
`vs-workload.props` produced |

Verified locally on the `inflight/current` tree with
`dotnet msbuild …Manifest.csproj -getProperty:IsPackable`:

- **Before** fix: manifest `IsPackable=false` (reproduces the break).
- **After** fix: manifest `IsPackable=true`, `Microsoft.Maui.Sdk`
`IsPackable=true`.
- `Microsoft.Maui.Core` (multi-target) at pack time is unchanged
(`IsPackable=true`, `TargetFramework=""`;
the netstandard rule only fires for single-TFM netstandard evaluations,
so multi-target library
  packaging is unaffected).
- A netstandard project with no explicit opt-in
(`Controls.CustomAttributes`) still resolves
  `IsPackable=false` — #32203's intent preserved.

The full signed Windows MSI leg only runs in the official pipeline; this
change restores the property
state that gates the entire pack → `vs-workload.props` → workloads
handoff.

**PR CI confirms the fix end-to-end:** on base `inflight/current` the
`Pack Windows` leg fails at the
root-cause spot; on this PR the same leg is green. The remaining red
`maui-pr` legs are pre-existing on
`inflight/current` (Windows Helix unit-test flakiness identical on base,
and integration legs that were
*skipped on base* because Pack was blocked and only run now that the fix
unblocked Pack).

### Follow-up note

When PR #32203's `Directory.Build.targets` change is forward-ported to
`net10.0`/`main`, it must carry
this `'$(IsPackable)' == ''` guard, otherwise the same break will
reappear on those branches.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen added a commit that referenced this pull request Jul 7, 2026
…s MSB4019) (#36089)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Fixes the **nightly official signed build** (`dotnet-maui`, definition
1095, dnceng/internal), which has
failed **8 consecutive nights** on `refs/heads/inflight/current`. The
Windows `Pack` job → step
**"Build Workloads, Sign & Publish"** fails at restore/evaluation:

```
src\Workload\workloads.csproj(30,3): error MSB4019: The imported project
"...\artifacts\packages\Release\Shipping\vs-workload.props" was not found.
```

### Root cause

The workload packs — `Microsoft.NET.Sdk.Maui.Manifest` and
`Microsoft.Maui.Sdk` — both target
**`netstandard2.0`** (via `src/Workload/Shared/Common.targets`) and both
**explicitly** set
`<IsPackable>true</IsPackable>` because they *are* the shipping workload
packages.

PR #32203 ("Don't pack .NET Standard") added a blanket rule to
`Directory.Build.targets`:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard'))">false</IsPackable>
```

`Directory.Build.targets` is auto-imported at the **end** of every
project, so this assignment runs
*after* the project body and **overrides** the explicit
`<IsPackable>true</IsPackable>` on the workload
packs. The net effect: the manifest project evaluates to
`IsPackable=false`.

The chain that breaks from there:

1. With `IsPackable=false`, NuGet's `Pack` target no-ops.
2. The official **"Pack, Sign"** step
(`eng/pipelines/arcade/stage-pack.yml`) runs `-restore -pack -sign`
**without `-build`**, so the `Build` target never runs for the manifest
project.
3. `_GenerateVSWorkloadProps` (in
`Microsoft.NET.Sdk.Maui.Manifest.csproj`) is hooked
`AfterTargets="Build"`, so it never runs → `vs-workload.props` is never
written to
   `$(ArtifactsShippingPackagesDir)`.
4. The next step (`-build … -projects src/Workload/workloads.csproj`)
hits `workloads.csproj` line 30,
whose `<Import Project="$(WorkloadMsiGenProps)" />` is unconditional and
fails at evaluation → **MSB4019**.

The blanket rule is also **over-broad**: it clobbers the explicit opt-in
on *every* netstandard-only
project, not just the workload packs (see **Scope** below). The
workload/MSI leg simply failed loudest
because its missing-file import is fatal at evaluation (MSB4019); the
others would silently stop shipping.

**Why `main` is green:** PR #32203 is on `inflight/current` only — it is
*not* an ancestor of `main` or
`net10.0` (verified with `git merge-base --is-ancestor`). `main` and
`inflight/current` otherwise share
identical `global.json` (dotnet `10.0.108`, arcade `25555.106`),
identical `src/Workload`, and identical
`Microsoft.Build.NoTargets 3.7.0`. The only relevant difference is the
#32203 block, which is why this
break is specific to `inflight/current`.

### Fix

Guard the #32203 rule so it only applies when a project has **not**
explicitly opted into packing:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard')) and '$(IsPackable)' == ''">false</IsPackable>
```

- Projects that explicitly set `<IsPackable>true</IsPackable>` (the
workload packs) are respected and
  pack again — restoring `vs-workload.props` generation.
- Unmarked netstandard projects still default to not-packable,
**preserving #32203's intent**.

This restores the exact condition (`IsPackable=true` for the manifest
project) that the passing `main`
build exhibits, where `_GenerateVSWorkloadProps` runs and
`vs-workload.props` is produced.

### Scope / blast radius

Because #32203's blanket rule was over-broad, this guard restores
packability for **6** netstandard-only
projects that explicitly opt in — not just the 2 workload packs:

| Project | TFM |
|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` | `netstandard2.0` |
| `Microsoft.Maui.Sdk` | `netstandard2.0` |
| `Resizetizer` | `netstandard2.0` |
| `Controls.Build.Tasks` | `netstandard2.0` |
| `Controls.SourceGen` | `netstandard2.0` |
| `Graphics.Text.Markdig` | `netstandard2.0` |

All 6 pack on `main` (which never had the #32203 rule), so this is a
**`main`-parity restore**, not new
shipping behavior. Multi-target libraries (e.g. `Core`, `Controls.Core`)
are unaffected: `dotnet pack`
evaluates `IsPackable` at the outer level where `TargetFramework=""`, so
the netstandard rule never fires
for them.

### How validated

Root cause confirmed from the actual failing/​passing build binlogs (not
guesswork):

| | failing `inflight/current` build `3006161` | passing `main` build
`3006214` |
|---|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` `IsPackable` | **`false`** |
**`true`** |
| `_GenerateVSWorkloadProps` ran? | no (Build never ran) | yes →
`vs-workload.props` produced |

Verified locally on the `inflight/current` tree with
`dotnet msbuild …Manifest.csproj -getProperty:IsPackable`:

- **Before** fix: manifest `IsPackable=false` (reproduces the break).
- **After** fix: manifest `IsPackable=true`, `Microsoft.Maui.Sdk`
`IsPackable=true`.
- `Microsoft.Maui.Core` (multi-target) at pack time is unchanged
(`IsPackable=true`, `TargetFramework=""`;
the netstandard rule only fires for single-TFM netstandard evaluations,
so multi-target library
  packaging is unaffected).
- A netstandard project with no explicit opt-in
(`Controls.CustomAttributes`) still resolves
  `IsPackable=false` — #32203's intent preserved.

The full signed Windows MSI leg only runs in the official pipeline; this
change restores the property
state that gates the entire pack → `vs-workload.props` → workloads
handoff.

**PR CI confirms the fix end-to-end:** on base `inflight/current` the
`Pack Windows` leg fails at the
root-cause spot; on this PR the same leg is green. The remaining red
`maui-pr` legs are pre-existing on
`inflight/current` (Windows Helix unit-test flakiness identical on base,
and integration legs that were
*skipped on base* because Pack was blocked and only run now that the fix
unblocked Pack).

### Follow-up note

When PR #32203's `Directory.Build.targets` change is forward-ported to
`net10.0`/`main`, it must carry
this `'$(IsPackable)' == ''` guard, otherwise the same break will
reappear on those branches.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 10, 2026
…s MSB4019) (#36089)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Fixes the **nightly official signed build** (`dotnet-maui`, definition
1095, dnceng/internal), which has
failed **8 consecutive nights** on `refs/heads/inflight/current`. The
Windows `Pack` job → step
**"Build Workloads, Sign & Publish"** fails at restore/evaluation:

```
src\Workload\workloads.csproj(30,3): error MSB4019: The imported project
"...\artifacts\packages\Release\Shipping\vs-workload.props" was not found.
```

### Root cause

The workload packs — `Microsoft.NET.Sdk.Maui.Manifest` and
`Microsoft.Maui.Sdk` — both target
**`netstandard2.0`** (via `src/Workload/Shared/Common.targets`) and both
**explicitly** set
`<IsPackable>true</IsPackable>` because they *are* the shipping workload
packages.

PR #32203 ("Don't pack .NET Standard") added a blanket rule to
`Directory.Build.targets`:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard'))">false</IsPackable>
```

`Directory.Build.targets` is auto-imported at the **end** of every
project, so this assignment runs
*after* the project body and **overrides** the explicit
`<IsPackable>true</IsPackable>` on the workload
packs. The net effect: the manifest project evaluates to
`IsPackable=false`.

The chain that breaks from there:

1. With `IsPackable=false`, NuGet's `Pack` target no-ops.
2. The official **"Pack, Sign"** step
(`eng/pipelines/arcade/stage-pack.yml`) runs `-restore -pack -sign`
**without `-build`**, so the `Build` target never runs for the manifest
project.
3. `_GenerateVSWorkloadProps` (in
`Microsoft.NET.Sdk.Maui.Manifest.csproj`) is hooked
`AfterTargets="Build"`, so it never runs → `vs-workload.props` is never
written to
   `$(ArtifactsShippingPackagesDir)`.
4. The next step (`-build … -projects src/Workload/workloads.csproj`)
hits `workloads.csproj` line 30,
whose `<Import Project="$(WorkloadMsiGenProps)" />` is unconditional and
fails at evaluation → **MSB4019**.

The blanket rule is also **over-broad**: it clobbers the explicit opt-in
on *every* netstandard-only
project, not just the workload packs (see **Scope** below). The
workload/MSI leg simply failed loudest
because its missing-file import is fatal at evaluation (MSB4019); the
others would silently stop shipping.

**Why `main` is green:** PR #32203 is on `inflight/current` only — it is
*not* an ancestor of `main` or
`net10.0` (verified with `git merge-base --is-ancestor`). `main` and
`inflight/current` otherwise share
identical `global.json` (dotnet `10.0.108`, arcade `25555.106`),
identical `src/Workload`, and identical
`Microsoft.Build.NoTargets 3.7.0`. The only relevant difference is the
#32203 block, which is why this
break is specific to `inflight/current`.

### Fix

Guard the #32203 rule so it only applies when a project has **not**
explicitly opted into packing:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard')) and '$(IsPackable)' == ''">false</IsPackable>
```

- Projects that explicitly set `<IsPackable>true</IsPackable>` (the
workload packs) are respected and
  pack again — restoring `vs-workload.props` generation.
- Unmarked netstandard projects still default to not-packable,
**preserving #32203's intent**.

This restores the exact condition (`IsPackable=true` for the manifest
project) that the passing `main`
build exhibits, where `_GenerateVSWorkloadProps` runs and
`vs-workload.props` is produced.

### Scope / blast radius

Because #32203's blanket rule was over-broad, this guard restores
packability for **6** netstandard-only
projects that explicitly opt in — not just the 2 workload packs:

| Project | TFM |
|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` | `netstandard2.0` |
| `Microsoft.Maui.Sdk` | `netstandard2.0` |
| `Resizetizer` | `netstandard2.0` |
| `Controls.Build.Tasks` | `netstandard2.0` |
| `Controls.SourceGen` | `netstandard2.0` |
| `Graphics.Text.Markdig` | `netstandard2.0` |

All 6 pack on `main` (which never had the #32203 rule), so this is a
**`main`-parity restore**, not new
shipping behavior. Multi-target libraries (e.g. `Core`, `Controls.Core`)
are unaffected: `dotnet pack`
evaluates `IsPackable` at the outer level where `TargetFramework=""`, so
the netstandard rule never fires
for them.

### How validated

Root cause confirmed from the actual failing/​passing build binlogs (not
guesswork):

| | failing `inflight/current` build `3006161` | passing `main` build
`3006214` |
|---|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` `IsPackable` | **`false`** |
**`true`** |
| `_GenerateVSWorkloadProps` ran? | no (Build never ran) | yes →
`vs-workload.props` produced |

Verified locally on the `inflight/current` tree with
`dotnet msbuild …Manifest.csproj -getProperty:IsPackable`:

- **Before** fix: manifest `IsPackable=false` (reproduces the break).
- **After** fix: manifest `IsPackable=true`, `Microsoft.Maui.Sdk`
`IsPackable=true`.
- `Microsoft.Maui.Core` (multi-target) at pack time is unchanged
(`IsPackable=true`, `TargetFramework=""`;
the netstandard rule only fires for single-TFM netstandard evaluations,
so multi-target library
  packaging is unaffected).
- A netstandard project with no explicit opt-in
(`Controls.CustomAttributes`) still resolves
  `IsPackable=false` — #32203's intent preserved.

The full signed Windows MSI leg only runs in the official pipeline; this
change restores the property
state that gates the entire pack → `vs-workload.props` → workloads
handoff.

**PR CI confirms the fix end-to-end:** on base `inflight/current` the
`Pack Windows` leg fails at the
root-cause spot; on this PR the same leg is green. The remaining red
`maui-pr` legs are pre-existing on
`inflight/current` (Windows Helix unit-test flakiness identical on base,
and integration legs that were
*skipped on base* because Pack was blocked and only run now that the fix
unblocked Pack).

### Follow-up note

When PR #32203's `Directory.Build.targets` change is forward-ported to
`net10.0`/`main`, it must carry
this `'$(IsPackable)' == ''` guard, otherwise the same break will
reappear on those branches.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 15, 2026
…s MSB4019) (#36089)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Fixes the **nightly official signed build** (`dotnet-maui`, definition
1095, dnceng/internal), which has
failed **8 consecutive nights** on `refs/heads/inflight/current`. The
Windows `Pack` job → step
**"Build Workloads, Sign & Publish"** fails at restore/evaluation:

```
src\Workload\workloads.csproj(30,3): error MSB4019: The imported project
"...\artifacts\packages\Release\Shipping\vs-workload.props" was not found.
```

### Root cause

The workload packs — `Microsoft.NET.Sdk.Maui.Manifest` and
`Microsoft.Maui.Sdk` — both target
**`netstandard2.0`** (via `src/Workload/Shared/Common.targets`) and both
**explicitly** set
`<IsPackable>true</IsPackable>` because they *are* the shipping workload
packages.

PR #32203 ("Don't pack .NET Standard") added a blanket rule to
`Directory.Build.targets`:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard'))">false</IsPackable>
```

`Directory.Build.targets` is auto-imported at the **end** of every
project, so this assignment runs
*after* the project body and **overrides** the explicit
`<IsPackable>true</IsPackable>` on the workload
packs. The net effect: the manifest project evaluates to
`IsPackable=false`.

The chain that breaks from there:

1. With `IsPackable=false`, NuGet's `Pack` target no-ops.
2. The official **"Pack, Sign"** step
(`eng/pipelines/arcade/stage-pack.yml`) runs `-restore -pack -sign`
**without `-build`**, so the `Build` target never runs for the manifest
project.
3. `_GenerateVSWorkloadProps` (in
`Microsoft.NET.Sdk.Maui.Manifest.csproj`) is hooked
`AfterTargets="Build"`, so it never runs → `vs-workload.props` is never
written to
   `$(ArtifactsShippingPackagesDir)`.
4. The next step (`-build … -projects src/Workload/workloads.csproj`)
hits `workloads.csproj` line 30,
whose `<Import Project="$(WorkloadMsiGenProps)" />` is unconditional and
fails at evaluation → **MSB4019**.

The blanket rule is also **over-broad**: it clobbers the explicit opt-in
on *every* netstandard-only
project, not just the workload packs (see **Scope** below). The
workload/MSI leg simply failed loudest
because its missing-file import is fatal at evaluation (MSB4019); the
others would silently stop shipping.

**Why `main` is green:** PR #32203 is on `inflight/current` only — it is
*not* an ancestor of `main` or
`net10.0` (verified with `git merge-base --is-ancestor`). `main` and
`inflight/current` otherwise share
identical `global.json` (dotnet `10.0.108`, arcade `25555.106`),
identical `src/Workload`, and identical
`Microsoft.Build.NoTargets 3.7.0`. The only relevant difference is the
#32203 block, which is why this
break is specific to `inflight/current`.

### Fix

Guard the #32203 rule so it only applies when a project has **not**
explicitly opted into packing:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard')) and '$(IsPackable)' == ''">false</IsPackable>
```

- Projects that explicitly set `<IsPackable>true</IsPackable>` (the
workload packs) are respected and
  pack again — restoring `vs-workload.props` generation.
- Unmarked netstandard projects still default to not-packable,
**preserving #32203's intent**.

This restores the exact condition (`IsPackable=true` for the manifest
project) that the passing `main`
build exhibits, where `_GenerateVSWorkloadProps` runs and
`vs-workload.props` is produced.

### Scope / blast radius

Because #32203's blanket rule was over-broad, this guard restores
packability for **6** netstandard-only
projects that explicitly opt in — not just the 2 workload packs:

| Project | TFM |
|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` | `netstandard2.0` |
| `Microsoft.Maui.Sdk` | `netstandard2.0` |
| `Resizetizer` | `netstandard2.0` |
| `Controls.Build.Tasks` | `netstandard2.0` |
| `Controls.SourceGen` | `netstandard2.0` |
| `Graphics.Text.Markdig` | `netstandard2.0` |

All 6 pack on `main` (which never had the #32203 rule), so this is a
**`main`-parity restore**, not new
shipping behavior. Multi-target libraries (e.g. `Core`, `Controls.Core`)
are unaffected: `dotnet pack`
evaluates `IsPackable` at the outer level where `TargetFramework=""`, so
the netstandard rule never fires
for them.

### How validated

Root cause confirmed from the actual failing/​passing build binlogs (not
guesswork):

| | failing `inflight/current` build `3006161` | passing `main` build
`3006214` |
|---|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` `IsPackable` | **`false`** |
**`true`** |
| `_GenerateVSWorkloadProps` ran? | no (Build never ran) | yes →
`vs-workload.props` produced |

Verified locally on the `inflight/current` tree with
`dotnet msbuild …Manifest.csproj -getProperty:IsPackable`:

- **Before** fix: manifest `IsPackable=false` (reproduces the break).
- **After** fix: manifest `IsPackable=true`, `Microsoft.Maui.Sdk`
`IsPackable=true`.
- `Microsoft.Maui.Core` (multi-target) at pack time is unchanged
(`IsPackable=true`, `TargetFramework=""`;
the netstandard rule only fires for single-TFM netstandard evaluations,
so multi-target library
  packaging is unaffected).
- A netstandard project with no explicit opt-in
(`Controls.CustomAttributes`) still resolves
  `IsPackable=false` — #32203's intent preserved.

The full signed Windows MSI leg only runs in the official pipeline; this
change restores the property
state that gates the entire pack → `vs-workload.props` → workloads
handoff.

**PR CI confirms the fix end-to-end:** on base `inflight/current` the
`Pack Windows` leg fails at the
root-cause spot; on this PR the same leg is green. The remaining red
`maui-pr` legs are pre-existing on
`inflight/current` (Windows Helix unit-test flakiness identical on base,
and integration legs that were
*skipped on base* because Pack was blocked and only run now that the fix
unblocked Pack).

### Follow-up note

When PR #32203's `Directory.Build.targets` change is forward-ported to
`net10.0`/`main`, it must carry
this `'$(IsPackable)' == ''` guard, otherwise the same break will
reappear on those branches.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
kubaflo pushed a commit that referenced this pull request Jul 22, 2026
…s MSB4019) (#36089)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

### Description of Change

Fixes the **nightly official signed build** (`dotnet-maui`, definition
1095, dnceng/internal), which has
failed **8 consecutive nights** on `refs/heads/inflight/current`. The
Windows `Pack` job → step
**"Build Workloads, Sign & Publish"** fails at restore/evaluation:

```
src\Workload\workloads.csproj(30,3): error MSB4019: The imported project
"...\artifacts\packages\Release\Shipping\vs-workload.props" was not found.
```

### Root cause

The workload packs — `Microsoft.NET.Sdk.Maui.Manifest` and
`Microsoft.Maui.Sdk` — both target
**`netstandard2.0`** (via `src/Workload/Shared/Common.targets`) and both
**explicitly** set
`<IsPackable>true</IsPackable>` because they *are* the shipping workload
packages.

PR #32203 ("Don't pack .NET Standard") added a blanket rule to
`Directory.Build.targets`:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard'))">false</IsPackable>
```

`Directory.Build.targets` is auto-imported at the **end** of every
project, so this assignment runs
*after* the project body and **overrides** the explicit
`<IsPackable>true</IsPackable>` on the workload
packs. The net effect: the manifest project evaluates to
`IsPackable=false`.

The chain that breaks from there:

1. With `IsPackable=false`, NuGet's `Pack` target no-ops.
2. The official **"Pack, Sign"** step
(`eng/pipelines/arcade/stage-pack.yml`) runs `-restore -pack -sign`
**without `-build`**, so the `Build` target never runs for the manifest
project.
3. `_GenerateVSWorkloadProps` (in
`Microsoft.NET.Sdk.Maui.Manifest.csproj`) is hooked
`AfterTargets="Build"`, so it never runs → `vs-workload.props` is never
written to
   `$(ArtifactsShippingPackagesDir)`.
4. The next step (`-build … -projects src/Workload/workloads.csproj`)
hits `workloads.csproj` line 30,
whose `<Import Project="$(WorkloadMsiGenProps)" />` is unconditional and
fails at evaluation → **MSB4019**.

The blanket rule is also **over-broad**: it clobbers the explicit opt-in
on *every* netstandard-only
project, not just the workload packs (see **Scope** below). The
workload/MSI leg simply failed loudest
because its missing-file import is fatal at evaluation (MSB4019); the
others would silently stop shipping.

**Why `main` is green:** PR #32203 is on `inflight/current` only — it is
*not* an ancestor of `main` or
`net10.0` (verified with `git merge-base --is-ancestor`). `main` and
`inflight/current` otherwise share
identical `global.json` (dotnet `10.0.108`, arcade `25555.106`),
identical `src/Workload`, and identical
`Microsoft.Build.NoTargets 3.7.0`. The only relevant difference is the
#32203 block, which is why this
break is specific to `inflight/current`.

### Fix

Guard the #32203 rule so it only applies when a project has **not**
explicitly opted into packing:

```xml
<IsPackable Condition="$(TargetFramework.Contains('netstandard')) and '$(IsPackable)' == ''">false</IsPackable>
```

- Projects that explicitly set `<IsPackable>true</IsPackable>` (the
workload packs) are respected and
  pack again — restoring `vs-workload.props` generation.
- Unmarked netstandard projects still default to not-packable,
**preserving #32203's intent**.

This restores the exact condition (`IsPackable=true` for the manifest
project) that the passing `main`
build exhibits, where `_GenerateVSWorkloadProps` runs and
`vs-workload.props` is produced.

### Scope / blast radius

Because #32203's blanket rule was over-broad, this guard restores
packability for **6** netstandard-only
projects that explicitly opt in — not just the 2 workload packs:

| Project | TFM |
|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` | `netstandard2.0` |
| `Microsoft.Maui.Sdk` | `netstandard2.0` |
| `Resizetizer` | `netstandard2.0` |
| `Controls.Build.Tasks` | `netstandard2.0` |
| `Controls.SourceGen` | `netstandard2.0` |
| `Graphics.Text.Markdig` | `netstandard2.0` |

All 6 pack on `main` (which never had the #32203 rule), so this is a
**`main`-parity restore**, not new
shipping behavior. Multi-target libraries (e.g. `Core`, `Controls.Core`)
are unaffected: `dotnet pack`
evaluates `IsPackable` at the outer level where `TargetFramework=""`, so
the netstandard rule never fires
for them.

### How validated

Root cause confirmed from the actual failing/​passing build binlogs (not
guesswork):

| | failing `inflight/current` build `3006161` | passing `main` build
`3006214` |
|---|---|---|
| `Microsoft.NET.Sdk.Maui.Manifest` `IsPackable` | **`false`** |
**`true`** |
| `_GenerateVSWorkloadProps` ran? | no (Build never ran) | yes →
`vs-workload.props` produced |

Verified locally on the `inflight/current` tree with
`dotnet msbuild …Manifest.csproj -getProperty:IsPackable`:

- **Before** fix: manifest `IsPackable=false` (reproduces the break).
- **After** fix: manifest `IsPackable=true`, `Microsoft.Maui.Sdk`
`IsPackable=true`.
- `Microsoft.Maui.Core` (multi-target) at pack time is unchanged
(`IsPackable=true`, `TargetFramework=""`;
the netstandard rule only fires for single-TFM netstandard evaluations,
so multi-target library
  packaging is unaffected).
- A netstandard project with no explicit opt-in
(`Controls.CustomAttributes`) still resolves
  `IsPackable=false` — #32203's intent preserved.

The full signed Windows MSI leg only runs in the official pipeline; this
change restores the property
state that gates the entire pack → `vs-workload.props` → workloads
handoff.

**PR CI confirms the fix end-to-end:** on base `inflight/current` the
`Pack Windows` leg fails at the
root-cause spot; on this PR the same leg is green. The remaining red
`maui-pr` legs are pre-existing on
`inflight/current` (Windows Helix unit-test flakiness identical on base,
and integration legs that were
*skipped on base* because Pack was blocked and only run now that the fix
unblocked Pack).

### Follow-up note

When PR #32203's `Directory.Build.targets` change is forward-ported to
`net10.0`/`main`, it must carry
this `'$(IsPackable)' == ''` guard, otherwise the same break will
reappear on those branches.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-infrastructure CI, Maestro / Coherency, upstream dependencies/versions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants