Skip to content

Commit 099eec4

Browse files
authored
Port Hot Reload changes from main (#50534)
1 parent e1ecbae commit 099eec4

File tree

109 files changed

+2690
-1774
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+2690
-1774
lines changed

Directory.Build.targets

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,15 @@
9797
<!-- Include SourcePackage.editorconfig in all source packages. -->
9898
<Target Name="_AddEditorConfigToSourcePackage">
9999
<ItemGroup>
100-
<TfmSpecificPackageFile Include="$(MSBuildThisFileDirectory)eng\SourcePackage.editorconfig" PackagePath="contentFiles/cs/$(TargetFramework)/.editorconfig" />
100+
<TfmSpecificPackageFile Include="$(MSBuildThisFileDirectory)eng\SourcePackage.editorconfig" PackagePath="contentFiles/cs/$(TargetFramework)/.editorconfig" Condition="'$(TargetFrameworkIdentifier)' != '.NETStandard'" />
101+
<TfmSpecificPackageFile Include="$(MSBuildThisFileDirectory)eng\SourcePackage.netstandard.editorconfig" PackagePath="contentFiles/cs/$(TargetFramework)/.editorconfig" Condition="'$(TargetFrameworkIdentifier)' == '.NETStandard'" />
101102
</ItemGroup>
102103
</Target>
103104

104105
<!-- Include linked files. Arcade SDK only includes files in the project directory. -->
105106
<Target Name="_AddLinkedCompileItemsToSourcePackage">
106107
<ItemGroup>
107-
<TfmSpecificPackageFile Include="@(Compile)" Condition="'%(Compile.Link)' != ''" PackagePath="contentFiles/cs/$(TargetFramework)/%(Compile.Link)" BuildAction="Compile"/>
108+
<TfmSpecificPackageFile Include="@(Compile)" Condition="'%(Compile.Link)' != '' and '%(Compile.Pack)' != 'false'" PackagePath="contentFiles/cs/$(TargetFramework)/%(Compile.Link)" BuildAction="Compile"/>
108109
</ItemGroup>
109110
</Target>
110111

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="$(MicrosoftCodeAnalysisAnalyzerTestingVersion)" />
2323
<PackageVersion Include="Microsoft.CodeAnalysis.BuildClient" Version="$(MicrosoftCodeAnalysisBuildClientVersion)" />
2424
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="$(MicrosoftCodeAnalysisPackageVersion)" />
25+
<PackageVersion Include="Microsoft.CodeAnalysis.Contracts" Version="$(MicrosoftCodeAnalysisPackageVersion)" />
2526
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisCSharpPackageVersion)" />
2627
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeStyle" Version="$(MicrosoftCodeAnalysisPackageVersion)" />
2728
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="$(MicrosoftCodeAnalysisCSharpPackageVersion)" />
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Remove the line below if you want to inherit .editorconfig settings from higher directories
2+
root = true
3+
4+
# C# files
5+
[*.cs]
6+
7+
# We don't want any analyzer diagnostics to be reported for people consuming this as a source package.
8+
dotnet_analyzer_diagnostic.severity = none
9+
10+
generated_code = true
11+
12+
# The above configurations don't apply to compiler warnings. Requiring all params to be documented
13+
# is not something we require for this project, so suppressing it directly here.
14+
dotnet_diagnostic.CS1573.severity = none
15+
16+
# As above, we need to specifically disable compiler warnings that we don't want to break downstream
17+
# builds
18+
dotnet_diagnostic.IDE0005.severity = none
19+
20+
# Disable nullability checks when targeting netstandard2.0
21+
dotnet_diagnostic.CS8604.severity = none

eng/Versions.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,13 @@
8686
Visual Studio is providing those assemblies, and we should work with whichever version it ships. -->
8787
<MicrosoftBclAsyncInterfacesToolsetPackageVersion>8.0.0</MicrosoftBclAsyncInterfacesToolsetPackageVersion>
8888
<MicrosoftDeploymentDotNetReleasesToolsetPackageVersion>2.0.0-preview.1.24427.4</MicrosoftDeploymentDotNetReleasesToolsetPackageVersion>
89+
<MicrosoftExtensionsLoggingAbstractionsToolsetPackageVersion>9.0.0</MicrosoftExtensionsLoggingAbstractionsToolsetPackageVersion>
8990
<SystemBuffersToolsetPackageVersion>4.5.1</SystemBuffersToolsetPackageVersion>
9091
<SystemCollectionsImmutableToolsetPackageVersion>9.0.0</SystemCollectionsImmutableToolsetPackageVersion>
9192
<SystemMemoryToolsetPackageVersion>4.5.5</SystemMemoryToolsetPackageVersion>
9293
<SystemReflectionMetadataLoadContextToolsetPackageVersion>9.0.0</SystemReflectionMetadataLoadContextToolsetPackageVersion>
9394
<SystemReflectionMetadataToolsetPackageVersion>9.0.0</SystemReflectionMetadataToolsetPackageVersion>
94-
<SystemDiagnosticsDiagnosticsSourceToolsetPackageVersion>9.0.0</SystemDiagnosticsDiagnosticsSourceToolsetPackageVersion>
95+
<SystemDiagnosticsDiagnosticSourceToolsetPackageVersion>9.0.0</SystemDiagnosticsDiagnosticSourceToolsetPackageVersion>
9596
<SystemTextJsonToolsetPackageVersion>8.0.5</SystemTextJsonToolsetPackageVersion>
9697
<SystemThreadingTasksExtensionsToolsetPackageVersion>4.5.4</SystemThreadingTasksExtensionsToolsetPackageVersion>
9798
<SystemResourcesExtensionsToolsetPackageVersion>8.0.0</SystemResourcesExtensionsToolsetPackageVersion>

sdk.slnx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
<Project Path="src/BuiltInTools/HotReloadAgent.WebAssembly.Browser/Microsoft.DotNet.HotReload.WebAssembly.Browser.csproj" />
5555
<Project Path="src/BuiltInTools/HotReloadAgent/Microsoft.DotNet.HotReload.Agent.Package.csproj" />
5656
<Project Path="src/BuiltInTools/HotReloadAgent/Microsoft.DotNet.HotReload.Agent.shproj" />
57+
<Project Path="src/BuiltInTools/HotReloadClient/Microsoft.DotNet.HotReload.Client.Package.csproj" />
58+
<Project Path="src/BuiltInTools/HotReloadClient/Microsoft.DotNet.HotReload.Client.shproj" Id="a78ff92a-d715-4249-9e3d-40d9997a098f" />
5759
</Folder>
5860
<Folder Name="/src/Cli/">
5961
<Project Path="src/Cli/dotnet/dotnet.csproj" />
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.IO.Pipes;
5+
using System.Reflection;
6+
using System.Runtime.Loader;
7+
8+
namespace Microsoft.DotNet.HotReload;
9+
10+
internal sealed class PipeListener(string pipeName, IHotReloadAgent agent, Action<string> log, int connectionTimeoutMS = 5000)
11+
{
12+
public Task Listen(CancellationToken cancellationToken)
13+
{
14+
// Connect to the pipe synchronously.
15+
//
16+
// If a debugger is attached and there is a breakpoint in the startup code connecting asynchronously would
17+
// set up a race between this code connecting to the server, and the breakpoint being hit. If the breakpoint
18+
// hits first, applying changes will throw an error that the client is not connected.
19+
//
20+
// Updates made before the process is launched need to be applied before loading the affected modules.
21+
22+
log($"Connecting to hot-reload server via pipe {pipeName}");
23+
24+
var pipeClient = new NamedPipeClientStream(serverName: ".", pipeName, PipeDirection.InOut, PipeOptions.CurrentUserOnly | PipeOptions.Asynchronous);
25+
try
26+
{
27+
pipeClient.Connect(connectionTimeoutMS);
28+
log("Connected.");
29+
}
30+
catch (TimeoutException)
31+
{
32+
log($"Failed to connect in {connectionTimeoutMS}ms.");
33+
pipeClient.Dispose();
34+
return Task.CompletedTask;
35+
}
36+
37+
try
38+
{
39+
// block execution of the app until initial updates are applied:
40+
InitializeAsync(pipeClient, cancellationToken).GetAwaiter().GetResult();
41+
}
42+
catch (Exception e)
43+
{
44+
if (e is not OperationCanceledException)
45+
{
46+
log(e.Message);
47+
}
48+
49+
pipeClient.Dispose();
50+
agent.Dispose();
51+
52+
return Task.CompletedTask;
53+
}
54+
55+
return Task.Run(async () =>
56+
{
57+
try
58+
{
59+
await ReceiveAndApplyUpdatesAsync(pipeClient, initialUpdates: false, cancellationToken);
60+
}
61+
catch (Exception e) when (e is not OperationCanceledException)
62+
{
63+
log(e.Message);
64+
}
65+
finally
66+
{
67+
pipeClient.Dispose();
68+
agent.Dispose();
69+
}
70+
}, cancellationToken);
71+
}
72+
73+
private async Task InitializeAsync(NamedPipeClientStream pipeClient, CancellationToken cancellationToken)
74+
{
75+
agent.Reporter.Report("Writing capabilities: " + agent.Capabilities, AgentMessageSeverity.Verbose);
76+
77+
var initPayload = new ClientInitializationResponse(agent.Capabilities);
78+
await initPayload.WriteAsync(pipeClient, cancellationToken);
79+
80+
// Apply updates made before this process was launched to avoid executing unupdated versions of the affected modules.
81+
82+
// We should only receive ManagedCodeUpdate when when the debugger isn't attached,
83+
// otherwise the initialization should send InitialUpdatesCompleted immediately.
84+
// The debugger itself applies these updates when launching process with the debugger attached.
85+
await ReceiveAndApplyUpdatesAsync(pipeClient, initialUpdates: true, cancellationToken);
86+
}
87+
88+
private async Task ReceiveAndApplyUpdatesAsync(NamedPipeClientStream pipeClient, bool initialUpdates, CancellationToken cancellationToken)
89+
{
90+
while (pipeClient.IsConnected)
91+
{
92+
var payloadType = (RequestType)await pipeClient.ReadByteAsync(cancellationToken);
93+
switch (payloadType)
94+
{
95+
case RequestType.ManagedCodeUpdate:
96+
await ReadAndApplyManagedCodeUpdateAsync(pipeClient, cancellationToken);
97+
break;
98+
99+
case RequestType.StaticAssetUpdate:
100+
await ReadAndApplyStaticAssetUpdateAsync(pipeClient, cancellationToken);
101+
break;
102+
103+
case RequestType.InitialUpdatesCompleted when initialUpdates:
104+
return;
105+
106+
default:
107+
// can't continue, the pipe content is in an unknown state
108+
throw new InvalidOperationException($"Unexpected payload type: {payloadType}");
109+
}
110+
}
111+
}
112+
113+
private async ValueTask ReadAndApplyManagedCodeUpdateAsync(
114+
NamedPipeClientStream pipeClient,
115+
CancellationToken cancellationToken)
116+
{
117+
var request = await ManagedCodeUpdateRequest.ReadAsync(pipeClient, cancellationToken);
118+
119+
bool success;
120+
try
121+
{
122+
agent.ApplyManagedCodeUpdates(request.Updates);
123+
success = true;
124+
}
125+
catch (Exception e)
126+
{
127+
agent.Reporter.Report($"The runtime failed to applying the change: {e.Message}", AgentMessageSeverity.Error);
128+
agent.Reporter.Report("Further changes won't be applied to this process.", AgentMessageSeverity.Warning);
129+
success = false;
130+
}
131+
132+
var logEntries = agent.Reporter.GetAndClearLogEntries(request.ResponseLoggingLevel);
133+
134+
var response = new UpdateResponse(logEntries, success);
135+
await response.WriteAsync(pipeClient, cancellationToken);
136+
}
137+
138+
private async ValueTask ReadAndApplyStaticAssetUpdateAsync(
139+
NamedPipeClientStream pipeClient,
140+
CancellationToken cancellationToken)
141+
{
142+
var request = await StaticAssetUpdateRequest.ReadAsync(pipeClient, cancellationToken);
143+
144+
try
145+
{
146+
agent.ApplyStaticAssetUpdate(request.Update);
147+
}
148+
catch (Exception e)
149+
{
150+
agent.Reporter.Report($"Failed to apply static asset update: {e.Message}", AgentMessageSeverity.Error);
151+
}
152+
153+
var logEntries = agent.Reporter.GetAndClearLogEntries(request.ResponseLoggingLevel);
154+
155+
// Updating static asset only invokes ContentUpdate metadata update handlers.
156+
// Failures of these handlers are reported to the log and ignored.
157+
// Therefore, this request always succeeds.
158+
var response = new UpdateResponse(logEntries, success: true);
159+
160+
await response.WriteAsync(pipeClient, cancellationToken);
161+
}
162+
}

0 commit comments

Comments
 (0)