Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hr): Remove deprecated XAML and partial reload HR modes #19092

Merged
merged 2 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ namespace Uno.UI.SourceGenerators.RemoteControl
[Generator]
public class RemoteControlGenerator : ISourceGenerator
{
const string LibraryXamlSearchPathAssemblyMetadata = "Uno.RemoteControl.XamlSearchPaths";

/// <summary>
/// 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.
Expand Down Expand Up @@ -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());
}
}
Expand Down Expand Up @@ -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(")]");
}

/// <summary>
/// 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.
/// </summary>
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<string> 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<string> EnumerateLocalSearchPaths(GeneratorExecutionContext context)
{
var msBuildProjectDirectory = context.GetMSBuildPropertyValue("MSBuildProjectDirectory");
var sources = new[] {
"Page",
"ApplicationDefinition"
};

IEnumerable<string> BuildSearchPath(string s)
=> context
.GetMSBuildItemsWithAdditionalFiles(s)
.Select(v => Path.IsPathRooted(v.Identity) ? v.Identity : Path.Combine(msBuildProjectDirectory, v.Identity));

IEnumerable<string> 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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -643,16 +591,6 @@ private async Task<bool> RequestHotReloadToIde(long sequenceId)

public void Dispose()
{
_watcherEventsDisposable?.Dispose();

if (_watchers != null)
{
foreach (var watcher in _watchers)
{
watcher.Dispose();
}
}

_solutionWatcherEventsDisposable?.Dispose();
if (_solutionWatchers != null)
{
Expand All @@ -666,39 +604,6 @@ public void Dispose()
}

#region Helpers
private static IObservable<IList<string>> 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<string>();

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<Task<ImmutableHashSet<string>>> To2StepsObservable(FileSystemWatcher[] watchers, Predicate<string> filter)
=> Observable.Create<Task<ImmutableHashSet<string>>>(o =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading
Loading