dotnet / dotnet/sdk

[.NET 11] dotnet watch hot reload warns about unbuilt TFMs on chained multi-TFM project references

Open
#53,985 0 comments 0 reactions 0 assignees View on GitHub
Area-Watch untriaged
Dominant language
C#
Stars
3.2k
Forks
1.3k
PR merge metrics
PR metrics pending

Description

### Describe the bug

On `11.0.100-preview.3`, `dotnet watch run` against a single-TFM app that transitively references a chain of multi-targeted class libraries emits an MSBuild warning during the hot-reload "Loading projects" phase. The warning complains about a project reference "without a matching metadata reference" for a TFM the consumer never targets, and that the initial build never produced outputs for.

With plain `Microsoft.NET.Sdk` class libs it's just a warning and watch still enters the change-watching loop. Swap the libs for `Microsoft.NET.Sdk.Razor` behind a `Microsoft.NET.Sdk.Web` consumer and the Static Web Assets reference-collection targets turn the same situation into a hard build failure, so watch never reaches the change-watching loop (details at the bottom).

`dotnet run` and `dotnet watch --no-hot-reload run` both work fine on the same solution. The warning (and the Razor hard error) didn't happen on .NET 10 or .NET 9; pinning to SDK `10.0.106` via `global.json` runs clean. It only shows up on `11.0.100-preview.3`.

### To Reproduce

Minimal repro: https://github.com/phil-scott-78/dotnet-watch-multi-tfm

Three projects:

- `ConsoleApp/ConsoleApp.csproj` — `Microsoft.NET.Sdk`, single TFM `net11.0`, references `ClassLib`
- `ClassLib/ClassLib.csproj` — `Microsoft.NET.Sdk`, multi-TFM `net10.0;net11.0`, references `ClassLib2`
- `ClassLib2/ClassLib2.csproj` — `Microsoft.NET.Sdk`, multi-TFM `net10.0;net11.0`

Steps:

```
rm -rf ClassLib/{obj,bin} ClassLib2/{obj,bin} ConsoleApp/{obj,bin}
cd ConsoleApp
dotnet watch run
```

Expected: watch builds and enters the change-watching loop with no warnings, same as with `--no-hot-reload`.

Actual: initial build succeeds, then watch's project-load phase emits an MSBuild warning for `ClassLib2` (the *inner* multi-TFM dependency), then enters the change-watching loop:

```
Build succeeded.
0 Warning(s)
0 Error(s)

dotnet watch ⌚ Loading projects ...
dotnet watch ⌚ Loaded 7 project(s) in 0.5s.
dotnet watch ⚠ msbuild: [Warning] Found project reference without a matching metadata reference: ...\ClassLib2\ClassLib2.csproj
dotnet watch ⌚ Waiting for changes
```

Two things worth noting:

1. "Loaded 7 project(s)" for a 3-project solution. More nodes are getting evaluated than the consumer's selected TFM needs.
2. The warning names `ClassLib2`, the inner multi-TFM lib. A single multi-TFM `ClassLib` directly referenced from the console app doesn't trigger it. At least two multi-TFM projects in the chain are needed.

All three conditions seem to be required:

1. App is single-TFM, chained libs are multi-TFM.
2. At least **two** multi-TFM SDK projects in the reference chain (one isn't enough).
3. Hot reload is on (the default).

Workarounds:

- `dotnet watch --no-hot-reload run` — no warning.
- Running `dotnet build` on the solution first (to materialize both TFMs' outputs), then `dotnet watch run` — no warning.
- Reordering `` to put `net11.0` first doesn't help.
- `dotnet watch --framework net11.0 run` doesn't help.

### Possible cause (theory, unconfirmed)

Speculation from a bit of source spelunking, not a verified diagnosis. Hopefully it helps narrow things down.

In `src/Dotnet.Watch/Watch/Build/EvaluationResult.cs` on `main`, the `CreateDesignTimeBuildRequests` method (added in #53466 ) filters root projects to the main project's TFM but keeps every non-root node whose parent has a non-empty `TargetFramework`:

```csharp
where mainProjectTargetFramework == null ||
targetFramework == mainProjectTargetFramework ||
HasParentWithTargetFramework(node)

static bool HasParentWithTargetFramework(ProjectGraphNode node)
=> node.ReferencingProjects.Any(p => p.ProjectInstance.GetTargetFramework() != "");
```

If I'm reading that right, every inner TFM node of a multi-TFM transitive dependency ends up in the design-time build regardless of which TFM the consumer picked. A new unit test in the same PR (`CreateDesignTimeBuildRequests_SingleTfm_WithMultiTargetedDependencies`) looks like it asserts exactly this for a single-TFM consumer.

The initial build watch performs only materializes the consumer's selected TFM, so the design-time build ends up evaluating a TFM whose outputs were never built. Which is what "project reference without a matching metadata reference" is pointing at here.

### Razor / Web SDK escalation: same cause, hard error

Swap the two multi-TFM class libs for `Microsoft.NET.Sdk.Razor` and the consumer for `Microsoft.NET.Sdk.Web` (keeping single-TFM `net11.0` consumer → multi-TFM `net10.0;net11.0` chain), and the same design-time-build-for-an-unbuilt-TFM situation becomes a hard build failure. The Static Web Assets reference-collection targets require a `staticwebassets.build.json` manifest for the TFM being evaluated:

```
dotnet watch ⌚ Loading projects ...
dotnet watch ❌ Failed to build project '...\RazorLib\RazorLib.csproj'.
Attempting to cancel the build...
dotnet watch ❌ Failed to build project '...\RazorLib2\RazorLib2.csproj'.
dotnet watch 🔨 MSBuild output:
dotnet watch 🔨 C:\Program Files\dotnet\sdk\11.0.100-preview.3.26207.106\Sdks\Microsoft.NET.Sdk.StaticWebAssets\targets\Microsoft.NET.Sdk.StaticWebAssets.References.targets(171,7): error : Manifest file at 'obj\Debug\net10.0\staticwebassets.build.json' not found.
dotnet watch ⏳ Waiting for a file to change before restarting ...
```

The missing manifest is for `net10.0`, a TFM the web app never targets and that the initial build never produced outputs for.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.