diff --git a/src/Plugins/SourceGenerator.Foundations.Windows/Interop/VisualStudio/VisualStudioInterop.cs b/src/Plugins/SourceGenerator.Foundations.Windows/Interop/VisualStudio/VisualStudioInterop.cs index 413d2d0..1f79cc3 100644 --- a/src/Plugins/SourceGenerator.Foundations.Windows/Interop/VisualStudio/VisualStudioInterop.cs +++ b/src/Plugins/SourceGenerator.Foundations.Windows/Interop/VisualStudio/VisualStudioInterop.cs @@ -87,11 +87,11 @@ static VisualStudioInterop() /// /// Attaches Visual Studio debugger to the current active process /// - public static void AttachDebugger() + public static bool AttachDebugger() { if(System.Diagnostics.Debugger.IsAttached) { - return; + return true; } DProcess currentProcess = DProcess.GetCurrentProcess(); @@ -100,9 +100,10 @@ public static void AttachDebugger() DteProcess? dteProcess = GetProcess(currentProcessId); if (dteProcess == null) { - throw new Exception("Unable to find DTE local process to attach too"); + return false; } dteProcess.Attach(); + return true; } /// diff --git a/src/Plugins/SourceGenerator.Foundations.Windows/WindowsDevelopmentEnvironment.cs b/src/Plugins/SourceGenerator.Foundations.Windows/WindowsDevelopmentEnvironment.cs index 56423b2..b828d02 100644 --- a/src/Plugins/SourceGenerator.Foundations.Windows/WindowsDevelopmentEnvironment.cs +++ b/src/Plugins/SourceGenerator.Foundations.Windows/WindowsDevelopmentEnvironment.cs @@ -31,8 +31,7 @@ public bool AttachDebugger(int processId) switch (Type) { case EnvironmentType.VisualStudio: - VisualStudioInterop.AttachDebugger(); - break; + return VisualStudioInterop.AttachDebugger(); } return true; } diff --git a/src/Sandbox/ConsoleApp.SourceGenerator/ConsoleAppSourceGenerator.cs b/src/Sandbox/ConsoleApp.SourceGenerator/ConsoleAppSourceGenerator.cs index 44f331f..8686e2c 100644 --- a/src/Sandbox/ConsoleApp.SourceGenerator/ConsoleAppSourceGenerator.cs +++ b/src/Sandbox/ConsoleApp.SourceGenerator/ConsoleAppSourceGenerator.cs @@ -16,10 +16,7 @@ public class Payload } public ConsoleAppSourceGenerator() : base("ConsoleApp") - { - AttachDebugger(); - - } + {} protected override void OnInitialize(SgfInitializationContext context) { diff --git a/src/SourceGenerator.Foundations.Contracts/DevelopmentEnviroment.cs b/src/SourceGenerator.Foundations.Contracts/DevelopmentEnviroment.cs index b96be00..40a7807 100644 --- a/src/SourceGenerator.Foundations.Contracts/DevelopmentEnviroment.cs +++ b/src/SourceGenerator.Foundations.Contracts/DevelopmentEnviroment.cs @@ -30,6 +30,9 @@ public static class DevelopmentEnviroment /// public static ILogger Logger { get; } + public static ILogger CreateLogger() + => Logger; + static DevelopmentEnviroment() { s_sinkAggregate = new LogEventSinkAggregate(); diff --git a/src/SourceGenerator.Foundations.Contracts/IDevelopmentEnviroment.cs b/src/SourceGenerator.Foundations.Contracts/IDevelopmentEnviroment.cs index 52567f9..42036e6 100644 --- a/src/SourceGenerator.Foundations.Contracts/IDevelopmentEnviroment.cs +++ b/src/SourceGenerator.Foundations.Contracts/IDevelopmentEnviroment.cs @@ -9,7 +9,8 @@ namespace SGF public interface IDevelopmentEnviroment { /// - /// Attaches the debugger to the given process Id + /// Attaches the debugger to the given process Id and returns back if it was successful or not. This can + /// fail if Visual Studio is not already running /// bool AttachDebugger(int processId); diff --git a/src/SourceGenerator.Foundations.Contracts/msbuild.binlog b/src/SourceGenerator.Foundations.Contracts/msbuild.binlog new file mode 100644 index 0000000..23f8397 Binary files /dev/null and b/src/SourceGenerator.Foundations.Contracts/msbuild.binlog differ diff --git a/src/SourceGenerator.Foundations/Generators/ScriptInjectorGenerator.cs b/src/SourceGenerator.Foundations/Generators/ScriptInjectorGenerator.cs index ca81316..bcb3612 100644 --- a/src/SourceGenerator.Foundations/Generators/ScriptInjectorGenerator.cs +++ b/src/SourceGenerator.Foundations/Generators/ScriptInjectorGenerator.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.Text; using SGF.Configuration; using System; +using System.Data; using System.IO; using System.Reflection; using System.Text; diff --git a/src/SourceGenerator.Foundations/Reflection/AssemblyResolver.cs b/src/SourceGenerator.Foundations/Reflection/AssemblyResolver.cs index 7805d66..16826f4 100644 --- a/src/SourceGenerator.Foundations/Reflection/AssemblyResolver.cs +++ b/src/SourceGenerator.Foundations/Reflection/AssemblyResolver.cs @@ -1,45 +1,47 @@ #nullable enable using SGF.Configuration; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; namespace SGF.Reflection { internal static class AssemblyResolver { - private enum LogLevel + /// + /// Used to compare two to pull them out of the dictionary of types + /// + private class AssemblyNameComparer : IEqualityComparer { - Info, - Error, - Warning + public bool Equals(AssemblyName x, AssemblyName y) + { + return string.Equals(x.Name, y.Name); + } + + public int GetHashCode(AssemblyName obj) + { + return obj.Name.GetHashCode(); + } } - private static readonly IList s_assemblies; - private static readonly AssemblyName s_contractsAssemblyName; - private static readonly string s_unpackDirectory; + private static readonly ConcurrentBag s_assembliesWithResources; + private static readonly Dictionary s_loadedAssemblies; static AssemblyResolver() { - s_assemblies = new List(); - s_unpackDirectory = Path.Combine(Path.GetTempPath(), "SourceGenerator.Foundations", "Assemblies"); - s_contractsAssemblyName = new AssemblyName(); - if (!Directory.Exists(s_unpackDirectory)) - { - Directory.CreateDirectory(s_unpackDirectory); - } + s_assembliesWithResources = new ConcurrentBag(); + s_loadedAssemblies = new Dictionary(new AssemblyNameComparer()); } - [ModuleInitializer] internal static void Initialize() { // The assembly resolvers get added to multiple source generators // so what we do here is only allow the first one defined to allow // itself to be a resolver. Since this could lead to cases where two resolvers // exists and provide two different instances of the same assembly. - const string RESOLVER_ATTACHED_KEY = "SGF_ASSEMBLY_RESOLVER_IS_ATTACHED"; AppDomain currentDomain = AppDomain.CurrentDomain; object? rawValue = currentDomain.GetData(RESOLVER_ATTACHED_KEY); @@ -47,24 +49,42 @@ internal static void Initialize() if (rawValue == null || (rawValue is bool isAttached && !isAttached)) { currentDomain.SetData(RESOLVER_ATTACHED_KEY, true); - currentDomain.AssemblyResolve += OnResolveAssembly; currentDomain.AssemblyLoad += OnAssemblyLoaded; + currentDomain.AssemblyResolve += ResolveMissingAssembly; foreach (Assembly assembly in currentDomain.GetAssemblies()) { - if (!s_assemblies.Contains(assembly)) - { - s_assemblies.Add(assembly); - } + AddAssembly(assembly); } } } + /// + /// Raised whenever our app domain loads a new assembly + /// + /// THe thing that raised the event + /// The parameters private static void OnAssemblyLoaded(object sender, AssemblyLoadEventArgs args) { - if (!s_assemblies.Contains(args.LoadedAssembly)) + AddAssembly(args.LoadedAssembly); + } + + /// + /// Adds an assembly to the veriuos collections used to keep track of loaded items + /// + private static void AddAssembly(Assembly assembly) + { + AssemblyName assemblyName = assembly.GetName(); + + if (s_loadedAssemblies.ContainsKey(assemblyName)) { - s_assemblies.Add(args.LoadedAssembly); + return; + } + s_loadedAssemblies.Add(assemblyName, assembly); + + if (!assembly.IsDynamic && assembly.GetManifestResourceNames().Any(r => r.StartsWith(ResourceConfiguration.AssemblyResourcePrefix))) + { + s_assembliesWithResources.Add(assembly); } } @@ -72,85 +92,72 @@ private static void OnAssemblyLoaded(object sender, AssemblyLoadEventArgs args) /// Attempts to resolve any assembly by looking for dependencies that are embedded directly /// in this dll. /// - private static Assembly? OnResolveAssembly(object sender, ResolveEventArgs args) + private static Assembly? ResolveMissingAssembly(object sender, ResolveEventArgs args) { AssemblyName assemblyName = new(args.Name); - return ResolveAssembly(assemblyName); - } - private static Assembly? ResolveAssembly(AssemblyName assemblyName) - { - for (int i = 0; i < s_assemblies.Count; i++) + if (s_loadedAssemblies.TryGetValue(assemblyName, out Assembly assembly)) { - Assembly assembly = s_assemblies[i]; - if (AssemblyName.ReferenceMatchesDefinition(assemblyName, assembly.GetName())) - { - return assembly; - } + return assembly; } - string resourceName = $"{ResourceConfiguration.AssemblyResourcePrefix}{assemblyName.Name}.dll"; - - for (int i = 0; i < s_assemblies.Count; i++) + foreach (Assembly loadedAssembly in s_assembliesWithResources) { - Assembly assembly = s_assemblies[i]; - - if (assembly.IsDynamic) + string resourceName = $"{ResourceConfiguration.AssemblyResourcePrefix}{assemblyName.Name}.dll"; + if (TryExtractingAssembly(loadedAssembly, resourceName, out Assembly? extractedAssembly)) { - // Dynamic assemblies don't have reosurces and throw exceptions if you try to access them. - continue; - } - - ManifestResourceInfo resourceInfo = assembly.GetManifestResourceInfo(resourceName); - if (resourceInfo != null) - { - string assemblyPath = Path.Combine(s_unpackDirectory, $"{assemblyName.Name}-{assemblyName.Version}.dll"); - - if (!File.Exists(assemblyPath)) - { - using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) - using (FileStream fileStream = new FileStream(assemblyPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read)) - { - resourceStream.CopyTo(fileStream); - fileStream.Flush(); - } - } - Assembly resolvedAssembly = Assembly.LoadFile(assemblyPath); - s_assemblies.Add(resolvedAssembly); - return resolvedAssembly; - } + AddAssembly(extractedAssembly!); + return extractedAssembly!; + }; } + return null; } + /// - /// Wrapper around the logging implemention to handle the case where loading the contracts library can actually fail + /// Attempts to load an assembly that is contained within aonther assembly as a resource /// - private static void Log(Exception? exception, LogLevel level, string message, params object?[]? parameters) + /// The assembly that should contain the resource + /// The expected name of the reosurce + /// The assembly if it was loaded + /// True if the assembly could be loaded otherwise false + private static bool TryExtractingAssembly(Assembly assembly, string resourceName, out Assembly? loadedAssembly) { - /// - /// This indirection might seem a bit weird but it's because we want to log output from the assembly resolver - /// however since the logging library is defined within `SourceGenerator.Foundations.Contracts` if that assembly - /// fails to load we will create a stake overflow since calling to the logger will try to load the assembly again. - /// We issoloate the logging in this function so the runtime does not attempt to load it directrly - /// - static void LogInternal(Exception? exception, LogLevel level, string message, object?[]? parameters) + loadedAssembly = null; + if (TryGetResourceBytes(assembly, resourceName, out byte[]? assemblyBytes)) { - switch (level) - { - case LogLevel.Info: - DevelopmentEnviroment.Logger.Information(exception, message, parameters); - break; - case LogLevel.Warning: - DevelopmentEnviroment.Logger.Warning(exception, message, parameters); - break; - case LogLevel.Error: - DevelopmentEnviroment.Logger.Error(exception, message, parameters); - break; - } + loadedAssembly = TryGetResourceBytes(assembly, Path.ChangeExtension(resourceName, ".pdb"), out byte[]? symbolBytes) + ? Assembly.Load(assemblyBytes, symbolBytes) + : Assembly.Load(assemblyBytes); + return true; + } + return false; + } + + /// + /// Attempts to read bytes from a resource and returns back if it's successful or not + /// + /// The assembly to pull the resource from + /// The name of the resource + /// The bytes[] if the resource could be found + /// True if the resource was found otherwise false + private static bool TryGetResourceBytes(Assembly assembly, string resourceName, out byte[]? bytes) + { + bytes = null; + ManifestResourceInfo resourceInfo = assembly.GetManifestResourceInfo(resourceName); + if (resourceInfo == null) + { + return false; + } + + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + { + bytes = new byte[stream.Length]; + _ = stream.Read(bytes, 0, bytes.Length); } - LogInternal(exception, LogLevel.Info, message, parameters); + return true; } } } \ No newline at end of file diff --git a/src/SourceGenerator.Foundations/SourceGenerator.Foundations.props b/src/SourceGenerator.Foundations/SourceGenerator.Foundations.props index f82d712..c4bbc0b 100644 --- a/src/SourceGenerator.Foundations/SourceGenerator.Foundations.props +++ b/src/SourceGenerator.Foundations/SourceGenerator.Foundations.props @@ -1,4 +1,4 @@ - + @@ -36,7 +36,7 @@ - +