From 753965d729448f1401fe46354c9513bbcdc31004 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 1 Oct 2024 16:54:02 -0400 Subject: [PATCH 1/3] feat: Add extensiblity support in dev-server --- .../Extensibility/AddIns.cs | 26 +++++ .../Extensibility/AddInsExtensions.cs | 17 +++ .../AttributeDataExtensions.cs | 101 ++++++++++++++++++ .../ServiceAttribute.cs | 22 ++++ .../ServiceCollectionServiceExtensions.cs | 63 +++++++++++ .../Helpers/AssemblyHelper.cs | 36 +++++++ src/Uno.UI.RemoteControl.Host/Program.cs | 22 +++- .../Helpers/ProcessHelper.cs | 4 +- .../CompilationWorkspaceProvider.cs | 2 +- src/Uno.UI.RemoteControl.VS/EntryPoint.cs | 2 +- 10 files changed, 291 insertions(+), 4 deletions(-) create mode 100644 src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs create mode 100644 src/Uno.UI.RemoteControl.Host/Extensibility/AddInsExtensions.cs create mode 100644 src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs create mode 100644 src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs create mode 100644 src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs create mode 100644 src/Uno.UI.RemoteControl.Host/Helpers/AssemblyHelper.cs diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs new file mode 100644 index 000000000000..38cfab4f27c8 --- /dev/null +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Text.RegularExpressions; +using Uno.UI.RemoteControl.Helpers; + +namespace Uno.UI.RemoteControl.Host.Extensibility; + +public class AddIns +{ + public static IImmutableList Discover(string solutionFile) + => ProcessHelper.RunProcess("dotnet", $"build \"{solutionFile}\" /t:GetRemoteControlAddIns /nowarn:MSB4057") switch // Ignore missing target + { + // Note: We ignore the exitCode not being 0: even if flagged as nowarn, we can still get MSB4057 for project that does not have the target GetRemoteControlAddIns + { error: { Length: > 0 } err } => throw new InvalidOperationException($"Failed to get add-ins for solution '{solutionFile}' (cf. inner exception for details).", new Exception(err)), + var result => GetConfigurationValue(result.output, "RemoteControlAddIns") + ?.Split(['\r', '\n', ';', ','], StringSplitOptions.RemoveEmptyEntries) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToImmutableList() ?? ImmutableList.Empty, + }; + + private static string? GetConfigurationValue(string msbuildResult, string nodeName) + => Regex.Match(msbuildResult, $"<{nodeName}>(?.*)") is { Success: true } match + ? match.Groups["value"].Value + : null; +} diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/AddInsExtensions.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/AddInsExtensions.cs new file mode 100644 index 000000000000..461340b9480e --- /dev/null +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/AddInsExtensions.cs @@ -0,0 +1,17 @@ +using System; +using System.Linq; +using Microsoft.AspNetCore.Hosting; +using Uno.Extensions.DependencyInjection; +using Uno.UI.RemoteControl.Helpers; + +namespace Uno.UI.RemoteControl.Host.Extensibility; + +public static class AddInsExtensions +{ + public static IWebHostBuilder ConfigureAddIns(this IWebHostBuilder builder, string solutionFile) + { + AssemblyHelper.Load(AddIns.Discover(solutionFile), throwIfLoadFailed: true); + + return builder.ConfigureServices(svc => svc.AddFromAttribute()); + } +} diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs new file mode 100644 index 000000000000..42438b49fecd --- /dev/null +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs @@ -0,0 +1,101 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Uno.Extensions.DependencyInjection; + +internal static class AttributeDataExtensions +{ + public static TAttribute? TryCreate(this CustomAttributeData data) + => (TAttribute?)TryCreate(data, typeof(TAttribute)); + + public static object? TryCreate(this CustomAttributeData data, Type attribute) + { + if (!data.AttributeType.FullName?.Equals(attribute.FullName, StringComparison.Ordinal) ?? false) + { + return null; + } + + var instance = default(object); + foreach (var ctor in attribute.GetConstructors()) + { + var parameters = ctor.GetParameters(); + var arguments = data.ConstructorArguments; + if (arguments.Count > parameters.Length + || arguments.Count < parameters.Count(p => !p.IsOptional)) + { + continue; + } + + var argumentsCompatible = true; + var args = new object?[parameters.Length]; + for (var i = 0; argumentsCompatible && i < arguments.Count; i++) + { + argumentsCompatible &= parameters[i].ParameterType == arguments[i].ArgumentType; + args[i] = arguments[i].Value; + } + + if (!argumentsCompatible) + { + continue; + } + + try + { + instance = ctor.Invoke(args); + break; + } + catch { } + } + + if (instance is null) + { + return null; + } + + try + { + var properties = attribute + .GetProperties() + .Where(prop => prop.CanWrite) + .ToDictionary(prop => prop.Name, StringComparer.Ordinal); + var fields = attribute + .GetFields() + .Where(field => !field.IsInitOnly) + .ToDictionary(field => field.Name, StringComparer.Ordinal); + foreach (var member in data.NamedArguments) + { + if (member.IsField) + { + if (fields.TryGetValue(member.MemberName, out var field) + && field.FieldType.IsAssignableFrom(member.TypedValue.ArgumentType)) + { + field.SetValue(instance, member.TypedValue.Value); + } + else + { + return null; + } + } + else + { + if (properties.TryGetValue(member.MemberName, out var prop) + && prop.PropertyType.IsAssignableFrom(member.TypedValue.ArgumentType)) + { + prop.SetValue(instance, member.TypedValue.Value); + } + else + { + return null; + } + } + } + + return instance; + } + catch + { + return null; + } + } +} diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs new file mode 100644 index 000000000000..83ad1d6ea2a4 --- /dev/null +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs @@ -0,0 +1,22 @@ +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; + +namespace Uno.Extensions.DependencyInjection; + +[AttributeUsage(AttributeTargets.Assembly)] +public class ServiceAttribute(Type contract, Type implementation) : Attribute +{ + public ServiceAttribute(Type implementation) + : this(implementation, implementation) + { + } + + public Type Contract { get; } = contract; + + public Type Implementation { get; } = implementation; + + public ServiceLifetime LifeTime { get; set; } = ServiceLifetime.Singleton; + + public bool IsAutoInit { get; set; } +} diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs new file mode 100644 index 000000000000..5dd32185dc12 --- /dev/null +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Uno.UI.RemoteControl.Host.Extensibility; + +namespace Uno.Extensions.DependencyInjection; + +public static class ServiceCollectionServiceExtensions +{ + public static IServiceCollection AddFromAttribute(this IServiceCollection svc) + { + var attribute = typeof(ServiceAttribute); + var services = AppDomain + .CurrentDomain + .GetAssemblies() + .SelectMany(assembly => assembly.GetCustomAttributesData()) + .Select(attrData => attrData.TryCreate(attribute) as ServiceAttribute) + .Where(attr => attr is not null) + .ToImmutableList(); + + foreach (var service in services) + { + svc.Add(new ServiceDescriptor(service!.Contract, service.Implementation, service.LifeTime)); + } + svc.AddHostedService(s => new AutoInitService(s, services!)); + + return svc; + } + + private class AutoInitService(IServiceProvider services, IImmutableList types) : BackgroundService, IHostedService + { + /// + protected override Task ExecuteAsync(CancellationToken stoppingToken) + { + foreach (var attr in types.Where(attr => attr.IsAutoInit)) + { + try + { + var svc = services.GetService(attr.Contract); + + if (this.Log().IsEnabled(LogLevel.Information)) + { + this.Log().Log(LogLevel.Information, $"Successfully created an instance of {attr.Contract} (impl: {svc?.GetType()})"); + } + } + catch (Exception error) + { + if (this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().Log(LogLevel.Error, error, $"Failed to create an instance of {attr.Contract}."); + } + } + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Uno.UI.RemoteControl.Host/Helpers/AssemblyHelper.cs b/src/Uno.UI.RemoteControl.Host/Helpers/AssemblyHelper.cs new file mode 100644 index 000000000000..be3d40bf1acd --- /dev/null +++ b/src/Uno.UI.RemoteControl.Host/Helpers/AssemblyHelper.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.Logging; +using Uno.Extensions; + +namespace Uno.UI.RemoteControl.Helpers; + +public class AssemblyHelper +{ + private static readonly ILogger _log = typeof(AssemblyHelper).Log(); + + public static IImmutableList Load(IImmutableList dllFiles, bool throwIfLoadFailed = false) + { + var assemblies = ImmutableList.CreateBuilder(); + foreach (var dll in dllFiles.Distinct(StringComparer.OrdinalIgnoreCase)) + { + try + { + assemblies.Add(Assembly.LoadFrom(dll)); + } + catch (Exception err) + { + _log.Log(LogLevel.Error, $"Failed to load assembly '{dll}'.", err); + + if (throwIfLoadFailed) + { + throw; + } + } + } + + return assemblies.ToImmutable(); + } +} diff --git a/src/Uno.UI.RemoteControl.Host/Program.cs b/src/Uno.UI.RemoteControl.Host/Program.cs index eed62ebe6890..73cceab1804a 100644 --- a/src/Uno.UI.RemoteControl.Host/Program.cs +++ b/src/Uno.UI.RemoteControl.Host/Program.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.ComponentModel; using System.Threading.Tasks; +using Uno.UI.RemoteControl.Host.Extensibility; using Uno.UI.RemoteControl.Host.IdeChannel; namespace Uno.UI.RemoteControl.Host @@ -19,8 +20,10 @@ static async Task Main(string[] args) { var httpPort = 0; var parentPID = 0; + var solution = default(string); - var p = new OptionSet() { + var p = new OptionSet + { { "httpPort=", s => { if(!int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out httpPort)) @@ -36,6 +39,17 @@ static async Task Main(string[] args) throw new ArgumentException($"The parent process id parameter is invalid {s}"); } } + }, + { + "solution=", s => + { + if (string.IsNullOrWhiteSpace(s) || !File.Exists(s)) + { + throw new ArgumentException($"The provided solution path '{s}' does not exists"); + } + + solution = s; + } } }; @@ -66,6 +80,12 @@ static async Task Main(string[] args) services.AddSingleton(); }); + if (solution is not null) + { + // For backward compatibility, we allow to not have a solution file specified. + builder.ConfigureAddIns(solution); + } + var host = builder.Build(); host.Services.GetService(); diff --git a/src/Uno.UI.RemoteControl.Server.Processors/Helpers/ProcessHelper.cs b/src/Uno.UI.RemoteControl.Server.Processors/Helpers/ProcessHelper.cs index 12829378af97..40ff4c45d8c0 100644 --- a/src/Uno.UI.RemoteControl.Server.Processors/Helpers/ProcessHelper.cs +++ b/src/Uno.UI.RemoteControl.Server.Processors/Helpers/ProcessHelper.cs @@ -7,7 +7,7 @@ using System.Runtime.InteropServices; using System.Text; -namespace Uno.UI.RemoteControl.Server.Processors.Helpers +namespace Uno.UI.RemoteControl.Helpers { internal class ProcessHelper { @@ -24,6 +24,8 @@ public static (int exitCode, string output, string error) RunProcess(string exec StartInfo = { UseShellExecute = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, RedirectStandardOutput = true, RedirectStandardError = true, FileName = executable, diff --git a/src/Uno.UI.RemoteControl.Server.Processors/HotReload/MetadataUpdates/CompilationWorkspaceProvider.cs b/src/Uno.UI.RemoteControl.Server.Processors/HotReload/MetadataUpdates/CompilationWorkspaceProvider.cs index 96703f88b4bd..67b25cace569 100644 --- a/src/Uno.UI.RemoteControl.Server.Processors/HotReload/MetadataUpdates/CompilationWorkspaceProvider.cs +++ b/src/Uno.UI.RemoteControl.Server.Processors/HotReload/MetadataUpdates/CompilationWorkspaceProvider.cs @@ -7,7 +7,7 @@ using System.IO; using System.Reflection; using Uno.Extensions; -using Uno.UI.RemoteControl.Server.Processors.Helpers; +using Uno.UI.RemoteControl.Helpers; using System.Collections.Generic; using Microsoft.Extensions.Logging; diff --git a/src/Uno.UI.RemoteControl.VS/EntryPoint.cs b/src/Uno.UI.RemoteControl.VS/EntryPoint.cs index 7ffec4817251..546b6781c8a4 100644 --- a/src/Uno.UI.RemoteControl.VS/EntryPoint.cs +++ b/src/Uno.UI.RemoteControl.VS/EntryPoint.cs @@ -313,7 +313,7 @@ private async Task EnsureServerAsync() var pipeGuid = Guid.NewGuid(); var hostBinPath = Path.Combine(_toolsPath, "host", $"net{version}.0", "Uno.UI.RemoteControl.Host.dll"); - var arguments = $"\"{hostBinPath}\" --httpPort {_remoteControlServerPort} --ppid {System.Diagnostics.Process.GetCurrentProcess().Id} --ideChannel \"{pipeGuid}\""; + var arguments = $"\"{hostBinPath}\" --httpPort {_remoteControlServerPort} --ppid {System.Diagnostics.Process.GetCurrentProcess().Id} --ideChannel \"{pipeGuid}\" --solution \"{_dte.Solution.FullName}\""; var pi = new ProcessStartInfo("dotnet", arguments) { UseShellExecute = false, From d73066ddf0741661e4e34970ded8c4303dd631f3 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Oct 2024 18:10:54 -0400 Subject: [PATCH 2/3] feat: Add discovery of dev-server add-ins --- src/Uno.Sdk/targets/Uno.Build.targets | 8 +++ .../Extensibility/AddIns.cs | 71 ++++++++++++++++--- .../AttributeDataExtensions.cs | 6 +- .../ServiceAttribute.cs | 21 ++++++ .../ServiceCollectionServiceExtensions.cs | 10 ++- .../Uno.UI.RemoteControl.Host.csproj | 1 + src/Uno.UI.RemoteControl.VS/EntryPoint.cs | 14 ++-- .../Uno.WinUI.DevServer.targets | 4 ++ 8 files changed, 113 insertions(+), 22 deletions(-) diff --git a/src/Uno.Sdk/targets/Uno.Build.targets b/src/Uno.Sdk/targets/Uno.Build.targets index f3d5e34b4cea..4d7bdf5629dd 100644 --- a/src/Uno.Sdk/targets/Uno.Build.targets +++ b/src/Uno.Sdk/targets/Uno.Build.targets @@ -141,6 +141,14 @@ + + + + + diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs index 38cfab4f27c8..0c80c868ede0 100644 --- a/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs @@ -1,26 +1,75 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text.RegularExpressions; +using Microsoft.Extensions.Logging; +using Uno.Extensions; using Uno.UI.RemoteControl.Helpers; namespace Uno.UI.RemoteControl.Host.Extensibility; public class AddIns { + private static ILogger _log = typeof(AddIns).Log(); + public static IImmutableList Discover(string solutionFile) - => ProcessHelper.RunProcess("dotnet", $"build \"{solutionFile}\" /t:GetRemoteControlAddIns /nowarn:MSB4057") switch // Ignore missing target + { + // Note: With .net 9 we need to specify --verbosity detailed to get messages with High importance. + var result = ProcessHelper.RunProcess("dotnet", $"build \"{solutionFile}\" --target:UnoDumpTargetFrameworks --verbosity detailed"); + var targetFrameworks = GetConfigurationValue(result.output ?? "", "TargetFrameworks") + .SelectMany(tfms => tfms.Split(['\r', '\n', ';', ','], StringSplitOptions.RemoveEmptyEntries)) + .Select(tfm => tfm.Trim()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToImmutableList(); + + if (targetFrameworks.IsEmpty) + { + if (_log.IsEnabled(LogLevel.Warning)) + { + _log.Log(LogLevel.Warning, new Exception(result.error), $"Failed to get target frameworks of solution '{solutionFile}' (cf. inner exception for details)."); + } + + return new ImmutableArray(); + } + + + foreach (var targetFramework in targetFrameworks) { - // Note: We ignore the exitCode not being 0: even if flagged as nowarn, we can still get MSB4057 for project that does not have the target GetRemoteControlAddIns - { error: { Length: > 0 } err } => throw new InvalidOperationException($"Failed to get add-ins for solution '{solutionFile}' (cf. inner exception for details).", new Exception(err)), - var result => GetConfigurationValue(result.output, "RemoteControlAddIns") - ?.Split(['\r', '\n', ';', ','], StringSplitOptions.RemoveEmptyEntries) + result = ProcessHelper.RunProcess("dotnet", $"build \"{solutionFile}\" --target:UnoDumpRemoteControlAddIns --verbosity detailed --framework \"{targetFramework}\" -nowarn:MSB4057"); + if (!string.IsNullOrWhiteSpace(result.error)) + { + if (_log.IsEnabled(LogLevel.Warning)) + { + _log.Log(LogLevel.Warning, new Exception(result.error), $"Failed to get add-ins for solution '{solutionFile}' for tfm {targetFramework} (cf. inner exception for details)."); + } + + continue; + } + + var addIns = GetConfigurationValue(result.output, "RemoteControlAddIns") + .SelectMany(tfms => tfms.Split(['\r', '\n', ';', ','], StringSplitOptions.RemoveEmptyEntries)) + .Select(tfm => tfm.Trim()) .Distinct(StringComparer.OrdinalIgnoreCase) - .ToImmutableList() ?? ImmutableList.Empty, - }; + .ToImmutableList(); + + if (!addIns.IsEmpty) + { + return addIns; + } + } + + if (_log.IsEnabled(LogLevel.Information)) + { + _log.Log(LogLevel.Information, $"Didn't find any add-ins for solution '{solutionFile}'."); + } + + return ImmutableList.Empty; + } - private static string? GetConfigurationValue(string msbuildResult, string nodeName) - => Regex.Match(msbuildResult, $"<{nodeName}>(?.*)") is { Success: true } match - ? match.Groups["value"].Value - : null; + private static IEnumerable GetConfigurationValue(string msbuildResult, string nodeName) + => Regex + .Matches(msbuildResult, $"<{nodeName}>(?.*)") + .Where(match => match.Success) + .Select(match => match.Groups["value"].Value); } diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs index 42438b49fecd..ccf139ef3fa2 100644 --- a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs @@ -11,7 +11,7 @@ internal static class AttributeDataExtensions public static object? TryCreate(this CustomAttributeData data, Type attribute) { - if (!data.AttributeType.FullName?.Equals(attribute.FullName, StringComparison.Ordinal) ?? false) + if ((!data.AttributeType.FullName?.Equals(attribute.FullName, StringComparison.Ordinal)) ?? true) { return null; } @@ -45,12 +45,12 @@ internal static class AttributeDataExtensions instance = ctor.Invoke(args); break; } - catch { } + catch { /* Nothing to do, lets try another constructor */ } } if (instance is null) { - return null; + return null; // Failed to find a valid constructor. } try diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs index 83ad1d6ea2a4..bddb48a9b226 100644 --- a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs @@ -4,19 +4,40 @@ namespace Uno.Extensions.DependencyInjection; +/// +/// Attribute to define a service registration in the service collection. +/// +/// Type of the contract (i.e. interface) implemented by the concrete type. +/// Concrete type to register in the service collection. [AttributeUsage(AttributeTargets.Assembly)] public class ServiceAttribute(Type contract, Type implementation) : Attribute { + /// + /// Creates a new instance of the class with only a concrete type (used as contract and implementation). + /// + /// Concrete type to register in the service collection. public ServiceAttribute(Type implementation) : this(implementation, implementation) { } + /// + /// Type of the contract (i.e. interface) implemented by the concrete type. + /// public Type Contract { get; } = contract; + /// + /// Concrete type to register in the service collection. + /// public Type Implementation { get; } = implementation; + /// + /// The lifetime of the service. + /// public ServiceLifetime LifeTime { get; set; } = ServiceLifetime.Singleton; + /// + /// Indicates if the service should be automatically initialized at startup. + /// public bool IsAutoInit { get; set; } } diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs index 5dd32185dc12..56b9b5bc04fa 100644 --- a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs @@ -6,12 +6,16 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Uno.UI.RemoteControl.Host.Extensibility; namespace Uno.Extensions.DependencyInjection; public static class ServiceCollectionServiceExtensions { + /// + /// Register services configured with the attribute from all loaded assemblies. + /// + /// The service collection on which services should be registered. + /// The service collection for fluent usage. public static IServiceCollection AddFromAttribute(this IServiceCollection svc) { var attribute = typeof(ServiceAttribute); @@ -45,14 +49,14 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken) if (this.Log().IsEnabled(LogLevel.Information)) { - this.Log().Log(LogLevel.Information, $"Successfully created an instance of {attr.Contract} (impl: {svc?.GetType()})"); + this.Log().Log(LogLevel.Information, $"Successfully created an instance of {attr.Contract} for auto-init (impl: {svc?.GetType()})"); } } catch (Exception error) { if (this.Log().IsEnabled(LogLevel.Error)) { - this.Log().Log(LogLevel.Error, error, $"Failed to create an instance of {attr.Contract}."); + this.Log().Log(LogLevel.Error, error, $"Failed to create an instance of {attr.Contract} for auto-init."); } } } diff --git a/src/Uno.UI.RemoteControl.Host/Uno.UI.RemoteControl.Host.csproj b/src/Uno.UI.RemoteControl.Host/Uno.UI.RemoteControl.Host.csproj index 7c8812c8d2a9..98062995f183 100644 --- a/src/Uno.UI.RemoteControl.Host/Uno.UI.RemoteControl.Host.csproj +++ b/src/Uno.UI.RemoteControl.Host/Uno.UI.RemoteControl.Host.csproj @@ -31,6 +31,7 @@ + diff --git a/src/Uno.UI.RemoteControl.VS/EntryPoint.cs b/src/Uno.UI.RemoteControl.VS/EntryPoint.cs index 546b6781c8a4..104bf01b1c7a 100644 --- a/src/Uno.UI.RemoteControl.VS/EntryPoint.cs +++ b/src/Uno.UI.RemoteControl.VS/EntryPoint.cs @@ -16,6 +16,7 @@ using Microsoft.Build.Evaluation; using Microsoft.Internal.VisualStudio.Shell; using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; using Microsoft.VisualStudio.Shell.Interop; using Uno.UI.RemoteControl.Messaging.IdeChannel; using Uno.UI.RemoteControl.VS.DebuggerHelper; @@ -348,17 +349,20 @@ private async Task EnsureServerAsync() // Set the port to the projects // This LEGACY as port should be set through the global properties (cf. OnProvideGlobalPropertiesAsync) var portString = _remoteControlServerPort.ToString(CultureInfo.InvariantCulture); - foreach (var p in await _dte.GetProjectsAsync()) + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var projects = (await _dte.GetProjectsAsync()).ToArray(); // EnumerateProjects must be called on the UI thread. + await TaskScheduler.Default; + foreach (var project in projects) { var filename = string.Empty; try { - filename = p.FileName; + filename = project.FileName; } catch (Exception ex) { - _debugAction?.Invoke($"Exception on retrieving {p.UniqueName} details. Err: {ex}."); - _warningAction?.Invoke($"Cannot read {p.UniqueName} project details (It may be unloaded)."); + _debugAction?.Invoke($"Exception on retrieving {project.UniqueName} details. Err: {ex}."); + _warningAction?.Invoke($"Cannot read {project.UniqueName} project details (It may be unloaded)."); } if (string.IsNullOrWhiteSpace(filename) == false && GetMsbuildProject(filename) is Microsoft.Build.Evaluation.Project msbProject @@ -478,7 +482,7 @@ public void SetGlobalProperty(string projectFullName, string propertyName, strin } } - private static Microsoft.Build.Evaluation.Project GetMsbuildProject(string projectFullName) + private static Microsoft.Build.Evaluation.Project? GetMsbuildProject(string projectFullName) => ProjectCollection.GlobalProjectCollection.GetLoadedProjects(projectFullName).FirstOrDefault(); public void SetGlobalProperties(string projectFullName, IDictionary properties) diff --git a/src/Uno.UI.RemoteControl/buildTransitive/Uno.WinUI.DevServer.targets b/src/Uno.UI.RemoteControl/buildTransitive/Uno.WinUI.DevServer.targets index aa05b2b2aaa8..5a315ec583f6 100644 --- a/src/Uno.UI.RemoteControl/buildTransitive/Uno.WinUI.DevServer.targets +++ b/src/Uno.UI.RemoteControl/buildTransitive/Uno.WinUI.DevServer.targets @@ -31,6 +31,10 @@ + + + + From 8904cbdef84e6743e0fc443af3a59ec61d273229 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 3 Oct 2024 12:58:04 -0400 Subject: [PATCH 3/3] fix: Fix default immutable array (+ docs + namespace update) --- .../Extensibility/AddIns.cs | 6 +++--- .../Extensibility/AddInsExtensions.cs | 2 +- .../AttributeDataExtensions.cs | 15 ++++++++++++++- .../ServiceAttribute.cs | 2 +- .../ServiceCollectionServiceExtensions.cs | 3 ++- 5 files changed, 21 insertions(+), 7 deletions(-) rename src/Uno.UI.RemoteControl.Host/Extensibility/{Uno.Extensions.DependencyInjection => Uno.Utils.DependencyInjection}/AttributeDataExtensions.cs (65%) rename src/Uno.UI.RemoteControl.Host/Extensibility/{Uno.Extensions.DependencyInjection => Uno.Utils.DependencyInjection}/ServiceAttribute.cs (97%) rename src/Uno.UI.RemoteControl.Host/Extensibility/{Uno.Extensions.DependencyInjection => Uno.Utils.DependencyInjection}/ServiceCollectionServiceExtensions.cs (97%) diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs index 0c80c868ede0..9d170b757ef2 100644 --- a/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/AddIns.cs @@ -11,7 +11,7 @@ namespace Uno.UI.RemoteControl.Host.Extensibility; public class AddIns { - private static ILogger _log = typeof(AddIns).Log(); + private static readonly ILogger _log = typeof(AddIns).Log(); public static IImmutableList Discover(string solutionFile) { @@ -30,7 +30,7 @@ public static IImmutableList Discover(string solutionFile) _log.Log(LogLevel.Warning, new Exception(result.error), $"Failed to get target frameworks of solution '{solutionFile}' (cf. inner exception for details)."); } - return new ImmutableArray(); + return ImmutableArray.Empty; } @@ -64,7 +64,7 @@ public static IImmutableList Discover(string solutionFile) _log.Log(LogLevel.Information, $"Didn't find any add-ins for solution '{solutionFile}'."); } - return ImmutableList.Empty; + return ImmutableArray.Empty; } private static IEnumerable GetConfigurationValue(string msbuildResult, string nodeName) diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/AddInsExtensions.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/AddInsExtensions.cs index 461340b9480e..80491025e1b3 100644 --- a/src/Uno.UI.RemoteControl.Host/Extensibility/AddInsExtensions.cs +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/AddInsExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using Microsoft.AspNetCore.Hosting; -using Uno.Extensions.DependencyInjection; +using Uno.Utils.DependencyInjection; using Uno.UI.RemoteControl.Helpers; namespace Uno.UI.RemoteControl.Host.Extensibility; diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Utils.DependencyInjection/AttributeDataExtensions.cs similarity index 65% rename from src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs rename to src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Utils.DependencyInjection/AttributeDataExtensions.cs index ccf139ef3fa2..b6c737f40eca 100644 --- a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/AttributeDataExtensions.cs +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Utils.DependencyInjection/AttributeDataExtensions.cs @@ -2,13 +2,26 @@ using System.Linq; using System.Reflection; -namespace Uno.Extensions.DependencyInjection; +namespace Uno.Utils.DependencyInjection; internal static class AttributeDataExtensions { + /// + /// Attempts to create an instance of the specified type from the provided . + /// + /// This offers the ability to a project to implements their own compatible version of the given type to reduce dependencies. + /// Data of an attribute. + /// An instance of if the provided was compatible, `null` otherwise. public static TAttribute? TryCreate(this CustomAttributeData data) => (TAttribute?)TryCreate(data, typeof(TAttribute)); + /// + /// Attempts to create an instance of the specified type from the provided . + /// + /// This offers the ability to a project to implements their own compatible version of the given type to reduce dependencies. + /// Data of an attribute. + /// Type of the attribute to try to instantiate. + /// An instance of if the provided was compatible, `null` otherwise. public static object? TryCreate(this CustomAttributeData data, Type attribute) { if ((!data.AttributeType.FullName?.Equals(attribute.FullName, StringComparison.Ordinal)) ?? true) diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Utils.DependencyInjection/ServiceAttribute.cs similarity index 97% rename from src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs rename to src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Utils.DependencyInjection/ServiceAttribute.cs index bddb48a9b226..7c99d5d2f9c8 100644 --- a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceAttribute.cs +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Utils.DependencyInjection/ServiceAttribute.cs @@ -2,7 +2,7 @@ using System.Linq; using Microsoft.Extensions.DependencyInjection; -namespace Uno.Extensions.DependencyInjection; +namespace Uno.Utils.DependencyInjection; /// /// Attribute to define a service registration in the service collection. diff --git a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Utils.DependencyInjection/ServiceCollectionServiceExtensions.cs similarity index 97% rename from src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs rename to src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Utils.DependencyInjection/ServiceCollectionServiceExtensions.cs index 56b9b5bc04fa..f0b44f9b539e 100644 --- a/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Extensions.DependencyInjection/ServiceCollectionServiceExtensions.cs +++ b/src/Uno.UI.RemoteControl.Host/Extensibility/Uno.Utils.DependencyInjection/ServiceCollectionServiceExtensions.cs @@ -6,8 +6,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Uno.Extensions; -namespace Uno.Extensions.DependencyInjection; +namespace Uno.Utils.DependencyInjection; public static class ServiceCollectionServiceExtensions {