Skip to content

Commit 2db38ef

Browse files
committed
Retarget Microsoft.Extensions.DotNetDeltaApplier to netstandard2.1
1 parent 72e90cf commit 2db38ef

File tree

6 files changed

+129
-95
lines changed

6 files changed

+129
-95
lines changed

src/BuiltInTools/DotNetDeltaApplier/HotReloadAgent.cs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,53 @@
44
using System;
55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
78
using System.Linq;
89
using System.Reflection;
9-
using System.Reflection.Metadata;
1010

1111
namespace Microsoft.Extensions.HotReload
1212
{
1313
internal sealed class HotReloadAgent : IDisposable
1414
{
15+
private delegate void ApplyUpdateDelegate(Assembly assembly, ReadOnlySpan<byte> metadataDelta, ReadOnlySpan<byte> ilDelta, ReadOnlySpan<byte> pdbDelta);
16+
1517
private readonly Action<string> _log;
1618
private readonly AssemblyLoadEventHandler _assemblyLoad;
1719
private readonly ConcurrentDictionary<Guid, List<UpdateDelta>> _deltas = new();
1820
private readonly ConcurrentDictionary<Assembly, Assembly> _appliedAssemblies = new();
21+
private readonly ApplyUpdateDelegate? _applyUpdate;
22+
private readonly string? _capabilities;
1923
private volatile UpdateHandlerActions? _handlerActions;
2024

2125
public HotReloadAgent(Action<string> log)
2226
{
27+
var metadataUpdater = Type.GetType("System.Reflection.Metadata.MetadataUpdater, System.Runtime.Loader", throwOnError: false);
28+
29+
if (metadataUpdater != null)
30+
{
31+
_applyUpdate = (ApplyUpdateDelegate?)metadataUpdater.GetMethod("ApplyUpdate", BindingFlags.Public | BindingFlags.Static, binder: null,
32+
new[] { typeof(Assembly), typeof(ReadOnlySpan<byte>), typeof(ReadOnlySpan<byte>), typeof(ReadOnlySpan<byte>) }, modifiers: null)?.CreateDelegate(typeof(ApplyUpdateDelegate));
33+
34+
if (_applyUpdate != null)
35+
{
36+
try
37+
{
38+
_capabilities = metadataUpdater.GetMethod("GetCapabilities", BindingFlags.NonPublic | BindingFlags.Static, binder: null, Type.EmptyTypes, modifiers: null)?.
39+
Invoke(obj: null, parameters: null) as string;
40+
}
41+
catch
42+
{
43+
}
44+
}
45+
}
46+
2347
_log = log;
2448
_assemblyLoad = OnAssemblyLoad;
2549
AppDomain.CurrentDomain.AssemblyLoad += _assemblyLoad;
2650
}
2751

52+
public string Capabilities => _capabilities ?? string.Empty;
53+
2854
private void OnAssemblyLoad(object? _, AssemblyLoadEventArgs eventArgs)
2955
{
3056
_handlerActions = null;
@@ -107,7 +133,7 @@ internal void GetHandlerActions(UpdateHandlerActions handlerActions, Type handle
107133

108134
Action<Type[]?> CreateAction(MethodInfo update)
109135
{
110-
Action<Type[]?> action = update.CreateDelegate<Action<Type[]?>>();
136+
var action = (Action<Type[]?>)update.CreateDelegate(typeof(Action<Type[]?>));
111137
return types =>
112138
{
113139
try
@@ -123,7 +149,7 @@ internal void GetHandlerActions(UpdateHandlerActions handlerActions, Type handle
123149

124150
MethodInfo? GetUpdateMethod(Type handlerType, string name)
125151
{
126-
if (handlerType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, new[] { typeof(Type[]) }) is MethodInfo updateMethod &&
152+
if (handlerType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, binder: null, new[] { typeof(Type[]) }, modifiers: null) is MethodInfo updateMethod &&
127153
updateMethod.ReturnType == typeof(void))
128154
{
129155
return updateMethod;
@@ -178,14 +204,17 @@ static void Visit(Assembly[] assemblies, Assembly assembly, List<Assembly> sorte
178204

179205
public void ApplyDeltas(IReadOnlyList<UpdateDelta> deltas)
180206
{
207+
Debug.Assert(Capabilities.Length > 0);
208+
Debug.Assert(_applyUpdate != null);
209+
181210
for (var i = 0; i < deltas.Count; i++)
182211
{
183212
var item = deltas[i];
184213
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
185214
{
186215
if (TryGetModuleId(assembly) is Guid moduleId && moduleId == item.ModuleId)
187216
{
188-
MetadataUpdater.ApplyUpdate(assembly, item.MetadataDelta, item.ILDelta, ReadOnlySpan<byte>.Empty);
217+
_applyUpdate(assembly, item.MetadataDelta, item.ILDelta, ReadOnlySpan<byte>.Empty);
189218
}
190219
}
191220

@@ -244,11 +273,13 @@ private Type[] GetMetadataUpdateTypes(IReadOnlyList<UpdateDelta> deltas)
244273

245274
public void ApplyDeltas(Assembly assembly, IReadOnlyList<UpdateDelta> deltas)
246275
{
276+
Debug.Assert(_applyUpdate != null);
277+
247278
try
248279
{
249280
foreach (var item in deltas)
250281
{
251-
MetadataUpdater.ApplyUpdate(assembly, item.MetadataDelta, item.ILDelta, ReadOnlySpan<byte>.Empty);
282+
_applyUpdate(assembly, item.MetadataDelta, item.ILDelta, ReadOnlySpan<byte>.Empty);
252283
}
253284

254285
_log("Deltas applied.");

src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<!-- Intentionally pinned. This feature is supported in projects targeting 6.0 or newer.-->
4-
<TargetFramework>net7.0</TargetFramework>
3+
<!--
4+
dotnet-watch may inject this assembly to .NET 6.0+ app, so we can't target a newer version.
5+
At the same time source build requires us to not target 6.0, so we fall back to netstandard.
6+
-->
7+
<TargetFramework>netstandard2.1</TargetFramework>
58
<StrongNameKeyId>MicrosoftAspNetCore</StrongNameKeyId>
69

710
<IsPackable>false</IsPackable>

src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ internal sealed class StartupHook
1313
{
1414
private static readonly bool LogDeltaClientMessages = Environment.GetEnvironmentVariable("HOTRELOAD_DELTA_CLIENT_LOG_MESSAGES") == "1";
1515

16+
/// <summary>
17+
/// Invoked by the runtime when the containing assembly is listed in DOTNET_STARTUP_HOOKS.
18+
/// </summary>
1619
public static void Initialize()
1720
{
1821
ClearHotReloadEnvironmentVariables(Environment.GetEnvironmentVariable, Environment.SetEnvironmentVariable);
@@ -77,7 +80,7 @@ public static async Task ReceiveDeltas(HotReloadAgent hotReloadAgent)
7780
return;
7881
}
7982

80-
var initPayload = new ClientInitializationPayload { Capabilities = GetApplyUpdateCapabilities() };
83+
var initPayload = new ClientInitializationPayload(hotReloadAgent.Capabilities);
8184
Log("Writing capabilities: " + initPayload.Capabilities);
8285
initPayload.Write(pipeClient);
8386

@@ -88,19 +91,9 @@ public static async Task ReceiveDeltas(HotReloadAgent hotReloadAgent)
8891

8992
hotReloadAgent.ApplyDeltas(update.Deltas);
9093
pipeClient.WriteByte((byte)ApplyResult.Success);
91-
9294
}
93-
Log("Stopped received delta updates. Server is no longer connected.");
94-
}
9595

96-
private static string GetApplyUpdateCapabilities()
97-
{
98-
var method = typeof(System.Reflection.Metadata.MetadataUpdater).GetMethod("GetCapabilities", BindingFlags.NonPublic | BindingFlags.Static, Type.EmptyTypes);
99-
if (method is null)
100-
{
101-
return string.Empty;
102-
}
103-
return (string)method.Invoke(obj: null, parameters: null)!;
96+
Log("Stopped received delta updates. Server is no longer connected.");
10497
}
10598

10699
private static void Log(string message)

src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,11 @@ public async ValueTask<bool> Apply(DotNetWatchContext context, ImmutableArray<Wa
7474
return false;
7575
}
7676

77-
var payload = new UpdatePayload
78-
{
79-
Deltas = ImmutableArray.CreateRange(solutionUpdate, c => new UpdateDelta
80-
{
81-
ModuleId = c.ModuleId,
82-
ILDelta = c.ILDelta.ToArray(),
83-
MetadataDelta = c.MetadataDelta.ToArray(),
84-
UpdatedTypes = c.UpdatedTypes.ToArray(),
85-
}),
86-
};
77+
var payload = new UpdatePayload(ImmutableArray.CreateRange(solutionUpdate, c => new UpdateDelta(
78+
c.ModuleId,
79+
metadataDelta: c.MetadataDelta.ToArray(),
80+
ilDelta: c.ILDelta.ToArray(),
81+
c.UpdatedTypes.ToArray())));
8782

8883
await payload.WriteAsync(_pipe, cancellationToken);
8984
await _pipe.FlushAsync(cancellationToken);

src/BuiltInTools/dotnet-watch/HotReload/NamedPipeContract.cs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7+
using System.Reflection;
78
using System.Text;
89
using System.Threading;
910
using System.Threading.Tasks;
@@ -14,8 +15,16 @@ internal readonly struct UpdatePayload
1415
{
1516
private static readonly byte Version = 1;
1617

17-
public IReadOnlyList<UpdateDelta> Deltas { get; init; }
18+
public IReadOnlyList<UpdateDelta> Deltas { get; }
1819

20+
public UpdatePayload(IReadOnlyList<UpdateDelta> deltas)
21+
{
22+
Deltas = deltas;
23+
}
24+
25+
/// <summary>
26+
/// Called by the dotnet-watch.
27+
/// </summary>
1928
public async ValueTask WriteAsync(Stream stream, CancellationToken cancellationToken)
2029
{
2130
await using var binaryWriter = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
@@ -54,6 +63,9 @@ static void WriteIntArray(BinaryWriter binaryWriter, int[] values)
5463
}
5564
}
5665

66+
/// <summary>
67+
/// Called by delta applier.
68+
/// </summary>
5769
public static async ValueTask<UpdatePayload> ReadAsync(Stream stream, CancellationToken cancellationToken)
5870
{
5971
using var binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
@@ -68,21 +80,15 @@ public static async ValueTask<UpdatePayload> ReadAsync(Stream stream, Cancellati
6880
var deltas = new UpdateDelta[count];
6981
for (var i = 0; i < count; i++)
7082
{
71-
var delta = new UpdateDelta
72-
{
73-
ModuleId = Guid.Parse(binaryReader.ReadString()),
74-
MetadataDelta = await ReadBytesAsync(binaryReader, cancellationToken),
75-
ILDelta = await ReadBytesAsync(binaryReader, cancellationToken),
76-
UpdatedTypes = ReadIntArray(binaryReader),
77-
};
83+
var moduleId = Guid.Parse(binaryReader.ReadString());
84+
var metadataDelta = await ReadBytesAsync(binaryReader, cancellationToken);
85+
var ilDelta = await ReadBytesAsync(binaryReader, cancellationToken);
86+
var updatedTypes = ReadIntArray(binaryReader);
7887

79-
deltas[i] = delta;
88+
deltas[i] = new UpdateDelta(moduleId, metadataDelta: metadataDelta, ilDelta: ilDelta, updatedTypes);
8089
}
8190

82-
return new UpdatePayload
83-
{
84-
Deltas = deltas,
85-
};
91+
return new UpdatePayload(deltas);
8692

8793
static async ValueTask<byte[]> ReadBytesAsync(BinaryReader binaryReader, CancellationToken cancellationToken)
8894
{
@@ -121,10 +127,18 @@ static int[] ReadIntArray(BinaryReader binaryReader)
121127

122128
internal readonly struct UpdateDelta
123129
{
124-
public Guid ModuleId { get; init; }
125-
public byte[] MetadataDelta { get; init; }
126-
public byte[] ILDelta { get; init; }
127-
public int[] UpdatedTypes { get; init; }
130+
public Guid ModuleId { get; }
131+
public byte[] MetadataDelta { get; }
132+
public byte[] ILDelta { get; }
133+
public int[] UpdatedTypes { get; }
134+
135+
public UpdateDelta(Guid moduleId, byte[] metadataDelta, byte[] ilDelta, int[] updatedTypes)
136+
{
137+
ModuleId = moduleId;
138+
MetadataDelta = metadataDelta;
139+
ILDelta = ilDelta;
140+
UpdatedTypes = updatedTypes;
141+
}
128142
}
129143

130144
internal enum ApplyResult
@@ -137,8 +151,16 @@ internal readonly struct ClientInitializationPayload
137151
{
138152
private const byte Version = 0;
139153

140-
public string Capabilities { get; init; }
154+
public string Capabilities { get; }
155+
156+
public ClientInitializationPayload(string capabilities)
157+
{
158+
Capabilities = capabilities;
159+
}
141160

161+
/// <summary>
162+
/// Called by delta applier.
163+
/// </summary>
142164
public void Write(Stream stream)
143165
{
144166
using var binaryWriter = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
@@ -147,6 +169,9 @@ public void Write(Stream stream)
147169
binaryWriter.Flush();
148170
}
149171

172+
/// <summary>
173+
/// Called by dotnet-watch.
174+
/// </summary>
150175
public static ClientInitializationPayload Read(Stream stream)
151176
{
152177
using var binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
@@ -157,7 +182,7 @@ public static ClientInitializationPayload Read(Stream stream)
157182
}
158183

159184
var capabilities = binaryReader.ReadString();
160-
return new ClientInitializationPayload { Capabilities = capabilities };
185+
return new ClientInitializationPayload(capabilities);
161186
}
162187
}
163188
}

0 commit comments

Comments
 (0)