NU1102: Microsoft.NETCore.App.Runtime.Mono.win-x64 on
dotnet publish -r win-x64 — NuGet went looking for a Windows build of Android's
Mono runtime, and there is no such package because there is no such thing.
On a multi-target MAUI project, -r win-x64 is applied at restore time to
every target framework in the project, not only the one you named with -f. The
Android target's runtime is Mono, its runtime packs exist for Android runtime identifiers only,
and the restore fails before the Windows build starts. Drop the flag: the Windows target already
defaults to win-x64, and the self-contained publish works without it.
The error
From the first Windows publish of the season (abridged — the package and version as the build log recorded them; NuGet's full line also lists every feed it searched):
NU1102: Microsoft.NETCore.App.Runtime.Mono.win-x64 10.0.11 not found
Why it happens
Restore is a project-level step. Even when -f net10.0-windows10.0.19041.0 narrows
the build to one target, NuGet still resolves packages for every framework in
<TargetFrameworks>, because the assets file it writes covers the whole
project. A -r on the command line becomes the RuntimeIdentifier for
that whole restore.
Each target then asks for the runtime pack matching its runtime and that identifier. The Windows
target wants Microsoft.NETCore.App.Runtime.win-x64, which exists. The Android target
runs on Mono, so it wants Microsoft.NETCore.App.Runtime.Mono.win-x64 — a pack that
was never published, since Mono on Android ships for android-arm64 and friends.
NU1102 is NuGet saying it searched and found nothing. The same command is correct for a
single-target app, which is exactly why it looks so natural.
The fix
The publish command that works, from docs/pocket.md in the repo:
dotnet publish src/ClinicLive.Pocket -f net10.0-windows10.0.19041.0 -c Release \
-p:WindowsPackageType=None -p:WindowsAppSDKSelfContained=true -p:SelfContained=true
No -r. The Windows App SDK targets pick win-x64 for the Windows target
on their own, and SelfContained=true still produces the folder you wanted — 643
files, 235 MB, running on a machine with nothing installed. If you genuinely need a different
identifier (an ARM64 build, say), set RuntimeIdentifier in the project file inside a
condition on the Windows target framework, so the Android restore never sees it. Passing
-p:RuntimeIdentifier=win-x64 on the command line has the same problem as
-r: it is global.
What the AI got wrong: it reached for the idiomatic self-contained flag from a thousand single-target examples. The lesson of the part was that an exit code is a claim, not a fact; this one at least failed loudly.
Where it bit us
Season three, Part 11: Ship it, on the first
attempt at the self-contained Windows publish. Tag pocket-11 in
the repo carries the working command
and a runbook note saying not to add -r. The lesson: a flag that is right for one
project shape is wrong for another, and a multi-target project is a different shape.
Frequently asked
- Why does dotnet publish -r win-x64 fail with NU1102 on a .NET MAUI project?
- Because the runtime identifier applies to the restore of every target framework in the project, not just the one selected with -f. The Android target uses the Mono runtime, and no Mono runtime pack exists for win-x64, so NuGet reports NU1102 for Microsoft.NETCore.App.Runtime.Mono.win-x64.
- How do I publish a self-contained Windows build of a MAUI app?
- Run dotnet publish with -f for the Windows target framework, -c Release, and -p:SelfContained=true plus -p:WindowsAppSDKSelfContained=true, and leave out -r. The Windows target defaults to win-x64 on its own, so the runtime identifier flag is unnecessary and breaks the Android restore.
- Is -p:RuntimeIdentifier=win-x64 any different from -r win-x64?
- No. Both set the RuntimeIdentifier property globally for the restore. To target a specific runtime for one framework only, set RuntimeIdentifier in the project file under a condition that matches the Windows target framework.
More decoded errors in the Fixes category; the whole shipping story is in Part 11.