[xabt] dotnet watch support, based on env vars#10778
Draft
jonathanpeppers wants to merge 4 commits intomainfrom
Draft
[xabt] dotnet watch support, based on env vars#10778jonathanpeppers wants to merge 4 commits intomainfrom
dotnet watch support, based on env vars#10778jonathanpeppers wants to merge 4 commits intomainfrom
Conversation
Context: dotnet/sdk#52492 Context: dotnet/sdk#52581 `dotnet-watch` now runs Android applications via: dotnet watch 🚀 [helloandroid (net10.0-android)] Launched 'D:\src\xamarin-android\bin\Debug\dotnet\dotnet.exe' with arguments 'run --no-build -e DOTNET_WATCH=1 -e DOTNET_WATCH_ITERATION=1 -e DOTNET_MODIFIABLE_ASSEMBLIES=debug -e DOTNET_WATCH_HOTRELOAD_WEBSOCKET_ENDPOINT=ws://localhost:9000 -e DOTNET_STARTUP_HOOKS=D:\src\xamarin-android\bin\Debug\dotnet\sdk\10.0.300-dev\DotnetTools\dotnet-watch\10.0.300-dev\tools\net10.0\any\hotreload\net10.0\Microsoft.Extensions.DotNetDeltaApplier.dll -bl': process id 3356 And so the pieces on Android for this to work are: ~~ Startup Hook Assembly ~~ Parse out the value: <_AndroidHotReloadAgentAssemblyPath>@(RuntimeEnvironmentVariable->WithMetadataValue('Identity', 'DOTNET_STARTUP_HOOKS')->'%(Value)'->Exists())</_AndroidHotReloadAgentAssemblyPath> And verify this assembly is included in the app: <ResolvedFileToPublish Include="$(_AndroidHotReloadAgentAssemblyPath)" /> Then, for Android, we need to patch up `$DOTNET_STARTUP_HOOKS` to be just the assembly name, not the full path: <_AndroidHotReloadAgentAssemblyName>$([System.IO.Path]::GetFileNameWithoutExtension('$(_AndroidHotReloadAgentAssemblyPath)'))</_AndroidHotReloadAgentAssemblyName> ... <RuntimeEnvironmentVariable Include="DOTNET_STARTUP_HOOKS" Value="$(_AndroidHotReloadAgentAssemblyName)" /> ~~ Port Forwarding ~~ A new `_AndroidConfigureAdbReverse` target runs after deploying apps, that does: adb reverse tcp:9000 tcp:9000 I parsed the value out of: <_AndroidWebSocketEndpoint>@(RuntimeEnvironmentVariable->WithMetadataValue('Identity', 'DOTNET_WATCH_HOTRELOAD_WEBSOCKET_ENDPOINT')->'%(Value)')</_AndroidWebSocketEndpoint> <_AndroidWebSocketPort>$([System.Text.RegularExpressions.Regex]::Match('$(_AndroidWebSocketEndpoint)', ':(\d+)').Groups[1].Value)</_AndroidWebSocketPort> ~~ Prevent Startup Hooks in Microsoft.Android.Run ~~ When I was implementing this, I keep seeing *two* clients connect to `dotnet-watch` and I was pulling my hair to figure out why! Then I realized that `Microsoft.Android.Run` was also getting `$DOTNET_STARTUP_HOOKS`, and so we had a desktop process + mobile process both trying to connect! Easiest fix, is to disable startup hook support in `Microsoft.Android.Run`. I reviewed the code in `dotnet run`, and it doesn't seem correct to try to clear the env vars. ~~ Conclusion ~~ With these changes, everything is working! dotnet watch 🔥 C# and Razor changes applied in 23ms. This will depend on getting changes in dotnet/sdk before we merge.
jonathanpeppers
commented
Feb 6, 2026
Comment on lines
+50
to
+51
| <!-- Set STARTUP_HOOKS via RuntimeHostConfigurationOption for MonoVM (read by Mono runtime) --> | ||
| <RuntimeHostConfigurationOption Include="STARTUP_HOOKS" Value="$(_AndroidHotReloadAgentAssemblyName)" Condition=" '$(UseMonoRuntime)' == 'true' " /> |
Member
Author
There was a problem hiding this comment.
Can be removed when we get:
simonrozsival
approved these changes
Feb 8, 2026
...in.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.HotReload.targets
Outdated
Show resolved
Hide resolved
…Microsoft.Android.Sdk.HotReload.targets Co-authored-by: Šimon Rozsíval <simon@rozsival.com>
Switch parsing of _AndroidWebSocketEndpoint to use System.UriBuilder so the Port can be read reliably, and normalize a -1 port (meaning no port specified) to an empty value. This ensures the subsequent Condition checking for a non-empty port behaves correctly when the endpoint has no explicit port.
Introduce a HotReloadWebSockets ProjectCapability in Microsoft.Android.Sdk.ProjectCapabilities.targets (conditioned on AndroidApplication). This exposes WebSocket-based hot-reload support to tooling so Android application projects can be detected as supporting hot reload.
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.
Context: dotnet/sdk#52492
Context: dotnet/sdk#52581
dotnet-watchnow runs Android applications via:And so the pieces on Android for this to work are:
Startup Hook Assembly
Parse out the value:
And verify this assembly is included in the app:
Then, for Android, we need to patch up
$DOTNET_STARTUP_HOOKSto be just the assembly name, not the full path:Port Forwarding
A new
_AndroidConfigureAdbReversetarget runs after deploying apps, that does:I parsed the value out of:
Prevent Startup Hooks in Microsoft.Android.Run
When I was implementing this, I keep seeing two clients connect to
dotnet-watchand I was pulling my hair to figure out why!Then I realized that
Microsoft.Android.Runwas also getting$DOTNET_STARTUP_HOOKS, and so we had a desktop process + mobile process both trying to connect!Easiest fix, is to disable startup hook support in
Microsoft.Android.Run. I reviewed the code indotnet run, and it doesn't seem correct to try to clear the env vars.Conclusion
With these changes, everything is working!
This will depend on getting changes in dotnet/sdk before we merge.