diff --git a/.editorconfig b/.editorconfig index 536c212ea709..45ead881a43c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -464,6 +464,13 @@ dotnet_diagnostic.IDE0161.severity = silent # IDE0005: Remove unused usings. Ignore for shared src files since imports for those depend on the projects in which they are included. dotnet_diagnostic.IDE0005.severity = silent +[{**/microsoft.dotnet.hotreload.agent*/**.cs}] +# IDE0005: Remove unused usings. Ignore for shared src files coming from nuget package. +dotnet_diagnostic.IDE0005.severity = silent +# IDE0073: A source file is missing a required header. Ignore for shared src files coming from nuget package. +dotnet_diagnostic.IDE0073.severity = silent + + # Verify settings [*.{received,verified}.{txt,xml,json}] charset = "utf-8-bom" diff --git a/eng/Dependencies.props b/eng/Dependencies.props index 94bd8109c0c7..cc03487fc95b 100644 --- a/eng/Dependencies.props +++ b/eng/Dependencies.props @@ -25,6 +25,8 @@ and are generated based on the last package release. + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 19c9aad572b1..86a6c11af22f 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -361,6 +361,14 @@ https://github.com/dotnet/dotnet 85778473549347b3e4bad3ea009e9438df7b11bb + + https://github.com/dotnet/dotnet + 5e6dacd4d3debda3266224b2a434811c6fa94987 + + + https://github.com/dotnet/dotnet + 5e6dacd4d3debda3266224b2a434811c6fa94987 + diff --git a/eng/Versions.props b/eng/Versions.props index 81a225c53d77..3136d18a802b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -155,6 +155,9 @@ 10.0.0-preview.4.25260.104 10.0.0-preview.4.25260.104 10.0.0-preview.4.25260.104 + + 10.0.100-preview.5.25258.39 + 10.0.100-preview.5.25258.39 4.13.0-3.24613.7 diff --git a/src/Components/WebAssembly/WebAssembly/src/HotReload/AgentMessageSeverity.cs b/src/Components/WebAssembly/WebAssembly/src/HotReload/AgentMessageSeverity.cs deleted file mode 100644 index 3ab84cecde97..000000000000 --- a/src/Components/WebAssembly/WebAssembly/src/HotReload/AgentMessageSeverity.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.HotReload; - -internal enum AgentMessageSeverity : byte -{ - Verbose = 0, - Warning = 1, - Error = 2, -} diff --git a/src/Components/WebAssembly/WebAssembly/src/HotReload/AgentReporter.cs b/src/Components/WebAssembly/WebAssembly/src/HotReload/AgentReporter.cs deleted file mode 100644 index 0950dac4e387..000000000000 --- a/src/Components/WebAssembly/WebAssembly/src/HotReload/AgentReporter.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq; - -namespace Microsoft.DotNet.HotReload; - -internal sealed class AgentReporter -{ - private readonly List<(string message, AgentMessageSeverity severity)> _log = []; - - public void Report(string message, AgentMessageSeverity severity) - { - _log.Add((message, severity)); - } - - public IReadOnlyCollection<(string message, AgentMessageSeverity severity)> GetAndClearLogEntries(ResponseLoggingLevel level) - { - lock (_log) - { - var filteredLog = (level != ResponseLoggingLevel.Verbose) - ? _log.Where(static entry => entry.severity != AgentMessageSeverity.Verbose) - : _log; - - var log = filteredLog.ToArray(); - _log.Clear(); - return log; - } - } -} diff --git a/src/Components/WebAssembly/WebAssembly/src/HotReload/HotReloadAgent.cs b/src/Components/WebAssembly/WebAssembly/src/HotReload/HotReloadAgent.cs deleted file mode 100644 index b2c50556be7b..000000000000 --- a/src/Components/WebAssembly/WebAssembly/src/HotReload/HotReloadAgent.cs +++ /dev/null @@ -1,203 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; - -namespace Microsoft.DotNet.HotReload; - -#if NET -[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Hot reload is only expected to work when trimming is disabled.")] -#endif -internal sealed class HotReloadAgent : IDisposable -{ - private const string MetadataUpdaterTypeName = "System.Reflection.Metadata.MetadataUpdater"; - private const string ApplyUpdateMethodName = "ApplyUpdate"; - private const string GetCapabilitiesMethodName = "GetCapabilities"; - - private delegate void ApplyUpdateDelegate(Assembly assembly, ReadOnlySpan metadataDelta, ReadOnlySpan ilDelta, ReadOnlySpan pdbDelta); - - private readonly ConcurrentDictionary> _deltas = new(); - private readonly ConcurrentDictionary _appliedAssemblies = new(); - private readonly ApplyUpdateDelegate _applyUpdate; - private readonly MetadataUpdateHandlerInvoker _metadataUpdateHandlerInvoker; - - public AgentReporter Reporter { get; } - public string Capabilities { get; } - - private HotReloadAgent(AgentReporter reporter, ApplyUpdateDelegate applyUpdate, string capabilities) - { - Reporter = reporter; - _metadataUpdateHandlerInvoker = new(reporter); - _applyUpdate = applyUpdate; - Capabilities = capabilities; - - AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad; - } - - public static bool TryCreate(AgentReporter reporter, [NotNullWhen(true)] out HotReloadAgent? agent) - { - GetUpdaterMethodsAndCapabilities(reporter, out var applyUpdate, out var capabilities); - if (applyUpdate != null && !string.IsNullOrEmpty(capabilities)) - { - agent = new HotReloadAgent(reporter, applyUpdate, capabilities); - return true; - } - - agent = null; - return false; - } - - public void Dispose() - { - AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad; - } - - private static void GetUpdaterMethodsAndCapabilities(AgentReporter reporter, out ApplyUpdateDelegate? applyUpdate, out string? capabilities) - { - applyUpdate = null; - capabilities = null; - - var metadataUpdater = Type.GetType(MetadataUpdaterTypeName + ", System.Runtime.Loader", throwOnError: false); - if (metadataUpdater == null) - { - reporter.Report($"Type not found: {MetadataUpdaterTypeName}", AgentMessageSeverity.Error); - return; - } - - var applyUpdateMethod = metadataUpdater.GetMethod(ApplyUpdateMethodName, BindingFlags.Public | BindingFlags.Static, binder: null, [typeof(Assembly), typeof(ReadOnlySpan), typeof(ReadOnlySpan), typeof(ReadOnlySpan)], modifiers: null); - if (applyUpdateMethod == null) - { - reporter.Report($"{MetadataUpdaterTypeName}.{ApplyUpdateMethodName} not found.", AgentMessageSeverity.Error); - return; - } - - applyUpdate = (ApplyUpdateDelegate)applyUpdateMethod.CreateDelegate(typeof(ApplyUpdateDelegate)); - - var getCapabilities = metadataUpdater.GetMethod(GetCapabilitiesMethodName, BindingFlags.NonPublic | BindingFlags.Static, binder: null, Type.EmptyTypes, modifiers: null); - if (getCapabilities == null) - { - reporter.Report($"{MetadataUpdaterTypeName}.{GetCapabilitiesMethodName} not found.", AgentMessageSeverity.Error); - return; - } - - try - { - capabilities = getCapabilities.Invoke(obj: null, parameters: null) as string; - } - catch (Exception e) - { - reporter.Report($"Error retrieving capabilities: {e.Message}", AgentMessageSeverity.Error); - } - } - - private void OnAssemblyLoad(object? _, AssemblyLoadEventArgs eventArgs) - { - _metadataUpdateHandlerInvoker.Clear(); - - var loadedAssembly = eventArgs.LoadedAssembly; - var moduleId = TryGetModuleId(loadedAssembly); - if (moduleId is null) - { - return; - } - - if (_deltas.TryGetValue(moduleId.Value, out var updateDeltas) && _appliedAssemblies.TryAdd(loadedAssembly, loadedAssembly)) - { - // A delta for this specific Module exists and we haven't called ApplyUpdate on this instance of Assembly as yet. - ApplyDeltas(loadedAssembly, updateDeltas); - } - } - - public void ApplyDeltas(IEnumerable deltas) - { - foreach (var delta in deltas) - { - Reporter.Report($"Applying delta to module {delta.ModuleId}.", AgentMessageSeverity.Verbose); - - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - if (TryGetModuleId(assembly) is Guid moduleId && moduleId == delta.ModuleId) - { - _applyUpdate(assembly, delta.MetadataDelta, delta.ILDelta, delta.PdbDelta); - } - } - - // Additionally stash the deltas away so it may be applied to assemblies loaded later. - var cachedDeltas = _deltas.GetOrAdd(delta.ModuleId, static _ => new()); - cachedDeltas.Add(delta); - } - - _metadataUpdateHandlerInvoker.Invoke(GetMetadataUpdateTypes(deltas)); - } - - private Type[] GetMetadataUpdateTypes(IEnumerable deltas) - { - List? types = null; - - foreach (var delta in deltas) - { - var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(assembly => TryGetModuleId(assembly) is Guid moduleId && moduleId == delta.ModuleId); - if (assembly is null) - { - continue; - } - - foreach (var updatedType in delta.UpdatedTypes) - { - // Must be a TypeDef. - Debug.Assert(MetadataTokens.EntityHandle(updatedType) is { Kind: HandleKind.TypeDefinition, IsNil: false }); - - // The type has to be in the manifest module since Hot Reload does not support multi-module assemblies: - try - { - var type = assembly.ManifestModule.ResolveType(updatedType); - types ??= new(); - types.Add(type); - } - catch (Exception e) - { - Reporter.Report($"Failed to load type 0x{updatedType:X8}: {e.Message}", AgentMessageSeverity.Warning); - } - } - } - - return types?.ToArray() ?? Type.EmptyTypes; - } - - private void ApplyDeltas(Assembly assembly, IReadOnlyList deltas) - { - try - { - foreach (var item in deltas) - { - _applyUpdate(assembly, item.MetadataDelta, item.ILDelta, item.PdbDelta); - } - - Reporter.Report("Deltas applied.", AgentMessageSeverity.Verbose); - } - catch (Exception ex) - { - Reporter.Report(ex.ToString(), AgentMessageSeverity.Warning); - } - } - - private static Guid? TryGetModuleId(Assembly loadedAssembly) - { - try - { - return loadedAssembly.Modules.FirstOrDefault()?.ModuleVersionId; - } - catch - { - // Assembly.Modules might throw. See https://github.com/dotnet/aspnetcore/issues/33152 - } - - return default; - } -} diff --git a/src/Components/WebAssembly/WebAssembly/src/HotReload/MetadataUpdateHandlerInvoker.cs b/src/Components/WebAssembly/WebAssembly/src/HotReload/MetadataUpdateHandlerInvoker.cs deleted file mode 100644 index fab00eb691cd..000000000000 --- a/src/Components/WebAssembly/WebAssembly/src/HotReload/MetadataUpdateHandlerInvoker.cs +++ /dev/null @@ -1,244 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using System.Reflection; - -namespace Microsoft.DotNet.HotReload; - -/// -/// Finds and invokes metadata update handlers. -/// -#if NET -[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Hot reload is only expected to work when trimming is disabled.")] -[UnconditionalSuppressMessage("Trimming", "IL2070", Justification = "Hot reload is only expected to work when trimming is disabled.")] -#endif -internal sealed class MetadataUpdateHandlerInvoker(AgentReporter reporter) -{ - internal sealed class RegisteredActions(IReadOnlyList> clearCache, IReadOnlyList> updateApplication) - { - public void Invoke(Type[] updatedTypes) - { - foreach (var action in clearCache) - { - action(updatedTypes); - } - - foreach (var action in updateApplication) - { - action(updatedTypes); - } - } - - /// - /// For testing. - /// - internal IEnumerable> ClearCache => clearCache; - - /// - /// For testing. - /// - internal IEnumerable> UpdateApplication => updateApplication; - } - - private const string ClearCacheHandlerName = "ClearCache"; - private const string UpdateApplicationHandlerName = "UpdateApplication"; - - private RegisteredActions? _actions; - - /// - /// Call when a new assembly is loaded. - /// - internal void Clear() - => Interlocked.Exchange(ref _actions, null); - - /// - /// Invokes all registerd handlers. - /// - internal void Invoke(Type[] updatedTypes) - { - try - { - // Defer discovering metadata updata handlers until after hot reload deltas have been applied. - // This should give enough opportunity for AppDomain.GetAssemblies() to be sufficiently populated. - var actions = _actions; - if (actions == null) - { - Interlocked.CompareExchange(ref _actions, GetMetadataUpdateHandlerActions(), null); - actions = _actions; - } - - reporter.Report($"Invoking metadata update handlers. {updatedTypes.Length} type(s) updated.", AgentMessageSeverity.Verbose); - - actions.Invoke(updatedTypes); - - reporter.Report("Deltas applied.", AgentMessageSeverity.Verbose); - } - catch (Exception e) - { - reporter.Report(e.ToString(), AgentMessageSeverity.Warning); - } - } - - private IEnumerable GetHandlerTypes() - { - // We need to execute MetadataUpdateHandlers in a well-defined order. For v1, the strategy that is used is to topologically - // sort assemblies so that handlers in a dependency are executed before the dependent (e.g. the reflection cache action - // in System.Private.CoreLib is executed before System.Text.Json clears its own cache.) - // This would ensure that caches and updates more lower in the application stack are up to date - // before ones higher in the stack are recomputed. - var sortedAssemblies = TopologicalSort(AppDomain.CurrentDomain.GetAssemblies()); - - foreach (var assembly in sortedAssemblies) - { - foreach (var attr in TryGetCustomAttributesData(assembly)) - { - // Look up the attribute by name rather than by type. This would allow netstandard targeting libraries to - // define their own copy without having to cross-compile. - if (attr.AttributeType.FullName != "System.Reflection.Metadata.MetadataUpdateHandlerAttribute") - { - continue; - } - - IList ctorArgs = attr.ConstructorArguments; - if (ctorArgs.Count != 1 || - ctorArgs[0].Value is not Type handlerType) - { - reporter.Report($"'{attr}' found with invalid arguments.", AgentMessageSeverity.Warning); - continue; - } - - yield return handlerType; - } - } - } - - public RegisteredActions GetMetadataUpdateHandlerActions() - => GetMetadataUpdateHandlerActions(GetHandlerTypes()); - - /// - /// Internal for testing. - /// - internal RegisteredActions GetMetadataUpdateHandlerActions(IEnumerable handlerTypes) - { - var clearCacheActions = new List>(); - var updateApplicationActions = new List>(); - - foreach (var handlerType in handlerTypes) - { - bool methodFound = false; - - if (GetUpdateMethod(handlerType, ClearCacheHandlerName) is MethodInfo clearCache) - { - clearCacheActions.Add(CreateAction(clearCache)); - methodFound = true; - } - - if (GetUpdateMethod(handlerType, UpdateApplicationHandlerName) is MethodInfo updateApplication) - { - updateApplicationActions.Add(CreateAction(updateApplication)); - methodFound = true; - } - - if (!methodFound) - { - reporter.Report( - $"Expected to find a static method '{ClearCacheHandlerName}' or '{UpdateApplicationHandlerName}' on type '{handlerType.AssemblyQualifiedName}' but neither exists.", - AgentMessageSeverity.Warning); - } - } - - return new RegisteredActions(clearCacheActions, updateApplicationActions); - - Action CreateAction(MethodInfo update) - { - var action = (Action)update.CreateDelegate(typeof(Action)); - return types => - { - try - { - action(types); - } - catch (Exception ex) - { - reporter.Report($"Exception from '{action}': {ex}", AgentMessageSeverity.Warning); - } - }; - } - - MethodInfo? GetUpdateMethod(Type handlerType, string name) - { - if (handlerType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, binder: null, [typeof(Type[])], modifiers: null) is MethodInfo updateMethod && - updateMethod.ReturnType == typeof(void)) - { - return updateMethod; - } - - foreach (MethodInfo method in handlerType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)) - { - if (method.Name == name) - { - reporter.Report($"Type '{handlerType}' has method '{method}' that does not match the required signature.", AgentMessageSeverity.Warning); - break; - } - } - - return null; - } - } - - private IList TryGetCustomAttributesData(Assembly assembly) - { - try - { - return assembly.GetCustomAttributesData(); - } - catch (Exception e) - { - // In cross-platform scenarios, such as debugging in VS through WSL, Roslyn - // runs on Windows, and the agent runs on Linux. Assemblies accessible to Windows - // may not be available or loaded on linux (such as WPF's assemblies). - // In such case, we can ignore the assemblies and continue enumerating handlers for - // the rest of the assemblies of current domain. - reporter.Report($"'{assembly.FullName}' is not loaded ({e.Message})", AgentMessageSeverity.Verbose); - return []; - } - } - - /// - /// Internal for testing. - /// - internal static List TopologicalSort(Assembly[] assemblies) - { - var sortedAssemblies = new List(assemblies.Length); - - var visited = new HashSet(StringComparer.Ordinal); - - foreach (var assembly in assemblies) - { - Visit(assemblies, assembly, sortedAssemblies, visited); - } - - static void Visit(Assembly[] assemblies, Assembly assembly, List sortedAssemblies, HashSet visited) - { - var assemblyIdentifier = assembly.GetName().Name!; - if (!visited.Add(assemblyIdentifier)) - { - return; - } - - foreach (var dependencyName in assembly.GetReferencedAssemblies()) - { - var dependency = Array.Find(assemblies, a => a.GetName().Name == dependencyName.Name); - if (dependency is not null) - { - Visit(assemblies, dependency, sortedAssemblies, visited); - } - } - - sortedAssemblies.Add(assembly); - } - - return sortedAssemblies; - } -} diff --git a/src/Components/WebAssembly/WebAssembly/src/HotReload/ResponseLoggingLevel.cs b/src/Components/WebAssembly/WebAssembly/src/HotReload/ResponseLoggingLevel.cs deleted file mode 100644 index dd05c372bf2e..000000000000 --- a/src/Components/WebAssembly/WebAssembly/src/HotReload/ResponseLoggingLevel.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.HotReload; - -internal enum ResponseLoggingLevel : byte -{ - WarningsAndErrors = 0, - Verbose = 1, -} diff --git a/src/Components/WebAssembly/WebAssembly/src/HotReload/UpdateDelta.cs b/src/Components/WebAssembly/WebAssembly/src/HotReload/UpdateDelta.cs deleted file mode 100644 index d02b1c4d46bf..000000000000 --- a/src/Components/WebAssembly/WebAssembly/src/HotReload/UpdateDelta.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.HotReload; - -internal readonly struct UpdateDelta(Guid moduleId, byte[] metadataDelta, byte[] ilDelta, byte[] pdbDelta, int[] updatedTypes) -{ - public Guid ModuleId { get; } = moduleId; - public byte[] MetadataDelta { get; } = metadataDelta; - public byte[] ILDelta { get; } = ilDelta; - public byte[] PdbDelta { get; } = pdbDelta; - public int[] UpdatedTypes { get; } = updatedTypes; -} diff --git a/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs b/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs index fc8eafc861f1..907e3042aa7f 100644 --- a/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs +++ b/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs @@ -69,10 +69,7 @@ internal static async Task InitializeAsync() { s_initialized = true; - if (!HotReloadAgent.TryCreate(s_reporter, out var agent)) - { - return; - } + var agent = new HotReloadAgent(); var existingAgent = Interlocked.CompareExchange(ref s_hotReloadAgent, agent, null); if (existingAgent != null) diff --git a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj index f0418fd10354..90c9b18180da 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj +++ b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj @@ -21,6 +21,8 @@ + +