From a46202a5aab996a1c3d301d1de8f7fb5f5d0031a Mon Sep 17 00:00:00 2001 From: David Date: Mon, 16 Dec 2024 16:37:52 -0500 Subject: [PATCH 1/2] feat(hr): Remove deprecated XAML and partial reload HR modes --- .../RemoteControl/RemoteControlGenerator.cs | 101 +------ .../HotReload/ServerHotReloadProcessor.cs | 74 +---- .../ClientHotReloadProcessor.Agent.cs | 2 +- .../ClientHotReloadProcessor.PartialReload.cs | 205 ------------- .../ClientHotReloadProcessor.Xaml.cs | 285 ------------------ .../HotReload/ClientHotReloadProcessor.cs | 50 +-- .../HotReload/HotReloadMode.cs | 15 - .../HotReload/Messages/ConfigureServer.cs | 5 +- .../HotReload/Messages/FileReload.cs | 31 -- .../ProjectConfigurationAttribute.cs | 24 +- 10 files changed, 22 insertions(+), 770 deletions(-) delete mode 100644 src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.PartialReload.cs delete mode 100644 src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Xaml.cs delete mode 100644 src/Uno.UI.RemoteControl/HotReload/Messages/FileReload.cs diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/RemoteControl/RemoteControlGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/RemoteControl/RemoteControlGenerator.cs index e579923b8ae7..25e5c5f5eda2 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/RemoteControl/RemoteControlGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/RemoteControl/RemoteControlGenerator.cs @@ -19,8 +19,6 @@ namespace Uno.UI.SourceGenerators.RemoteControl [Generator] public class RemoteControlGenerator : ISourceGenerator { - const string LibraryXamlSearchPathAssemblyMetadata = "Uno.RemoteControl.XamlSearchPaths"; - /// /// This list of properties used to capture the environment of the building project /// so it can be propagated to the remote control server's hot reload workspace. @@ -77,16 +75,6 @@ public void Execute(GeneratorExecutionContext context) BuildProjectConfiguration(context, sb); BuildServerProcessorsPaths(context, sb); - context.AddSource("RemoteControl", sb.ToString()); - } - else if (PlatformHelper.IsValidPlatform(context)) - { - var sb = new IndentedStringBuilder(); - - BuildGeneratedFileHeader(sb); - - BuildLibrarySearchPath(context, sb); - context.AddSource("RemoteControl", sb.ToString()); } } @@ -151,100 +139,15 @@ private static void BuildProjectConfiguration(GeneratorExecutionContext context, sb.AppendLineIndented($"[assembly: global::Uno.UI.RemoteControl.ProjectConfigurationAttribute("); sb.AppendLineIndented($"@\"{context.GetMSBuildPropertyValue("MSBuildProjectFullPath")}\",\n"); - var xamlPaths = EnumerateLocalSearchPaths(context) - .Concat(EnumerateLibrarySearchPaths(context)); - - var distinctPaths = string.Join(",\n", xamlPaths.Select(p => $"@\"{p}\"")); - - sb.AppendLineIndented($"new string[]{{{distinctPaths}}},"); - // Provide additional properties so that our hot reload workspace can properly // replicate the original build environment that was used (e.g. VS or dotnet build) - var additionalPropertiesValue = string.Join( - ", ", - AdditionalMSProperties.Select(p => $"@\"{p}={Convert.ToBase64String(Encoding.UTF8.GetBytes(context.GetMSBuildPropertyValue(p)))}\"")); + var additionalPropertiesValues = AdditionalMSProperties.Select(p => $"@\"{p}={Convert.ToBase64String(Encoding.UTF8.GetBytes(context.GetMSBuildPropertyValue(p)))}\""); - sb.AppendLineIndented($"new [] {{ {additionalPropertiesValue} }}"); + sb.AppendLineIndented($"new [] {{ {string.Join(", ", additionalPropertiesValues)} }}"); sb.AppendLineIndented(")]"); } - /// - /// Generates the list of paths used by the current project, to be used by the main project - /// to create the "ProjectConfigurationAttribute". This avoids relying on msbuild to determine - /// paths to search for hot-reloadable files files. - /// - private static void BuildLibrarySearchPath(GeneratorExecutionContext context, IndentedStringBuilder sb) - { - var xamlPaths = EnumerateLocalSearchPaths(context); - - if (xamlPaths.Any()) - { - var distictPaths = string.Join(Path.PathSeparator.ToString(), xamlPaths); - - sb.AppendLineIndented($""" - [assembly: global::System.Reflection.AssemblyMetadata( - "{LibraryXamlSearchPathAssemblyMetadata}", - @"{distictPaths}" - )] - """); - } - } - - private static IEnumerable EnumerateLibrarySearchPaths(GeneratorExecutionContext context) - { - var assemblyMetadataSymbol = context.Compilation.GetTypeByMetadataName("System.Reflection.AssemblyMetadataAttribute"); - - foreach (var reference in context.Compilation.References) - { - if (context.Compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly) - { - var xamlSearchPathAttribute = assembly.GetAttributes().FirstOrDefault(a => - SymbolEqualityComparer.Default.Equals(a.AttributeClass, assemblyMetadataSymbol) - && a.ConstructorArguments.Length == 2 - && a.ConstructorArguments[0].Value is LibraryXamlSearchPathAssemblyMetadata); - - if (xamlSearchPathAttribute is not null) - { - var rawPaths = xamlSearchPathAttribute - ?.ConstructorArguments[1] - .Value - ?.ToString(); - - if (rawPaths is not null) - { - foreach (var path in rawPaths.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) - { - yield return path; - } - } - } - } - } - } - - private static IEnumerable EnumerateLocalSearchPaths(GeneratorExecutionContext context) - { - var msBuildProjectDirectory = context.GetMSBuildPropertyValue("MSBuildProjectDirectory"); - var sources = new[] { - "Page", - "ApplicationDefinition" - }; - - IEnumerable BuildSearchPath(string s) - => context - .GetMSBuildItemsWithAdditionalFiles(s) - .Select(v => Path.IsPathRooted(v.Identity) ? v.Identity : Path.Combine(msBuildProjectDirectory, v.Identity)); - - IEnumerable BuildCompilePaths() - => context.Compilation.SyntaxTrees.Select(s => s.FilePath); - - var xamlPaths = from item in sources.SelectMany(BuildSearchPath).Concat(BuildCompilePaths()) - select Path.GetDirectoryName(item); - - return xamlPaths.Distinct().Where(x => !string.IsNullOrEmpty(x)); - } - private static void BuildEndPointAttribute(GeneratorExecutionContext context, IndentedStringBuilder sb) { var unoRemoteControlPort = context.GetMSBuildPropertyValue("UnoRemoteControlPort"); diff --git a/src/Uno.UI.RemoteControl.Server.Processors/HotReload/ServerHotReloadProcessor.cs b/src/Uno.UI.RemoteControl.Server.Processors/HotReload/ServerHotReloadProcessor.cs index 850d7e92f33a..9a4cacaf5023 100644 --- a/src/Uno.UI.RemoteControl.Server.Processors/HotReload/ServerHotReloadProcessor.cs +++ b/src/Uno.UI.RemoteControl.Server.Processors/HotReload/ServerHotReloadProcessor.cs @@ -26,8 +26,6 @@ namespace Uno.UI.RemoteControl.Host.HotReload { partial class ServerHotReloadProcessor : IServerProcessor, IDisposable { - private FileSystemWatcher[]? _watchers; - private CompositeDisposable? _watcherEventsDisposable; private readonly IRemoteControlServer _remoteControlServer; public ServerHotReloadProcessor(IRemoteControlServer remoteControlServer) @@ -397,68 +395,18 @@ private void ProcessConfigureServer(ConfigureServer configureServer) if (this.Log().IsEnabled(LogLevel.Debug)) { this.Log().LogDebug($"Base project path: {configureServer.ProjectPath}"); - this.Log().LogDebug($"Xaml Search Paths: {string.Join(", ", configureServer.XamlPaths)}"); } - if (!InitializeMetadataUpdater(configureServer)) + if (InitializeMetadataUpdater(configureServer)) { - // We are relying on IDE (or XAML only), we won't have any other hot-reload initialization steps. - _ = Notify(HotReloadEvent.Ready); + this.Log().LogDebug($"Metadata updater initialized"); } - - _watchers = configureServer.XamlPaths - .Select(p => new FileSystemWatcher - { - Path = p, - Filter = "*.*", - NotifyFilter = NotifyFilters.LastWrite | - NotifyFilters.Attributes | - NotifyFilters.Size | - NotifyFilters.CreationTime | - NotifyFilters.FileName, - EnableRaisingEvents = true, - IncludeSubdirectories = false - }) - .ToArray(); - - _watcherEventsDisposable = new CompositeDisposable(); - - foreach (var watcher in _watchers) + else { - var disposable = ToObservable(watcher).Subscribe( - filePaths => - { - var files = filePaths - .Distinct() - .Where(f => - Path.GetExtension(f).Equals(".xaml", StringComparison.OrdinalIgnoreCase) - || Path.GetExtension(f).Equals(".cs", StringComparison.OrdinalIgnoreCase)); - - foreach (var file in files) - { - OnSourceFileChanged(file); - } - }, - e => Console.WriteLine($"Error {e}")); - - _watcherEventsDisposable.Add(disposable); + // We are relying on IDE, we won't have any other hot-reload initialization steps. + _ = Notify(HotReloadEvent.Ready); + this.Log().LogDebug("Metadata updater **NOT** initialized."); } - - void OnSourceFileChanged(string fullPath) - => Task.Run(async () => - { - if (this.Log().IsEnabled(LogLevel.Debug)) - { - this.Log().LogDebug($"File {fullPath} changed"); - } - - await _remoteControlServer.SendFrame( - new FileReload - { - Content = File.ReadAllText(fullPath), - FilePath = fullPath - }); - }); } #endregion @@ -643,16 +591,6 @@ private async Task RequestHotReloadToIde(long sequenceId) public void Dispose() { - _watcherEventsDisposable?.Dispose(); - - if (_watchers != null) - { - foreach (var watcher in _watchers) - { - watcher.Dispose(); - } - } - _solutionWatcherEventsDisposable?.Dispose(); if (_solutionWatchers != null) { diff --git a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Agent.cs b/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Agent.cs index 5b709de8bb16..30b8ba01f337 100644 --- a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Agent.cs +++ b/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Agent.cs @@ -79,7 +79,7 @@ private void CheckMetadataUpdatesSupport() //var targetFramework = GetMSBuildProperty("TargetFramework"); var buildingInsideVisualStudio = GetMSBuildProperty("BuildingInsideVisualStudio").Equals("true", StringComparison.OrdinalIgnoreCase); - var isForcedMetadata = _forcedHotReloadMode is HotReloadMode.MetadataUpdates or HotReloadMode.Partial; + var isForcedMetadata = _forcedHotReloadMode is HotReloadMode.MetadataUpdates; var isSkia = unoRuntimeIdentifier.Equals("skia", StringComparison.OrdinalIgnoreCase); var isWasm = unoRuntimeIdentifier.Equals("webassembly", StringComparison.OrdinalIgnoreCase); diff --git a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.PartialReload.cs b/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.PartialReload.cs deleted file mode 100644 index 1b674d6773c4..000000000000 --- a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.PartialReload.cs +++ /dev/null @@ -1,205 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Uno.Extensions; -using Uno.Foundation.Logging; -using Uno.UI.RemoteControl.HotReload.Messages; -using System.Runtime.Loader; -using System.Runtime.CompilerServices; -using System.Diagnostics; - -namespace Uno.UI.RemoteControl.HotReload -{ - partial class ClientHotReloadProcessor - { - private Dictionary? _mappedTypes; - - private bool _supportsPartialHotReload; - - private Task? _partialReload; - private readonly object _partialReloadGate = new(); - - private void InitializePartialReload() - { - var unoRuntimeIdentifier = GetMSBuildProperty("UnoRuntimeIdentifier"); - var targetFramework = GetMSBuildProperty("TargetFramework"); - var buildingInsideVisualStudio = GetMSBuildProperty("BuildingInsideVisualStudio"); - var msBuildVersion = GetMSBuildProperty("MSBuildVersion"); - - _supportsPartialHotReload = - buildingInsideVisualStudio.Equals("true", StringComparison.OrdinalIgnoreCase) - && _forcedHotReloadMode is null or HotReloadMode.Partial - && (Version.TryParse(msBuildVersion, out var v) is false || v is not { Major: >= 17, Minor: >= 12 }) - && ( - // As of VS 17.8, when the debugger is attached, mobile targets don't invoke MetadataUpdateHandlers - // and both targets are not providing updated types. We simulate parts of this process - // to determine which types have been updated, particularly those with "CreateNewOnMetadataUpdate". - // - // Disabled until https://github.com/dotnet/runtime/issues/93860 is fixed - // - (Debugger.IsAttached - && IsIssue93860Fixed() - && (targetFramework.Contains("-android") - || targetFramework.Contains("-ios"))) - - // WebAssembly does not support sending updated types, and does not support debugger based hot reload. - || (unoRuntimeIdentifier?.Equals("WebAssembly", StringComparison.OrdinalIgnoreCase) ?? false)); - - if (this.Log().IsEnabled(LogLevel.Trace)) - { - this.Log().Trace($"Partial Hot Reload Enabled:{_supportsPartialHotReload} " + - $"unoRuntimeIdentifier:{unoRuntimeIdentifier} " + - $"targetFramework:{targetFramework} " + - $"buildingInsideVisualStudio:{targetFramework} " + - $"debuggerAttached:{Debugger.IsAttached} " + - $"IsIssue93860Fixed:{IsIssue93860Fixed()}"); - } - - _mappedTypes = _supportsPartialHotReload - ? BuildMappedTypes() - : new(); - } - - private async Task PartialReload(FileReload fileReload) - { - if (!_supportsPartialHotReload) - { - if (this.Log().IsEnabled(LogLevel.Debug)) - { - this.Log().Debug($"Skipping file reload"); - } - - return; - } - - if (!fileReload.IsValid()) - { - if (fileReload.FilePath is not null && this.Log().IsEnabled(LogLevel.Debug)) - { - this.Log().LogDebug($"FileReload is missing a file path"); - } - - if (fileReload.Content is null && this.Log().IsEnabled(LogLevel.Debug)) - { - this.Log().LogDebug($"FileReload is missing content"); - } - - return; - } - - lock (_partialReloadGate) - { - if (_partialReload is null or { Status: TaskStatus.RanToCompletion or TaskStatus.Faulted }) - { - _partialReload = PerformPartialReload(); - } - else - { - if (this.Log().IsEnabled(LogLevel.Debug)) - { - this.Log().Debug($"PartialReload: Waiting for existing type observer"); - } - } - } - - await _partialReload; - } - - private async Task PerformPartialReload() - { - var originalMappedTypes = _mappedTypes ?? new(); - var sw = Stopwatch.StartNew(); - - if (this.Log().IsEnabled(LogLevel.Trace)) - { - this.Log().Trace($"ObserveUpdateTypeMapping: Start observing (Original mapped types {originalMappedTypes.Count})"); - } - - while (sw.Elapsed < TimeSpan.FromSeconds(15)) - { - // Arbitrary delay to wait for VS to push updates to the app - // so we can discover which types have changed - // The scanning operation can take 500ms under wasm, keep the app - // running for longer to ensure we don't miss any updates -#if __WASM__ - await Task.Delay(1000); -#else - await Task.Delay(250); -#endif - - var mappedSw = Stopwatch.StartNew(); - - // Lookup for types marked with MetadataUpdateOriginalTypeAttribute - var mappedTypes = BuildMappedTypes(); - - if (this.Log().IsEnabled(LogLevel.Trace)) - { - this.Log().Trace($"ObserveUpdateTypeMapping: fetched mapped types {mappedTypes.Count} in {mappedSw.Elapsed}"); - } - - if (!mappedTypes.Values.All(b => originalMappedTypes.ContainsValue(b))) - { - _mappedTypes = mappedTypes; - - var newTypes = mappedTypes - .Values - .Except(originalMappedTypes.Values) - .ToArray(); - - if (this.Log().IsEnabled(LogLevel.Trace)) - { - var types = string.Join(", ", _mappedTypes.Values); - - this.Log().Trace($"Found {newTypes.Length} updated types ({types})"); - } - - if (_agent is not null) - { - var actions = _agent.GetMetadataUpdateHandlerActions(); - - actions.ClearCache.ForEach(a => a(newTypes)); - actions.UpdateApplication.ForEach(a => a(newTypes)); - - if (this.Log().IsEnabled(LogLevel.Trace)) - { - this.Log().Trace($"ObserveUpdateTypeMapping: Invoked metadata updaters"); - } - } - - return; - } - } - - if (this.Log().IsEnabled(LogLevel.Trace)) - { - this.Log().Trace($"ObserveUpdateTypeMapping: Stopped observing after timeout"); - } - } - - private Dictionary BuildMappedTypes() - { - var mappedTypes = - from asm in AssemblyLoadContext.Default.Assemblies - let debuggableAttribute = asm.GetCustomAttribute() - where debuggableAttribute is not null - && (debuggableAttribute.DebuggingFlags & DebuggableAttribute.DebuggingModes.DisableOptimizations) != 0 - from type in asm.GetTypes() - let originalType = type.GetCustomAttribute() - where originalType is not null - group type by originalType.OriginalType into g - select new - { - Key = g.Key.FullName, - Type = g.Key, - LastMapped = g.OrderBy(t => t.FullName).Last() - }; - - return mappedTypes.ToDictionary(p => p.Key, p => p.LastMapped); - } - } -} diff --git a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Xaml.cs b/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Xaml.cs deleted file mode 100644 index b83e43178023..000000000000 --- a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.Xaml.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Uno.Extensions; -using Uno.Foundation.Logging; -using Uno.UI.Extensions; -using Uno.UI.Helpers; -using Uno.UI.RemoteControl.HotReload; -using Uno.UI.RemoteControl.HotReload.Messages; -using Uno.UI.Xaml; -using Windows.Storage.Pickers.Provider; -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Markup; -using Microsoft.UI.Xaml.Media; -#if __IOS__ -using _View = UIKit.UIView; -#else -using _View = Microsoft.UI.Xaml.FrameworkElement; -#endif - -#if __IOS__ -using UIKit; -#elif __MACOS__ -using AppKit; -#elif __ANDROID__ -using Uno.UI; -#endif - -[assembly: System.Reflection.Metadata.MetadataUpdateHandler(typeof(Uno.UI.RemoteControl.HotReload.ClientHotReloadProcessor))] - -namespace Uno.UI.RemoteControl.HotReload -{ - partial class ClientHotReloadProcessor - { - private bool _supportsXamlReader; - -#if __IOS__ || __CATALYST__ || __ANDROID__ - private bool? _isIssue93860Fixed; -#endif - - private void InitializeXamlReader() - { - var targetFramework = GetMSBuildProperty("TargetFramework"); - var buildingInsideVisualStudio = GetMSBuildProperty("BuildingInsideVisualStudio").Equals("true", StringComparison.OrdinalIgnoreCase); - - // As of VS 17.8, the only target which supports - // - // Disabled until https://github.com/dotnet/runtime/issues/93860 is fixed - // - _supportsXamlReader = - !IsIssue93860Fixed() - && (targetFramework.Contains("-android", StringComparison.OrdinalIgnoreCase) - || targetFramework.Contains("-ios", StringComparison.OrdinalIgnoreCase) - || targetFramework.Contains("-maccatalyst", StringComparison.OrdinalIgnoreCase)); - - if (this.Log().IsEnabled(LogLevel.Trace)) - { - this.Log().Trace($"XamlReader Hot Reload Enabled:{_supportsXamlReader} " + - $"targetFramework:{targetFramework}"); - } - } - - private bool IsIssue93860Fixed() - { -#if __IOS__ || __CATALYST__ || __ANDROID__ - -#if __IOS__ || __CATALYST__ - var assembly = typeof(global::Foundation.NSObject).GetTypeInfo().Assembly; -#elif __ANDROID__ - var assembly = typeof(global::Android.Views.ViewGroup).GetTypeInfo().Assembly; -#endif - if (_isIssue93860Fixed is null) - { - // If we can't find or parse the version attribute, we're assuming #93860 is fixed. - _isIssue93860Fixed = true; - - if (assembly.GetCustomAttribute() is AssemblyInformationalVersionAttribute aiva) - { -#if __IOS__ || __CATALYST__ || __ANDROID__ - if (this.Log().IsEnabled(LogLevel.Trace)) - { - this.Log().Trace($"iOS/Catalyst/Android do not support C# based XAML hot reload: https://github.com/unoplatform/uno/issues/15918"); - } - - _isIssue93860Fixed = false; - return false; -#else - if (this.Log().IsEnabled(LogLevel.Trace)) - { - this.Log().Trace($".NET Platform Bindings Version: {aiva.InformationalVersion}"); - } - - // From 8.0.100 assemblies: - // 34.0.1.42; git-rev-head:f1b7113; git-branch:release/8.0.1xx - - var parts = aiva.InformationalVersion.Split(';'); - - if (parts.Length > 0 && Version.TryParse(parts[0], out var version)) - { - _isIssue93860Fixed = version >= -#if __ANDROID__ - new Version(34, 0, 1, 52); // 8.0.102 -#endif - - if (!_isIssue93860Fixed.Value && this.Log().IsEnabled(LogLevel.Warning)) - { - this.Log().Warn( - $"The .NET Platform Bindings version {version} is too old " + - $"and contains this issue: https://github.com/dotnet/runtime/issues/93860. " + - $"Make sure to upgrade to .NET 8.0.102 or later"); - } - } -#endif - } - } - - return _isIssue93860Fixed.Value; -#else - // XAML Reader should not be used for non-mobile targets. - return false; -#endif - } - - private void ReloadFileWithXamlReader(FileReload fileReload) - { - if (!fileReload.IsValid()) - { - if (fileReload.FilePath.IsNullOrEmpty() && this.Log().IsEnabled(LogLevel.Debug)) - { - this.Log().LogDebug($"FileReload is missing a file path"); - } - - if (fileReload.Content is null && this.Log().IsEnabled(LogLevel.Debug)) - { - this.Log().LogDebug($"FileReload is missing content"); - } - - return; - } - - var op = _status.ReportLocalStarting([]); - - _ = Windows.ApplicationModel.Core.CoreApplication.MainView.Dispatcher.RunAsync( - Windows.UI.Core.CoreDispatcherPriority.Normal, - async () => - { - await ReloadWithFileAndContent(op, fileReload.FilePath, fileReload.Content); - - RemoteControlClient.Instance?.NotifyOfEvent(nameof(FileReload), fileReload.FilePath); - }); - } - - private async Task ReloadWithFileAndContent(HotReloadClientOperation op, string filePath, string fileContent) - { - try - { - if (this.Log().IsEnabled(LogLevel.Debug)) - { - this.Log().LogDebug($"XamlReader reloading changed file [{filePath}]"); - } - - var uri = new Uri("file:///" + filePath.Replace('\\', '/')); - - Application.RegisterComponent(uri, fileContent); - - bool IsSameBaseUri(FrameworkElement i) - { - return uri.OriginalString == i.DebugParseContext?.LocalFileUri - - // Compatibility with older versions of Uno, where BaseUri is set to the - // local file path instead of the component Uri. - || uri.OriginalString == i.BaseUri?.OriginalString; - } - - foreach (var window in ApplicationHelper.Windows) - { - if (window.Content is null) - { - return; - } - - foreach (var instance in EnumerateInstances(window.Content, IsSameBaseUri).OfType()) - { - if (XamlReader.LoadUsingXClass(fileContent, uri.ToString()) is FrameworkElement newContent) - { - SwapViews(instance, newContent); - op.AddType(newContent.GetType()); - } - } - } - - if (ResourceResolver.RetrieveDictionaryForFilePath(uri.AbsolutePath) is { } targetDictionary) - { - var replacementDictionary = (ResourceDictionary)XamlReader.Load(fileContent); - targetDictionary.CopyFrom(replacementDictionary); - Application.Current.UpdateResourceBindingsForHotReload(); - } - - if (op.Types.Length is 0) - { - op.ReportIgnored("No instances found"); - } - else - { - op.ReportCompleted(); - } - } - catch (Exception e) - { - if (e is TargetInvocationException { InnerException: { } innerException }) - { - e = innerException; - } - - if (this.Log().IsEnabled(LogLevel.Error)) - { - this.Log().LogError($"Failed reloading changed file [{filePath}]", e); - } - - await _rcClient.SendMessage( - new HotReload.Messages.XamlLoadError( - filePath: filePath, - exceptionType: e.GetType().ToString(), - message: e.Message, - stackTrace: e.StackTrace)); - - op.ReportError(e); - } - } - - private static IEnumerable EnumerateInstances(object instance, Func predicate) - { - if (instance is FrameworkElement fe && predicate(fe)) - { - yield return fe; - } - else if (instance != null) - { - IEnumerable> Dig() - { - switch (instance) - { - case Panel panel: - foreach (var child in panel.Children) - { - yield return EnumerateInstances(child, predicate); - } - break; - - case Border border: - yield return EnumerateInstances(border.Child, predicate); - break; - - case ContentControl control when control.ContentTemplateRoot != null || control.Content != null: - yield return EnumerateInstances(control.ContentTemplateRoot ?? control.Content, predicate); - break; - - case Control control: - yield return EnumerateInstances(control.TemplatedRoot, predicate); - break; - - case ContentPresenter presenter: - yield return EnumerateInstances(presenter.Content, predicate); - break; - } - } - - foreach (var inner in Dig()) - { - foreach (var validElement in inner) - { - yield return validElement; - } - } - } - } - } -} diff --git a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.cs b/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.cs index e69677037803..6ead2216e6dd 100644 --- a/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.cs +++ b/src/Uno.UI.RemoteControl/HotReload/ClientHotReloadProcessor.cs @@ -3,22 +3,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Uno.Foundation.Logging; using Uno.UI.RemoteControl.HotReload.Messages; -using Uno.Diagnostics.UI; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Reflection; -using Uno.UI.Helpers; namespace Uno.UI.RemoteControl.HotReload; public partial class ClientHotReloadProcessor : IClientProcessor { private string? _projectPath; - private string[]? _xamlPaths; private readonly IRemoteControlClient _rcClient; private HotReloadMode? _forcedHotReloadMode; @@ -49,10 +42,6 @@ public async Task ProcessFrame(Messages.Frame frame) ProcessUpdateFileResponse(frame.GetContent()); break; - case FileReload.Name: - await ProcessFileReload(frame.GetContent()); - break; - case HotReloadWorkspaceLoadResult.Name: WorkspaceLoadResult(frame.GetContent()); break; @@ -72,23 +61,6 @@ public async Task ProcessFrame(Messages.Frame frame) partial void ProcessUpdateFileResponse(UpdateFileResponse response); - private async Task ProcessFileReload(HotReload.Messages.FileReload fileReload) - { - if (( - _forcedHotReloadMode is null - && !_supportsPartialHotReload - && !_serverMetadataUpdatesEnabled - && _supportsXamlReader) - || _forcedHotReloadMode == HotReloadMode.XamlReader) - { - ReloadFileWithXamlReader(fileReload); - } - else - { - await PartialReload(fileReload); - } - } - #region Configure hot-reload private async Task ConfigureServer() { @@ -102,36 +74,18 @@ private async Task ConfigureServer() var config = configs.First(); _projectPath = config.ProjectPath; - _xamlPaths = config.XamlPaths; - - if (this.Log().IsEnabled(LogLevel.Debug)) - { - this.Log().LogDebug($"ProjectConfigurationAttribute={config.ProjectPath}, Paths={_xamlPaths.Length}"); - } - - if (this.Log().IsEnabled(LogLevel.Trace)) - { - foreach (var path in _xamlPaths) - { - this.Log().Trace($"\t- {path}"); - } - } _msbuildProperties = Messages.ConfigureServer.BuildMSBuildProperties(config.MSBuildProperties); ConfigureHotReloadMode(); InitializeMetadataUpdater(); - InitializePartialReload(); - InitializeXamlReader(); - if (!_supportsMetadataUpdates - && !_supportsPartialHotReload - && !_supportsXamlReader) + if (!_supportsMetadataUpdates) { _status.ReportInvalidRuntime(); } - ConfigureServer message = new(_projectPath, _xamlPaths, GetMetadataUpdateCapabilities(), _serverMetadataUpdatesEnabled, config.MSBuildProperties); + ConfigureServer message = new(_projectPath, GetMetadataUpdateCapabilities(), _serverMetadataUpdatesEnabled, config.MSBuildProperties); await _rcClient.SendMessage(message); } diff --git a/src/Uno.UI.RemoteControl/HotReload/HotReloadMode.cs b/src/Uno.UI.RemoteControl/HotReload/HotReloadMode.cs index dee71d903f9d..454d973b3bd3 100644 --- a/src/Uno.UI.RemoteControl/HotReload/HotReloadMode.cs +++ b/src/Uno.UI.RemoteControl/HotReload/HotReloadMode.cs @@ -12,19 +12,4 @@ internal enum HotReloadMode /// /// This can be metadata-updates pushed by either VS or the dev-server. MetadataUpdates, - - /// - /// Hot Reload using partial updated types discovery. - /// - /// - /// In some cases application's MetadataUpdateHandlers are not invoked by the IDE. - /// When this mode is active, application listen for FileReload (a.k.a. FileUpdated) messages, enumerates (after a small delay) all types loaded in the application to detect changes - /// and invokes the MetadataUpdateHandlers **for types flags with the CreateNewOnMetadataUpdateAttribute**. - /// - Partial, - - /// - /// Hot Reload using XAML reader - /// - XamlReader, } diff --git a/src/Uno.UI.RemoteControl/HotReload/Messages/ConfigureServer.cs b/src/Uno.UI.RemoteControl/HotReload/Messages/ConfigureServer.cs index 98427b522f6c..5dfce86d3a91 100644 --- a/src/Uno.UI.RemoteControl/HotReload/Messages/ConfigureServer.cs +++ b/src/Uno.UI.RemoteControl/HotReload/Messages/ConfigureServer.cs @@ -9,10 +9,9 @@ class ConfigureServer : IMessage public const string Name = nameof(ConfigureServer); private Dictionary? _msbuildPropertiesCache; - public ConfigureServer(string projectPath, string[] xamlPaths, string[] metadataUpdateCapabilities, bool enableMetadataUpdates, string[] msbuildPropertiesRaw) + public ConfigureServer(string projectPath, string[] metadataUpdateCapabilities, bool enableMetadataUpdates, string[] msbuildPropertiesRaw) { ProjectPath = projectPath; - XamlPaths = xamlPaths; MetadataUpdateCapabilities = metadataUpdateCapabilities; MSBuildPropertiesRaw = msbuildPropertiesRaw; EnableMetadataUpdates = enableMetadataUpdates; @@ -22,8 +21,6 @@ public ConfigureServer(string projectPath, string[] xamlPaths, string[] metadata public string[] MSBuildPropertiesRaw { get; set; } - public string[] XamlPaths { get; set; } - public string[] MetadataUpdateCapabilities { get; set; } public bool EnableMetadataUpdates { get; set; } diff --git a/src/Uno.UI.RemoteControl/HotReload/Messages/FileReload.cs b/src/Uno.UI.RemoteControl/HotReload/Messages/FileReload.cs deleted file mode 100644 index e981104fbd56..000000000000 --- a/src/Uno.UI.RemoteControl/HotReload/Messages/FileReload.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Newtonsoft.Json; -using Uno.Extensions; - -namespace Uno.UI.RemoteControl.HotReload.Messages -{ - /// - /// Message sent by the dev-server when it detects a file change in the solution. - /// - /// This is being sent only for xaml and cs files. - internal class FileReload : IMessage // a.k.a. FileUpdated - { - public const string Name = nameof(FileReload); - - [JsonProperty] - public string? FilePath { get; set; } - - [JsonProperty] - public string? Content { get; set; } - - [JsonIgnore] - public string Scope => WellKnownScopes.HotReload; - - [JsonIgnore] - string IMessage.Name => Name; - - [MemberNotNullWhen(true, nameof(FilePath), nameof(Content))] - public bool IsValid() - => !FilePath.IsNullOrEmpty() && Content is not null; - } -} diff --git a/src/Uno.UI.RemoteControl/ProjectConfigurationAttribute.cs b/src/Uno.UI.RemoteControl/ProjectConfigurationAttribute.cs index f282030f8d40..c6779515cc14 100644 --- a/src/Uno.UI.RemoteControl/ProjectConfigurationAttribute.cs +++ b/src/Uno.UI.RemoteControl/ProjectConfigurationAttribute.cs @@ -2,22 +2,18 @@ using System.Collections.Generic; using System.Text; -namespace Uno.UI.RemoteControl +namespace Uno.UI.RemoteControl; + +[System.AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)] +public sealed class ProjectConfigurationAttribute : Attribute { - [System.AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)] - public sealed class ProjectConfigurationAttribute : Attribute + public ProjectConfigurationAttribute(string projectPath, string[] msbuildProperties) { - public ProjectConfigurationAttribute(string projectPath, string[] xamlPaths, string[] msbuildProperties) - { - ProjectPath = projectPath; - XamlPaths = xamlPaths; - MSBuildProperties = msbuildProperties; - } - - public string ProjectPath { get; } + ProjectPath = projectPath; + MSBuildProperties = msbuildProperties; + } - public string[] MSBuildProperties { get; } + public string ProjectPath { get; } - public string[] XamlPaths { get; } - } + public string[] MSBuildProperties { get; } } From f1ee3674151eb5e3b5865bad7622d961fcae4853 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 17 Dec 2024 11:30:34 -0500 Subject: [PATCH 2/2] chore: Remove dead code --- .../HotReload/ServerHotReloadProcessor.cs | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/src/Uno.UI.RemoteControl.Server.Processors/HotReload/ServerHotReloadProcessor.cs b/src/Uno.UI.RemoteControl.Server.Processors/HotReload/ServerHotReloadProcessor.cs index 9a4cacaf5023..1d97bac73364 100644 --- a/src/Uno.UI.RemoteControl.Server.Processors/HotReload/ServerHotReloadProcessor.cs +++ b/src/Uno.UI.RemoteControl.Server.Processors/HotReload/ServerHotReloadProcessor.cs @@ -604,39 +604,6 @@ public void Dispose() } #region Helpers - private static IObservable> ToObservable(params FileSystemWatcher[] watchers) - => Observable.Defer(() => - { - // Create an observable instead of using the FromEventPattern which - // does not register to events properly. - // Renames are required for the WriteTemporary->DeleteOriginal->RenameToOriginal that - // Visual Studio uses to save files. - - var subject = new Subject(); - - void changed(object s, FileSystemEventArgs args) => subject.OnNext(args.FullPath); - void renamed(object s, RenamedEventArgs args) => subject.OnNext(args.FullPath); - - foreach (var watcher in watchers) - { - watcher.Changed += changed; - watcher.Created += changed; - watcher.Renamed += renamed; - } - - return subject - .Buffer(() => subject.Throttle(TimeSpan.FromMilliseconds(250))) // Wait for 250 ms without any file change - .Finally(() => - { - foreach (var watcher in watchers) - { - watcher.Changed -= changed; - watcher.Created -= changed; - watcher.Renamed -= renamed; - } - }); - }); - private static IObservable>> To2StepsObservable(FileSystemWatcher[] watchers, Predicate filter) => Observable.Create>>(o => {