Skip to content

Don't pack .NET Standard - #32203

Merged
kubaflo merged 1 commit into
inflight/currentfrom
dev/no-netstd
Jun 7, 2026
Merged

Don't pack .NET Standard#32203
kubaflo merged 1 commit into
inflight/currentfrom
dev/no-netstd

Conversation

@mattleibow

Copy link
Copy Markdown
Member

Description of Change

.NET Standard was never supported, but just remained to help MSBuild tasks.

We don't need to actually ship them.

Copilot AI review requested due to automatic review settings October 24, 2025 15:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR prevents .NET Standard target framework versions from being included in NuGet packages. While .NET Standard targets remain in the codebase for MSBuild task compatibility, they are no longer intended for distribution.

Key Changes

  • Adds MSBuild property to exclude .NET Standard TFMs from packaging

Comment thread Directory.Build.targets
@rmarinho
rmarinho changed the base branch from net10.0 to main January 21, 2026 23:10
@kubaflo

kubaflo commented May 24, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/refactor-copilot-yml

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.

Comment thread Directory.Build.targets
<Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[major] Build & MSBuild — This disables packing for every netstandard inner build, including netstandard-only packable tooling projects such as src/Controls/src/Build.Tasks/Controls.Build.Tasks.csproj and src/SingleProject/Resizetizer/src/Resizetizer.csproj. Because those packages only target netstandard2.0, setting IsPackable=false here prevents the packages from being produced at all. Please scope the suppression to multi-target product packages/removing netstandard pack frameworks instead of globally disabling all netstandard pack builds.

@MauiBot MauiBot added s/agent-review-incomplete s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels May 24, 2026
@MauiBot

This comment has been minimized.

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

Could you please check the ai's suggestions?

@kubaflo

kubaflo commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/enhanced-reviewer -p windows

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@mattleibow — new AI review results are available based on this last commit: 18b861c.
Don't pack .NET Standard To request a fresh review after new comments or commits, comment /review rerun.

Gate Skipped Code Review Needs Changes Confidence High Platform Packaging

Review Sessions — click to expand
Gate — Test Before & After Fix

Gate Result: ⚠️ SKIPPED

No tests were detected in this PR.

Recommendation: Add tests to verify the fix using the write-tests-agent.


Pre-Flight — Context & Validation

Issue: N/A - No linked issue detected
PR: #32203 - Don't pack .NET Standard
Platforms Affected: packaging/build; testing platform windows
Files Changed: 1 implementation, 0 test

Key Findings

  • PR changes Directory.Build.targets to set IsPackable=false whenever TargetFramework contains netstandard.
  • Gate was already skipped by the caller because no tests were detected in this PR; gate verification was not re-run.
  • Current fix is too broad: Directory.Build.targets is imported after project files, so the new property overrides explicit IsPackable=true in netstandard-only tooling packages.
  • Netstandard-only packable tooling projects that must continue producing packages include src/Controls/src/Build.Tasks/Controls.Build.Tasks.csproj, src/SingleProject/Resizetizer/src/Resizetizer.csproj, and src/Controls/src/SourceGen/Controls.SourceGen.csproj.
  • No UI test categories are impacted; this is packaging/MSBuild behavior.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: high
Errors: 1 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • Directory.Build.targets:6 blanket netstandard pack suppression breaks tooling packages by overriding explicit IsPackable=true in netstandard-only package projects.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #32203 Set IsPackable=false when TargetFramework contains netstandard ⚠️ SKIPPED (Gate; no tests detected) Directory.Build.targets Original PR fix; code review found it disables required netstandard-only tooling packages

Code Review — Deep Analysis

Code Review — PR #32203

Independent Assessment

What this changes: Globally sets IsPackable=false whenever TargetFramework contains netstandard.
Inferred motivation: Stop shipping .NET Standard package assets while keeping netstandard builds for tooling/MSBuild tasks.

Reconciliation with PR Narrative

Author claims: .NET Standard was never supported and only remains to help MSBuild tasks.
Agreement/disagreement: The goal matches, but the implementation also disables packaging for required netstandard tooling packages.

Findings

❌ Error — Blanket netstandard pack suppression breaks tooling packages

Directory.Build.targets:6 is imported after project files, so it overrides explicit <IsPackable>true</IsPackable> in netstandard-only package projects such as:

  • src/Controls/src/Build.Tasks/Controls.Build.Tasks.csproj
  • src/SingleProject/Resizetizer/src/Resizetizer.csproj
  • src/Controls/src/SourceGen/Controls.SourceGen.csproj

I verified these now evaluate IsPackable=false. These packages carry required buildTransitive assets/task assemblies, so pack output can disappear. CI also shows maui-pr (Pack .NET MAUI Pack Windows) failing.

Devil's Advocate

The one-line fix is attractive and may remove product-library netstandard assets, but it is too broad. A safer fix should target product package framework assets/dependency groups without disabling netstandard-only tooling packages.

Verdict: NEEDS_CHANGES

Confidence: high
Summary: Valid cleanup goal, but current implementation suppresses required tooling packages. Scope the change more narrowly before merging.


Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix Scope IsPackable=false to multi-target netstandard inner builds ❌ Fail Directory.Build.targets Preserved tooling package IsPackable=true, but Microsoft.Maui.Core still shipped lib/netstandard2.0 and lib/netstandard2.1 assets.
2 try-fix Suppress netstandard build output and dependency groups during product package pack ✅ Pass Directory.Build.targets Removed Microsoft.Maui.Core netstandard package assets/groups while preserving Controls.Build.Tasks and Resizetizer packages.
PR PR #32203 Blanket IsPackable=false for any netstandard target ⚠️ SKIPPED (Gate; no tests detected) Directory.Build.targets Original PR; code review found it disables netstandard-only tooling packages.

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 1 Yes Suggested scoping IsPackable=false to multi-target netstandard inner builds. Tested as try-fix-1 and failed.
maui-expert-reviewer 2 Yes Suggested IncludeBuildOutput=false plus SuppressDependenciesWhenPacking=true for product package netstandard inner builds. Tested as try-fix-2 and passed.
maui-expert-reviewer 3 No Only remaining distinct idea was filtering netstandard* out of pack TargetFrameworks, judged riskier and not plausibly better than try-fix-2.

Exhausted: Yes — two meaningfully different approaches were tested, and expert follow-up found no plausibly better third approach.
Selected Fix: Candidate #2 — It is demonstrably better than the PR's fix because it removes product-package netstandard assets and dependency groups without disabling netstandard-only tooling packages.

Candidate Details

try-fix-1 — Scoped inner-build IsPackable=false

  • Result: ❌ Fail
  • Validation: Controls.Build.Tasks and Resizetizer still evaluated IsPackable=true.
  • Failure: Microsoft.Maui.Core package still contained lib/netstandard2.0/* and lib/netstandard2.1/* entries.
  • Lesson: Inner-build IsPackable=false is not sufficient to remove netstandard assets from NuGet's outer pack graph.

try-fix-2 — Suppress netstandard pack output and dependency groups

  • Result: ✅ Pass
  • Validation: Packed Microsoft.Maui.Core with Packing=true and BuildForNetStandard=true; the nupkg had no lib/ref/netstandard* entries and no netstandard dependency groups. Packed Microsoft.Maui.Controls.Build.Tasks and Microsoft.Maui.Resizetizer; both still produced packages.
  • Risk: Relies on IncludeBuildOutput and SuppressDependenciesWhenPacking being evaluated before NuGet pack collects per-TFM output/dependency metadata. The targeted validation confirms this placement works for representative product and tooling packages.

Report — Final Recommendation

Comparative Report - PR #32203

Candidates

Candidate Approach Test / review result Rank
try-fix-2 For product package netstandard inner builds, set IncludeBuildOutput=false and SuppressDependenciesWhenPacking=true while preserving netstandard-only tooling packages. Passed targeted Windows package validation: Microsoft.Maui.Core nupkg had no lib/netstandard* or ref/netstandard* assets and no netstandard dependency groups; Controls.Build.Tasks and Resizetizer still packed. 1
pr-plus-reviewer Preserve explicit IsPackable=true opt-ins while keeping the PR's netstandard IsPackable=false suppression for other projects. Not selected: addresses the expert review's tooling-package regression, but product packages such as Microsoft.Maui.Core also explicitly set IsPackable=true, so this candidate would preserve the unwanted netstandard pack assets. 2
pr Set IsPackable=false whenever TargetFramework contains netstandard. Gate skipped, but expert review found a blocking regression: required netstandard-only tooling packages become non-packable. 3
try-fix-1 Scope IsPackable=false to multi-target netstandard inner builds. Failed regression validation: tooling packages remained packable, but Microsoft.Maui.Core still shipped lib/netstandard2.0 and lib/netstandard2.1 assets. Failed candidates rank below candidates without failing regression tests. 4

Winner

Winner: try-fix-2

try-fix-2 is the only candidate with passing targeted package validation and it directly addresses both sides of the problem: product packages stop contributing netstandard assets/dependency groups, while netstandard-only tooling packages remain packable. The raw PR has a confirmed packaging regression, try-fix-1 failed validation, and pr-plus-reviewer is safer than the raw PR but does not fully remove product netstandard package output.

Notes

No gate tests were detected for the PR, so the PR author should add package-focused validation that checks both absence of netstandard assets/dependency groups in product packages and continued pack output for tooling packages.


Future Action — alternative fix proposed (try-fix-2)

Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against automatically generated candidates and selected try-fix-2 as the strongest fix.

Why: try-fix-2 is the only candidate with passing targeted package validation. It removes product-package netstandard assets and dependency groups while preserving required netstandard-only tooling packages.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (try-fix-2)
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 77feb5312b..a8b39d2c24 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,9 +1,18 @@
 <Project>
 
   <Import Project="Sdk.targets" Sdk="Microsoft.DotNet.Arcade.Sdk" />
 
+  <PropertyGroup Condition="'$(Packing)' == 'true'
+      and '$(BuildForNetStandard)' == 'true'
+      and '$(TargetFrameworks)' != ''
+      and '$(TargetFramework)' != ''
+      and $([System.String]::Copy('$(TargetFramework)').StartsWith('netstandard'))">
+    <IncludeBuildOutput>false</IncludeBuildOutput>
+    <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
+  </PropertyGroup>
+
   <ItemGroup>
     <!-- Upgrade xunit's transitive NETStandard.Library dependency to avoid .NET Standard 1.x dependencies. -->
     <!-- <PackageReference Include="NETStandard.Library"
                       IsImplicitlyDefined="false"

@kubaflo
kubaflo changed the base branch from main to inflight/current June 7, 2026 12:15
@kubaflo
kubaflo merged commit 1702afe into inflight/current Jun 7, 2026
4 of 14 checks passed
@kubaflo
kubaflo deleted the dev/no-netstd branch June 7, 2026 12:17
@github-actions github-actions Bot added this to the .NET 10.0 SR8 milestone Jun 7, 2026
PureWeen pushed a commit that referenced this pull request Jun 11, 2026
### Description of Change

.NET Standard was never supported, but just remained to help MSBuild
tasks.

We don't need to actually ship them.
@sheiksyedm sheiksyedm modified the milestones: .NET 10 SR8, .NET 10 SR9 Jun 18, 2026
PureWeen pushed a commit that referenced this pull request Jun 22, 2026
### Description of Change

.NET Standard was never supported, but just remained to help MSBuild
tasks.

We don't need to actually ship them.
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>
PureWeen added 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 Jun 25, 2026
### Description of Change

.NET Standard was never supported, but just remained to help MSBuild
tasks.

We don't need to actually ship them.
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
### Description of Change

.NET Standard was never supported, but just remained to help MSBuild
tasks.

We don't need to actually ship them.
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
### Description of Change

.NET Standard was never supported, but just remained to help MSBuild
tasks.

We don't need to actually ship them.
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 pushed a commit that referenced this pull request Jul 7, 2026
### Description of Change

.NET Standard was never supported, but just remained to help MSBuild
tasks.

We don't need to actually ship them.
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 pushed a commit that referenced this pull request Jul 7, 2026
### Description of Change

.NET Standard was never supported, but just remained to help MSBuild
tasks.

We don't need to actually ship them.
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
### Description of Change

.NET Standard was never supported, but just remained to help MSBuild
tasks.

We don't need to actually ship them.
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
### Description of Change

.NET Standard was never supported, but just remained to help MSBuild
tasks.

We don't need to actually ship them.
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>
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants