[release/11.0.1xx-preview4] [dotnet-watch] backport changes for mobile & .NET MAUI#54108
Merged
jonathanpeppers merged 4 commits intorelease/11.0.1xx-preview4from Apr 27, 2026
Conversation
Adds device selection to dotnet-watch for MAUI/mobile scenarios, mirroring the `dotnet-run` device selection flow from the spec (`documentation/specs/dotnet-run-for-maui.md`). Merged `TargetFrameworkSelectionPrompt` and the new device prompt into a single `WatchSelectionPrompt` (library) / `SpectreWatchSelectionPrompt` (console app), keeping Spectre.Console isolated to the console app project. After TFM selection, `HotReloadDotNetWatcher` calls the `ComputeAvailableDevices` MSBuild target via in-process MSBuild. A single device is auto-selected; multiple devices show an interactive Spectre prompt with search. The selected device and its `RuntimeIdentifier` are passed to `dotnet build` (`-p:Device`, `-p:RuntimeIdentifier`) and to the launched dotnet run subprocess (`--device`). A re-restore is performed when the device provides a `RuntimeIdentifier` not present in the original restore. Adds `--device` CLI option to `dotnet-watch` for pre-specifying a device. Tests: - Unit tests for prompt selection, search, caching, and `FormatDevice` - E2E tests using `DotnetRunDevices` test asset: interactive device selection, single-device auto-select Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tomáš Matoušek <tmat@users.noreply.github.com>
Fixes: #53634 When a mobile/WebSocket app is shut down (Ctrl+C) or restarted (Ctrl+R), the child process is killed which closes the TCP connection. Kestrel tears down the HTTP context, and then `RunningProject.DisposeAsync` tries to dispose the WebSocket — hitting an `ObjectDisposedException` on the already-disposed `IFeatureCollection`. The `ListenForResponsesAsync` loop also logs a spurious `WebSocketException` error. **Fixes:** - `RequestHandler.Dispose`: catch `ObjectDisposedException` when the underlying Kestrel HTTP context is already torn down - `IsExpectedConnectionTermination`: treat `WebSocketException` as expected when the socket state is `Aborted` (remote disconnected) **Tests:** - `CtrlC_ShutsDownCleanly`: verifies clean shutdown with WebSocket transport - `CtrlR_RestartsCleanly`: verifies restart without `WebSocketException` or `ObjectDisposedException` errors Both tests fail without the fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Testing `dotnet-watch` end-to end on a `dotnet new maui` project... I found the Spectre.Console input was just "stuck", neither up/down arrows or typing to search did anything! `PhysicalConsole` runs `Console.ReadKey()` in a tight background loop, consuming all key presses and dispatching them via the `KeyPressed` event. When `SpectreBuildParametersSelectionPrompt` returned `AnsiConsole.Console` for non-redirected stdin, Spectre.Console would also call `Console.ReadKey()` internally, creating a race where `PhysicalConsole` steals every key press and the selection prompt appears completely stuck (arrow keys, typing, and search all do nothing). Fix by always using `KeyPressedAnsiConsole`, which reads keys from `PhysicalConsole.KeyPressed` events instead of competing for `Console.ReadKey()`. This ensures a single key reader (`PhysicalConsole`) distributes keys to all consumers including Spectre.Console. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…#54023) On iOS with CoreCLR, `UIKitSynchronizationContext` is installed before startup hooks run. `Listener.Listen()` calls `GetAwaiter().GetResult()` on the main thread, and await continuations try to post back to the blocked UI thread, causing a deadlock. Fix by adding `ConfigureAwait(false)` to awaits in the startup hook's call chain. On Android, just due to startup ordering the `SynchronizationContext` was not set. I didn't think of a way we could test this easily -- I basically built the dotnet/macios repo and copied dotnet-watch files on top to manually test. An end-to-end test in the dotnet/macios repo might be the best place. With these changes in place: ``` 2026-04-21 14:41:01.681990-0500 heyo[27489:980549] [HotReload] DOTNET_WATCH_HOTRELOAD_WEBSOCKET_ENDPOINT=ws://localhost:61875 2026-04-21 14:41:01.686900-0500 heyo[27489:980549] [HotReload] Connecting to Hot Reload server via WebSocket ws://localhost:61875. 2026-04-21 14:41:01.696114-0500 heyo[27489:980549] [HotReload] Connecting to ws://localhost:61875... dotnet watch 🔥 [heyo (net11.0-ios)] WebSocket client connected 2026-04-21 14:41:01.754571-0500 heyo[27489:980834] [HotReload] Connected. 2026-04-21 14:41:01.755569-0500 heyo[27489:980834] [HotReload] Sending InitializationResponse (247 bytes) dotnet watch 🔥 [heyo (net11.0-ios)] Capabilities: 'Baseline AddMethodToExistingType AddStaticFieldToExistingType AddInstanceFieldToExistingType NewTypeDefinition ChangeCustomAttributes UpdateParameters GenericUpdateMethod GenericAddMethodToExistingType GenericAddFieldToExistingType AddFieldRva AddExplicitInterfaceImplementation'. 2026-04-21 14:41:01.768519-0500 heyo[27489:980834] [HotReload] Received 1 bytes dotnet watch ⌚ Waiting for changes dotnet watch ⌚ File change: Update '/Users/jopeppers/src/heyo/SceneDelegate.cs'. dotnet watch ⌚ File updated: ./SceneDelegate.cs dotnet watch ⌚ Updating document text of '/Users/jopeppers/src/heyo/SceneDelegate.cs'. dotnet watch ⌚ Solution after document update: v2 dotnet watch 🔥 Hot reload capabilities: AddExplicitInterfaceImplementation AddFieldRva AddInstanceFieldToExistingType AddMethodToExistingType AddStaticFieldToExistingType Baseline ChangeCustomAttributes GenericAddFieldToExistingType GenericAddMethodToExistingType GenericUpdateMethod NewTypeDefinition UpdateParameters. dotnet watch 🔥 [heyo (net11.0-ios)] Sending update batch #0 2026-04-21 14:41:13.296225-0500 heyo[27489:980834] [HotReload] Received 5770 bytes 2026-04-21 14:41:13.338366-0500 heyo[27489:980834] [HotReload] Sending UpdateResponse (466 bytes) dotnet watch 🕵️ [heyo (net11.0-ios)] Writing capabilities: Baseline AddMethodToExistingType AddStaticFieldToExistingType AddInstanceFieldToExistingType NewTypeDefinition ChangeCustomAttributes UpdateParameters GenericUpdateMethod GenericAddMethodToExistingType GenericAddFieldToExistingType AddFieldRva dotnet watch 🕵️ [heyo (net11.0-ios)] Applying updates to module 24147f53-599d-4c51-872f-f2073583eddb. dotnet watch 🕵️ [heyo (net11.0-ios)] Invoking metadata update handlers. dotnet watch 🕵️ [heyo (net11.0-ios)] System.Reflection.Metadata.RuntimeTypeMetadataUpdateHandler.ClearCache dotnet watch 🕵️ [heyo (net11.0-ios)] Updates applied. dotnet watch 🔥 [heyo (net11.0-ios)] Update batch #0 completed. dotnet watch 🔥 C# and Razor changes applied in 567ms. ``` I can see it working on a net11.0-ios project in an iOS simulator: <img width="480" height="588" alt="image" src="https://github.com/user-attachments/assets/9e5944ff-db11-4912-ae4d-48b53190b1da" /> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Backport of recent dotnet-watch improvements needed for mobile/.NET MAUI scenarios in the 11.0.1xx-preview4 release branch, including device selection, WebSocket shutdown/restart robustness, console prompt input fixes, and iOS deadlock avoidance.
Changes:
- Add
--devicesupport and interactive device selection (via MSBuildComputeAvailableDevices) and flow the selected device/RID through build and launch. - Fix WebSocket transport shutdown/restart behavior (expected termination handling; safe disposal) and add E2E coverage for Ctrl+C/Ctrl+R.
- Replace the old TFM-only prompt with a combined build-parameters prompt, update localized resources, and add
ConfigureAwait(false)in agent startup code paths to avoid iOS UI-thread deadlocks.
Reviewed changes
Copilot reviewed 46 out of 46 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/dotnet-watch.Tests/TestUtilities/TestOptions.cs | Introduces shared GlobalOptions for tests (incl. binlog path). |
| test/dotnet-watch.Tests/TestUtilities/MockFileSetFactory.cs | Updates test factory construction for new MSBuild factory signature. |
| test/dotnet-watch.Tests/TestUtilities/DotNetWatchTestBase.cs | Updates watcher ctor usage for new selection prompt abstraction. |
| test/dotnet-watch.Tests/HotReload/TargetFrameworkSelectionPromptTests.cs | Removes tests for the old TFM-only prompt. |
| test/dotnet-watch.Tests/HotReload/MobileHotReloadTests.cs | Adds Ctrl+C/Ctrl+R regression tests for WebSocket transport shutdown/restart. |
| test/dotnet-watch.Tests/HotReload/MauiHotReloadTests.cs | Adds device selection E2E tests using DotnetRunDevices test asset. |
| test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs | Updates ProjectGraphFactory construction signature in tests. |
| test/dotnet-watch.Tests/HotReload/BuildProjectsTests.cs | Updates watcher ctor and build API calls for device selector parameter. |
| test/dotnet-watch.Tests/HotReload/BuildParametersSelectionPromptTests.cs | Adds unit tests for combined TFM + device selection prompt behavior. |
| test/dotnet-watch.Tests/Build/ProjectGraphFactoryTests.cs | Updates ProjectGraphFactory signature usage in tests. |
| test/dotnet-watch.Tests/Build/EvaluationTests.cs | Updates MSBuildFileSetFactory signature usage in tests. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.zh-Hant.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.zh-Hans.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.tr.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.ru.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.pt-BR.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.pl.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.ko.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.ja.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.it.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.fr.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.es.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.de.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/xlf/Resources.cs.xlf | Adds new strings for device selection/help. |
| src/Dotnet.Watch/dotnet-watch/Watch/MsBuildFileSetFactory.cs | Threads GlobalOptions into evaluation/project graph creation for build reporting/binlogs. |
| src/Dotnet.Watch/dotnet-watch/Watch/BuildEvaluator.cs | Adds --device propagation for non-hot-reload watch loop process launches. |
| src/Dotnet.Watch/dotnet-watch/UI/SpectreBuildParametersSelectionPrompt.cs | New combined Spectre prompt + key input bridge to avoid Console.ReadKey races. |
| src/Dotnet.Watch/dotnet-watch/Resources.resx | Adds new device selection/help strings. |
| src/Dotnet.Watch/dotnet-watch/Program.cs | Switches to combined selection prompt; updates MSBuild file listing evaluation call. |
| src/Dotnet.Watch/dotnet-watch/CommandLine/DotnetWatchCommandDefinition.cs | Adds --device option as a watch-specific option. |
| src/Dotnet.Watch/dotnet-watch/CommandLine/CommandLineOptions.cs | Parses/stores Device and flows it into ProjectOptions. |
| src/Dotnet.Watch/Watch/UI/TargetFrameworkSelectionPrompt.cs | Removes old prompt abstraction (superseded). |
| src/Dotnet.Watch/Watch/UI/IReporter.cs | Adds a new message descriptor for “no devices available”. |
| src/Dotnet.Watch/Watch/UI/DeviceInfo.cs | Adds device model used for MSBuild-provided device metadata. |
| src/Dotnet.Watch/Watch/UI/BuildParametersSelectionPrompt.cs | Adds new base prompt abstraction for TFM + device selection with caching. |
| src/Dotnet.Watch/Watch/Process/ProjectLauncher.cs | Passes --device and --runtime to dotnet run when launching. |
| src/Dotnet.Watch/Watch/HotReload/HotReloadDotNetWatcher.cs | Adds device selection orchestration via MSBuild target + re-restore for RID changes. |
| src/Dotnet.Watch/Watch/Context/ProjectOptions.cs | Extends project options with Device and DeviceRuntimeIdentifier. |
| src/Dotnet.Watch/Watch/Build/ProjectGraphFactory.cs | Threads GlobalOptions/EnvironmentOptions into LoadedProjectGraph for build manager/reporting. |
| src/Dotnet.Watch/Watch/Build/LoadedProjectGraph.cs | Adds a shared ProjectBuildManager per loaded graph to support device computation/builds. |
| src/Dotnet.Watch/Watch/Build/EvaluationResult.cs | Uses LoadedProjectGraph.BuildManager rather than owning a separate build manager. |
| src/Dotnet.Watch/Watch/Build/BuildNames.cs | Adds ComputeAvailableDevices target name constant. |
| src/Dotnet.Watch/Watch.Aspire/Server/AspireWatcherLauncher.cs | Updates watcher ctor usage for new selection prompt parameter name/type. |
| src/Dotnet.Watch/HotReloadClient/WebSocketClientTransport.cs | Treats certain WebSocket failures/disposals as expected; guards disposal. |
| src/Dotnet.Watch/HotReloadAgent.Host/WebSocketTransport.cs | Adds ConfigureAwait(false) to avoid iOS UI-thread deadlocks. |
| src/Dotnet.Watch/HotReloadAgent.Host/Listener.cs | Adds ConfigureAwait(false) throughout receive/apply pipeline to avoid iOS UI-thread deadlocks. |
Member
Author
|
Going to rerun some of the failures, copilot thinks this: CI Failures on PR #54108 — Not related to the changes
❌ Failure 1: dotnet-watch.Tests.dll.5 (Linux x64)
Test: FileUpdateTests.TestCommand
Cause: Test hung — blame-hang timeout triggered and hang dumps were collected
Relation to PR: FileUpdateTests is not modified in this PR. This is a known flaky integration test that occasionally deadlocks waiting for dotnet watch subprocess output.
❌ Failure 2: Microsoft.NET.Publish.Tests.dll.5 (Windows x64)
Test: It_does_not_rewrite_the_single_file_unnecessarily
Cause: Timestamp flake — expected file write time 19:33:41.784 but got 19:33:43.774 (~2s drift)
Relation to PR: Microsoft.NET.Publish.Tests is completely unrelated to dotnet-watch changes. This is a known flaky timestamp-comparison test. |
dbreshears
approved these changes
Apr 27, 2026
22 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backports:
UIKitSynchronizationContext#54023