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 @@
-
+