Avoid crossgening dotnet-watch startup hooks#53501
Merged
tmat merged 1 commit intodotnet:mainfrom Mar 17, 2026
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the redist CrossGen inputs to exclude dotnet-watch “hot reload” runtime dependency assemblies that are injected into user apps at runtime, avoiding ReadyToRun compilation of those specific DLLs during layout generation.
Changes:
- Exclude
Microsoft.Extensions.DotNetDeltaApplier.dllfrom CrossGen when it appears underDotnetTools/dotnet-watch. - Exclude
Microsoft.AspNetCore.Watch.BrowserRefresh.dllfrom CrossGen when it appears underDotnetTools/dotnet-watch.
jonathanpeppers
approved these changes
Mar 17, 2026
Member
There was a problem hiding this comment.
This looks right.
I found this file has an r2r image inside:
D:\src\xamarin-android\bin\Debug\dotnet\sdk\11.0.100-preview.3.26165.107\DotnetTools\dotnet-watch\11.0.100-preview.3.26165.107\tools\net11.0\any\hotreload\net10.0\Microsoft.Extensions.DotNetDeltaApplier.dll
And then if you try to dotnet-watch an Android app, the app crashes at startup with:
03-17 15:08:17.066 15581 15581 E DOTNET : Unhandled exception. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
03-17 15:08:17.066 15581 15581 E DOTNET : ---> System.ArgumentException: Startup hook assembly 'Microsoft.Extensions.DotNetDeltaApplier' failed to load. See inner exception for details.
03-17 15:08:17.066 15581 15581 E DOTNET : ---> System.BadImageFormatException: An attempt was made to load a program with an incorrect format.
03-17 15:08:17.066 15581 15581 E DOTNET : (0x8007000B)
03-17 15:08:17.066 15581 15581 E DOTNET : at System.Reflection.RuntimeAssembly.<InternalLoad>g____PInvoke|48_0(NativeAssemblyNameParts* __pAssemblyNameParts_native, ObjectHandleOnStack __requestingAssembly_native, StackCrawlMarkHandle __stackMark_native, Int32 __throwOnFileNotFound_native, ObjectHandleOnStack __assemblyLoadContext_native, ObjectHandleOnStack __retAssembly_native)
03-17 15:08:17.066 15581 15581 E DOTNET : at System.Reflection.RuntimeAssembly.<InternalLoad>g____PInvoke|48_0(NativeAssemblyNameParts* __pAssemblyNameParts_native, ObjectHandleOnStack __requestingAssembly_native, StackCrawlMarkHandle __stackMark_native, Int32 __throwOnFileNotFound_native, ObjectHandleOnStack __assemblyLoadContext_native, ObjectHandleOnStack __retAssembly_native)
03-17 15:08:17.066 15581 15581 E DOTNET : at System.Reflection.RuntimeAssembly.InternalLoad(NativeAssemblyNameParts* pAssemblyNameParts, ObjectHandleOnStack requestingAssembly, StackCrawlMarkHandle stackMark, Boolean throwOnFileNotFound, ObjectHandleOnStack assemblyLoadContext, ObjectHandleOnStack retAssembly)
03-17 15:08:17.066 15581 15581 E DOTNET : at System.Reflection.RuntimeAssembly.InternalLoad(AssemblyName assemblyName, StackCrawlMark& stackMark, AssemblyLoadContext assemblyLoadContext, RuntimeAssembly requestingAssembly, Boolean throwOnFileNotFound)
03-17 15:08:17.066 15581 15581 E DOTNET : at System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyName(AssemblyName assemblyName)
03-17 15:08:17.066 15581 15581 E DOTNET : at System.StartupHookProvider.CallStartupHook(StartupHookNameOrPath startupHook)
03-17 15:08:17.066 15581 15581 E DOTNET : --- End of inner exception stack trace ---
03-17 15:08:17.066 15581 15581 E DOTNET : at System.StartupHookProvider.CallStartupHook(StartupHookNameOrPath startupHook)
03-17 15:08:17.066 15581 15581 E DOTNET : at System.StartupHookProvider.ProcessStartupHooks(String diagnosticStartupHooks)
Because it's trying to load a win-x64 R2R image on android-arm64.
Member
|
Local build: Broken build: ILLibrary means it has R2R inside. Looks good! 👍 |
jonathanpeppers
added a commit
to dotnet/android
that referenced
this pull request
Mar 19, 2026
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. * Include $(AdbTarget) in adb reverse command When multiple devices/emulators are connected, the adb reverse command needs (e.g. -s emulator-5554) to target the correct device. Without it, the command fails with 'more than one device' or forwards on the wrong device. NOTE: we run the test on MonoVM-only for now, until we get: * dotnet/sdk#53501 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppers
added a commit
to dotnet/android
that referenced
this pull request
Mar 19, 2026
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. * Include $(AdbTarget) in adb reverse command When multiple devices/emulators are connected, the adb reverse command needs (e.g. -s emulator-5554) to target the correct device. Without it, the command fails with 'more than one device' or forwards on the wrong device. NOTE: we run the test on MonoVM-only for now, until we get: * dotnet/sdk#53501 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.
The architecture of the app process might be different then the architecture of SDK.