diff --git a/Agents/Xamarin.Interactive.DotNetCore/DotNetCoreAgent.cs b/Agents/Xamarin.Interactive.DotNetCore/DotNetCoreAgent.cs index 6e768a9ec..e31da1fad 100644 --- a/Agents/Xamarin.Interactive.DotNetCore/DotNetCoreAgent.cs +++ b/Agents/Xamarin.Interactive.DotNetCore/DotNetCoreAgent.cs @@ -50,11 +50,17 @@ sealed class DotNetCoreEvaluationContextManager : EvaluationContextManager "CS1685" }; + readonly NetCoreEvaluationAssemblyContext evaluationAssemblyContext + = new NetCoreEvaluationAssemblyContext (); + internal DotNetCoreEvaluationContextManager (DotNetCoreAgent agent) : base (agent.RepresentationManager, agent) { } + private protected override EvaluationAssemblyContextBase AssemblyContext + => evaluationAssemblyContext; + protected override object CreateGlobalState () => new EvaluationContextGlobalObject ((DotNetCoreAgent)Context); @@ -76,12 +82,7 @@ internal override void LoadExternalDependencies ( foreach (var externalDep in externalDependencies) { try { Log.Debug (TAG, $"Loading external dependency from {externalDep.Location}…"); - if (MacIntegration.IsMac) { - // Don't do anything for now on Mac, nothing we've tried - // so far works. :( - } else { - WindowsSupport.LoadLibrary (externalDep.Location); - } + evaluationAssemblyContext.Add (externalDep); } catch (Exception e) { Log.Error (TAG, "Could not load external dependency.", e); } diff --git a/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs b/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs new file mode 100644 index 000000000..c8f96a679 --- /dev/null +++ b/Agents/Xamarin.Interactive.DotNetCore/NetCoreEvaluationAssemblyContext.cs @@ -0,0 +1,122 @@ +// +// Author: +// Bojan Rajkovic +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Reflection; +using System.IO; +using System.Runtime.Loader; + +using Xamarin.Interactive.CodeAnalysis.Evaluating; +using Xamarin.Interactive.CodeAnalysis.Resolving; +using Xamarin.Interactive.Logging; + +namespace Xamarin.Interactive.DotNetCore +{ + sealed class NetCoreEvaluationAssemblyContext : EvaluationAssemblyContextBase + { + const string TAG = nameof (NetCoreEvaluationAssemblyContext); + + readonly InteractiveAssemblyLoadContext assemblyLoadContext; + + sealed class InteractiveAssemblyLoadContext : AssemblyLoadContext + { + const string TAG = nameof (InteractiveAssemblyLoadContext); + + NetCoreEvaluationAssemblyContext evaluationAssemblyContext; + + public InteractiveAssemblyLoadContext (NetCoreEvaluationAssemblyContext evaluationAssemblyContext) + => this.evaluationAssemblyContext = evaluationAssemblyContext; + + protected override Assembly Load (AssemblyName assemblyName) + { + Log.Info (TAG, $"Requested assembly load for {assemblyName}."); + + if (evaluationAssemblyContext.CompilationAssemblyMap.TryGetValue (assemblyName, out var netAssembly)) + return netAssembly; + + if (evaluationAssemblyContext.ReferencedAssemblyMap.TryGetValue (assemblyName, out var assemblyDefinition)) + return LoadAssemblyFromAssemblyDefinition (assemblyName, assemblyDefinition); + + Log.Warning ( + TAG, + $"Could not load assembly {assemblyName}, it wasn't present in any list of assemblies."); + + return null; + } + + Assembly LoadAssemblyFromAssemblyDefinition (AssemblyName assemblyName, AssemblyDefinition assemblyDefinition) + { + Assembly loadedAsm; + + if (File.Exists (assemblyDefinition.Content.Location)) { + loadedAsm = LoadFromAssemblyPath (assemblyDefinition.Content.Location); + Log.Info (TAG, $"Loaded assembly {loadedAsm} from {assemblyDefinition.Content.Location}."); + } else if (assemblyDefinition.Content.PEImage != null) { + var imageStream = new MemoryStream (assemblyDefinition.Content.PEImage); + if (assemblyDefinition.Content.DebugSymbols != null) { + var symStream = new MemoryStream (assemblyDefinition.Content.DebugSymbols); + loadedAsm = LoadFromStream (imageStream, symStream); + } + loadedAsm = LoadFromStream (imageStream); + Log.Info (TAG, $"Loaded assembly {loadedAsm} from sent PE image."); + } else { + Log.Warning ( + TAG, + $"Could not load assembly {assemblyName}, location {assemblyDefinition.Content.Location} did not " + + "exist and PEImage was not sent."); + return null; + } + + evaluationAssemblyContext.AssemblyResolvedHandler?.Invoke (loadedAsm, assemblyDefinition); + + return loadedAsm; + } + + internal Assembly InternalLoadByName (AssemblyName assemblyName) + => Load (assemblyName); + + protected override IntPtr LoadUnmanagedDll (string unmanagedDllName) + { + Log.Info (TAG, $"Requested unmanaged DLL load for {unmanagedDllName}."); + + if (!evaluationAssemblyContext.ExternalDependencyMap.ContainsKey (unmanagedDllName)) { + Log.Info (TAG, $"Don't know where to load this from, falling back to base."); + return base.LoadUnmanagedDll (unmanagedDllName); + } + + string unmanagedPath = evaluationAssemblyContext.ExternalDependencyMap [unmanagedDllName]; + Log.Info (TAG, $"Loading unmanaged DLL {unmanagedDllName} from {unmanagedPath}."); + + var unmanagedHandle = LoadUnmanagedDllFromPath (unmanagedPath); + Log.Info (TAG, $"Loaded unmanaged DLL {unmanagedDllName} from {unmanagedPath}, handle is {unmanagedHandle}."); + + return unmanagedHandle; + } + + public IntPtr LoadExternalDependency (AssemblyDependency dependency) + => LoadUnmanagedDllFromPath (dependency.Location.FullPath); + } + + public NetCoreEvaluationAssemblyContext () + { + assemblyLoadContext = new InteractiveAssemblyLoadContext (this); + AssemblyLoadContext.Default.Resolving += HandleAssemblyResolve; + } + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + AssemblyLoadContext.Default.Resolving -= HandleAssemblyResolve; + } + + public IntPtr LoadExternalDependency (AssemblyDependency dependency) + => assemblyLoadContext.LoadExternalDependency (dependency); + + Assembly HandleAssemblyResolve (AssemblyLoadContext loadContext, AssemblyName assemblyName) + => assemblyLoadContext.InternalLoadByName (assemblyName); + } +} \ No newline at end of file diff --git a/Agents/Xamarin.Interactive.DotNetCore/Xamarin.Interactive.DotNetCore.csproj b/Agents/Xamarin.Interactive.DotNetCore/Xamarin.Interactive.DotNetCore.csproj index 822379f4c..4f9271c5e 100644 --- a/Agents/Xamarin.Interactive.DotNetCore/Xamarin.Interactive.DotNetCore.csproj +++ b/Agents/Xamarin.Interactive.DotNetCore/Xamarin.Interactive.DotNetCore.csproj @@ -8,4 +8,7 @@ + + + \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs new file mode 100644 index 000000000..0d9d9a994 --- /dev/null +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/AssemblyNameInsensitiveNameOnlyComparer.cs @@ -0,0 +1,63 @@ +// +// Author: +// Aaron Bockover +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Xamarin.Interactive.CodeAnalysis.Evaluating +{ + // Implement a custom AssemblyName comparer so that we don't have to insert + // multiple different varieties of the same assembly name into the dictionary. + // Different pieces of external code seem to look up our submission assemblies in + // different ways: JSON.NET uses bare names (see https://bugzilla.xamarin.com/show_bug.cgi?id=58801), + // most of the framework uses fully qualified assembly names, and ASP.NET Core + // seems to use fully-qualified-except-no-version names. As submission assemblies + // aren't versioned, don't have a culture, and don't have a public key token, treating + // the name in a case insensitive way is fine. + sealed class AssemblyNameInsensitiveNameOnlyComparer : IEqualityComparer, IComparer + { + public static bool Equals (string x, string y) + => string.Equals (x, y, StringComparison.OrdinalIgnoreCase); + + public static bool Equals (AssemblyName x, AssemblyName y) + => Equals (x?.Name, y?.Name); + + public static readonly IComparer Default + = new AssemblyNameInsensitiveNameOnlyComparer (); + + bool IEqualityComparer.Equals (AssemblyName x, AssemblyName y) + => Equals (x?.Name, y?.Name); + + int IEqualityComparer.GetHashCode (AssemblyName obj) + => obj?.Name == null ? 0 : obj.Name.GetHashCode (); + + public int Compare (AssemblyName x, AssemblyName y) + => string.Compare (x?.Name, y?.Name, StringComparison.OrdinalIgnoreCase); + } + + sealed class AssemblyNameFullNameComparer : IEqualityComparer, IComparer + { + public static bool Equals (string x, string y) + => string.Equals (x, y, StringComparison.OrdinalIgnoreCase); + + public static bool Equals (AssemblyName x, AssemblyName y) + => Equals (x?.Name, y?.Name); + + public static readonly IComparer Default + = new AssemblyNameFullNameComparer (); + + bool IEqualityComparer.Equals (AssemblyName x, AssemblyName y) + => Equals (x?.FullName, y?.FullName); + + int IEqualityComparer.GetHashCode (AssemblyName obj) + => obj?.Name == null ? 0 : obj.Name.GetHashCode (); + + public int Compare (AssemblyName x, AssemblyName y) + => string.Compare (x?.FullName, y?.FullName); + } +} \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContext.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContext.cs deleted file mode 100644 index 3f211ab85..000000000 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContext.cs +++ /dev/null @@ -1,156 +0,0 @@ -// -// Author: -// Aaron Bockover -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; - -using Xamarin.Interactive.Logging; - -using Xamarin.Interactive.CodeAnalysis.Resolving; - -namespace Xamarin.Interactive.CodeAnalysis.Evaluating -{ - public sealed class EvaluationAssemblyContext : IDisposable - { - // Implement a custom AssemblyName comparer so that we don't have to insert - // multiple different varieties of the same assembly name into the dictionary. - // Different pieces of external code seem to look up our submission assemblies in - // different ways: JSON.NET uses bare names (see https://bugzilla.xamarin.com/show_bug.cgi?id=58801), - // most of the framework uses fully qualified assembly names, and ASP.NET Core - // seems to use fully-qualified-except-no-version names. As submission assemblies - // aren't versioned, don't have a culture, and don't have a public key token, treating - // the name in a case insensitive way is fine. - sealed class AssemblyNameInsensitiveNameOnlyComparer : IEqualityComparer - { - public static bool Equals (string x, string y) - => string.Equals (x, y, StringComparison.OrdinalIgnoreCase); - - public static bool Equals (AssemblyName x, AssemblyName y) - => Equals (x?.Name, y?.Name); - - public static readonly IEqualityComparer Default - = new AssemblyNameInsensitiveNameOnlyComparer (); - - bool IEqualityComparer.Equals (AssemblyName x, AssemblyName y) - => Equals (x?.Name, y?.Name); - - int IEqualityComparer.GetHashCode (AssemblyName obj) - => obj?.Name == null ? 0 : obj.Name.GetHashCode (); - } - - const string TAG = nameof (EvaluationAssemblyContext); - - readonly Dictionary assemblyMap - = new Dictionary ( - StringComparer.OrdinalIgnoreCase); - - readonly Dictionary netAssemblyMap - = new Dictionary ( - AssemblyNameInsensitiveNameOnlyComparer.Default); - - public Action AssemblyResolvedHandler { get; } - - public EvaluationAssemblyContext (Action assemblyResolvedHandler = null) - { - AssemblyResolvedHandler = assemblyResolvedHandler; - AppDomain.CurrentDomain.AssemblyResolve += HandleAssemblyResolve; - } - - public void Dispose () - { - AppDomain.CurrentDomain.AssemblyResolve -= HandleAssemblyResolve; - GC.SuppressFinalize (this); - } - - Assembly HandleAssemblyResolve (object sender, ResolveEventArgs args) - { - Log.Verbose (TAG, $"Handling assembly resolve event for {args.Name}."); - - Assembly netAssembly; - Log.Verbose (TAG, $"Looking up {args.Name} in compiled assembly map."); - if (netAssemblyMap.TryGetValue (new AssemblyName (args.Name), out netAssembly)) - return netAssembly; - - AssemblyDefinition assembly; - var shortName = new AssemblyName (args.Name).Name; - - Log.Verbose (TAG, $"Looking up {shortName} in assembly definition map."); - if (assemblyMap.TryGetValue (shortName, out assembly)) { - if (args.RequestingAssembly?.ReflectionOnly == true) { - if (File.Exists (assembly.Content.Location)) - return Assembly.ReflectionOnlyLoadFrom (assembly.Content.Location); - - if (assembly.Content.PEImage != null) - return Assembly.ReflectionOnlyLoad (assembly.Content.PEImage); - - Log.Verbose ( - TAG, - $"Could not reflection-only load assembly {args.Name}, location {assembly.Content.Location} " + - "did not exist and PEImage was not sent."); - - return null; - } - - Assembly loadedAsm; - - if (File.Exists (assembly.Content.Location)) - loadedAsm = Assembly.LoadFrom (assembly.Content.Location); - else if (assembly.Content.PEImage != null) { - if (assembly.Content.DebugSymbols != null) - loadedAsm = Assembly.Load ( - assembly.Content.PEImage, - assembly.Content.DebugSymbols); - else - loadedAsm = Assembly.Load (assembly.Content.PEImage); - } else - loadedAsm = null; - - if (loadedAsm == null) - Log.Verbose ( - TAG, - $"Could not load assembly {args.Name}, location {assembly.Content.Location} did not " + - "exist and PEImage was not sent."); - - AssemblyResolvedHandler?.Invoke (loadedAsm, assembly); - return loadedAsm; - } - - Log.Verbose (TAG, $"Could not find {args.Name} in any assembly map."); - - return null; - } - - public void AddRange (IEnumerable assemblies) - { - if (assemblies == null) - throw new ArgumentNullException (nameof (assemblies)); - - foreach (var assembly in assemblies) { - if (assembly != null) - Add (assembly); - } - } - - public void Add (AssemblyDefinition assembly) - { - if (assembly == null) - throw new ArgumentNullException (nameof (assembly)); - - assemblyMap [assembly.Name.Name] = assembly; - } - - public void Add (Assembly assembly) - { - if (assembly == null) - throw new ArgumentNullException (nameof (assembly)); - - netAssemblyMap [assembly.GetName ()] = assembly; - } - } -} \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs new file mode 100644 index 000000000..708b10ac1 --- /dev/null +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationAssemblyContextBase.cs @@ -0,0 +1,79 @@ +// +// Author: +// Aaron Bockover +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +using Xamarin.Interactive.Core; +using Xamarin.Interactive.Logging; + +using Xamarin.Interactive.CodeAnalysis.Resolving; + +namespace Xamarin.Interactive.CodeAnalysis.Evaluating +{ + abstract class EvaluationAssemblyContextBase : IDisposable + { + const string TAG = nameof (EvaluationAssemblyContextBase); + + protected IDictionary ReferencedAssemblyMap { get; } + = new SortedDictionary ( + AssemblyNameInsensitiveNameOnlyComparer.Default); + + protected IDictionary CompilationAssemblyMap { get; } + = new SortedDictionary ( + AssemblyNameInsensitiveNameOnlyComparer.Default); + + protected IDictionary ExternalDependencyMap { get; } + = new SortedDictionary ( + StringComparer.OrdinalIgnoreCase); + + public Action AssemblyResolvedHandler { get; set; } + + public EvaluationAssemblyContextBase (Action assemblyResolvedHandler = null) + => AssemblyResolvedHandler = assemblyResolvedHandler; + + public void Dispose () + => Dispose (true); + + protected virtual void Dispose (bool disposing) + => GC.SuppressFinalize (this); + + public void AddRange (IEnumerable assemblies) + { + if (assemblies == null) + throw new ArgumentNullException (nameof (assemblies)); + + foreach (var assembly in assemblies) { + if (assembly != null) + Add (assembly); + } + } + + public void Add (AssemblyDefinition assembly) + { + if (assembly == null) + throw new ArgumentNullException (nameof (assembly)); + + ReferencedAssemblyMap [assembly.Name] = assembly; + } + + public void Add (Assembly assembly) + { + if (assembly == null) + throw new ArgumentNullException (nameof (assembly)); + + CompilationAssemblyMap [assembly.GetName ()] = assembly; + } + + public void Add (AssemblyDependency externalDependency) + { + ExternalDependencyMap [externalDependency.Name] = externalDependency.Location; + } + } +} \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContext.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContext.cs index 60a1df40e..6e28324bd 100644 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContext.cs +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContext.cs @@ -32,16 +32,20 @@ public sealed class EvaluationContext public IObservable Events => events; public EvaluationContextManager Host { get; } - internal EvaluationAssemblyContext AssemblyContext { get; } + internal EvaluationAssemblyContextBase AssemblyContext { get; } internal EvaluationContext ( EvaluationContextManager host, + EvaluationAssemblyContextBase assemblyContext, object globalState) { Host = host ?? throw new ArgumentNullException (nameof (host)); - AssemblyContext = new EvaluationAssemblyContext (HandleAssemblyResolved); + AssemblyContext = assemblyContext + ?? throw new ArgumentNullException (nameof (assemblyContext)); + + AssemblyContext.AssemblyResolvedHandler = HandleAssemblyResolved; submissionStates = new [] { globalState, null }; } diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContextManager.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContextManager.cs index ec00aa4a5..aba4cca9c 100644 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContextManager.cs +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/EvaluationContextManager.cs @@ -69,6 +69,10 @@ readonly Lazy agentSynchronizationContext = new Lazy (); public virtual IAgentSynchronizationContext SynchronizationContexts => agentSynchronizationContext.Value; + readonly Lazy evaluationAssemblyContext + = new Lazy (); + private protected virtual EvaluationAssemblyContextBase AssemblyContext => evaluationAssemblyContext.Value; + readonly IList resetStateHandlers = new List (); public EvaluationContextManager ( @@ -125,6 +129,7 @@ public Task CreateEvaluationContextAsync ( var evaluationContext = new EvaluationContext ( this, + AssemblyContext, globalStateObject); if (globalStateObject is EvaluationContextGlobalObject evaluationContextGlobalObject) diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs new file mode 100644 index 000000000..d915e9e03 --- /dev/null +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Evaluating/NetFxEvaluationAssemblyContext.cs @@ -0,0 +1,88 @@ +// +// Author: +// Aaron Bockover +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Reflection; + +using Xamarin.Interactive.CodeAnalysis.Resolving; +using Xamarin.Interactive.Logging; + +namespace Xamarin.Interactive.CodeAnalysis.Evaluating +{ + sealed class NetFxEvaluationAssemblyContext : EvaluationAssemblyContextBase + { + const string TAG = nameof (NetFxEvaluationAssemblyContext); + + public NetFxEvaluationAssemblyContext () + => AppDomain.CurrentDomain.AssemblyResolve += HandleAssemblyResolve; + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + AppDomain.CurrentDomain.AssemblyResolve -= HandleAssemblyResolve; + } + + Assembly HandleAssemblyResolve (object sender, ResolveEventArgs args) + { + Log.Verbose (TAG, $"Handling assembly resolve event for {args.Name}."); + return LoadAssemblyFromName (args.Name, args.RequestingAssembly); + } + + Assembly LoadAssemblyFromName (string assemblyName, Assembly requestingAssembly) + { + Assembly netAssembly; + if (CompilationAssemblyMap.TryGetValue (new AssemblyName (assemblyName), out netAssembly)) + return netAssembly; + + AssemblyDefinition assembly; + if (ReferencedAssemblyMap.TryGetValue (new AssemblyName (assemblyName), out assembly)) { + if (requestingAssembly?.ReflectionOnly == true) { + if (File.Exists (assembly.Content.Location)) + return Assembly.ReflectionOnlyLoadFrom (assembly.Content.Location); + + if (assembly.Content.PEImage != null) + return Assembly.ReflectionOnlyLoad (assembly.Content.PEImage); + + Log.Warning ( + TAG, + $"Could not reflection-only load assembly {assemblyName}, location {assembly.Content.Location}" + + "did not exist and PEImage was not sent."); + + return null; + } + + Assembly loadedAsm; + + if (File.Exists (assembly.Content.Location)) + loadedAsm = Assembly.LoadFrom (assembly.Content.Location); + else if (assembly.Content.PEImage != null) { + if (assembly.Content.DebugSymbols != null) + loadedAsm = Assembly.Load ( + assembly.Content.PEImage, + assembly.Content.DebugSymbols); + else + loadedAsm = Assembly.Load (assembly.Content.PEImage); + } else + loadedAsm = null; + + if (loadedAsm == null) { + Log.Warning ( + TAG, + $"Could not load assembly {assemblyName}, location {assembly.Content.Location} did not " + + "exist and PEImage was not sent."); + return null; + } + + AssemblyResolvedHandler?.Invoke (loadedAsm, assembly); + return loadedAsm; + } + + return null; + } + } +} \ No newline at end of file diff --git a/Agents/Xamarin.Interactive/CodeAnalysis/Resolving/AssemblyDependency.cs b/Agents/Xamarin.Interactive/CodeAnalysis/Resolving/AssemblyDependency.cs index 74825e32c..55279f88f 100644 --- a/Agents/Xamarin.Interactive/CodeAnalysis/Resolving/AssemblyDependency.cs +++ b/Agents/Xamarin.Interactive/CodeAnalysis/Resolving/AssemblyDependency.cs @@ -12,15 +12,17 @@ namespace Xamarin.Interactive.CodeAnalysis.Resolving [JsonObject] public struct AssemblyDependency { + public string Name { get; } public FilePath Location { get; } public byte [] Data { get; } [JsonConstructor] - internal AssemblyDependency (FilePath location, byte [] data = null) + internal AssemblyDependency (string name, FilePath location, byte [] data = null) { if (location == null) throw new ArgumentNullException (nameof (location)); + Name = name; Location = location; Data = data; } diff --git a/Agents/Xamarin.Interactive/Logging/LogEntry.cs b/Agents/Xamarin.Interactive/Logging/LogEntry.cs index d5b87d30f..734db060c 100644 --- a/Agents/Xamarin.Interactive/Logging/LogEntry.cs +++ b/Agents/Xamarin.Interactive/Logging/LogEntry.cs @@ -7,9 +7,11 @@ using System; +using Newtonsoft.Json; + namespace Xamarin.Interactive.Logging { - [Serializable] + [JsonObject] public struct LogEntry { internal string OwnerId { get; } @@ -23,6 +25,7 @@ public struct LogEntry public string CallerFilePath { get; } public int CallerLineNumber { get; } + [JsonConstructor] internal LogEntry ( string ownerId, DateTime time, diff --git a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/InteractiveDependencyResolver.cs b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/InteractiveDependencyResolver.cs index 46d2cf4cd..c059c0c61 100644 --- a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/InteractiveDependencyResolver.cs +++ b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/InteractiveDependencyResolver.cs @@ -16,15 +16,9 @@ using System.Reflection.Metadata; using System.Reflection.PortableExecutable; -using Microsoft.CodeAnalysis; - -using Xamarin.Interactive.CodeAnalysis; using Xamarin.Interactive.Core; using Xamarin.Interactive.Logging; -using AssemblyIdentity = Xamarin.Interactive.CodeAnalysis.Resolving.AssemblyIdentity; -using AssemblyDefinition = Xamarin.Interactive.CodeAnalysis.Resolving.AssemblyDefinition; - namespace Xamarin.Interactive.CodeAnalysis.Resolving { public class InteractiveDependencyResolver : NativeDependencyResolver @@ -125,11 +119,7 @@ public Task ResolveReferencesAsync (IEnumerable .Select (r => { var syms = includePeImages ? GetDebugSymbolsFromAssemblyPath (r.Path) : null; var peImage = includePeImages ? GetFileBytes (r.Path) : null; - var externalDeps = r.ExternalDependencies - .Select (d => new AssemblyDependency ( - d.Location, - includePeImages ? GetFileBytes (d.Location) : null)) - .ToArray (); + var externalDeps = GetExternalDependencies (r, includePeImages); return new AssemblyDefinition ( r.AssemblyName, r.Path, @@ -149,6 +139,20 @@ public Task ResolveReferencesAsync (IEnumerable }, cancellationToken); } + static AssemblyDependency [] GetExternalDependencies (ResolvedAssembly resolvedAssembly, bool includePeImages) + { + var externalDependencies = new AssemblyDependency [resolvedAssembly.ExternalDependencies.Length]; + for (var i = 0; i < resolvedAssembly.ExternalDependencies.Length; i++) { + var externalDep = resolvedAssembly.ExternalDependencies [i]; + + externalDependencies [i] = new AssemblyDependency ( + (externalDep is NativeDependency nativeDep) ? nativeDep.Name : null, + externalDep.Location, + includePeImages ? GetFileBytes (externalDep.Location) : null); + } + return externalDependencies; + } + public static byte [] GetFileBytes (FilePath path) { try { diff --git a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs index 3efd449d4..c4e77b527 100644 --- a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs +++ b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/Resolving/NativeDependencyResolver.cs @@ -5,17 +5,21 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; +using NuGet.Packaging; +using NuGet.Versioning; + using Xamarin.Interactive.Core; using Xamarin.Interactive.Logging; +using Xamarin.Interactive.NuGet; namespace Xamarin.Interactive.CodeAnalysis.Resolving { @@ -23,6 +27,41 @@ public class NativeDependencyResolver : DependencyResolver { const string TAG = nameof (NativeDependencyResolver); + const string skiaSharpDllMap = @" + + + + + + + "; + + const string urhoSharpDllMap = @" + + + + + + + "; + + const string libuvDllMap = @" + + + + + + "; + + const string msmlDllMap = @" + + + + + + + "; + readonly Architecture agentArchitecture; internal TargetCompilationConfiguration CompilationConfiguration { get; } @@ -31,10 +70,13 @@ internal NativeDependencyResolver (TargetCompilationConfiguration compilationCon { CompilationConfiguration = compilationConfiguration; - agentArchitecture = Environment.Is64BitProcess && - (HostEnvironment.OS != HostOS.Windows || compilationConfiguration.Sdk.IsNot (SdkId.ConsoleNetCore)) - ? Architecture.X64 - : Architecture.X86; + if (compilationConfiguration != null) + // Agents always send an architecture value via the TCC. + agentArchitecture = compilationConfiguration.Runtime.Architecture.Value; + else + // This case is reached when we create a resolver with null configuration from + // GacCache, in which case we can stick with the current process architecture. + agentArchitecture = RuntimeInformation.ProcessArchitecture; } protected override ResolvedAssembly ParseAssembly ( @@ -54,27 +96,42 @@ protected override ResolvedAssembly ParseAssembly ( peReader, metadataReader)); -#if false - - // HACK: Hard-code hacks for SkiaSharp. - if (path.Name == "SkiaSharp.dll") - nativeDependencies.AddRange (GetSkiaSharpDependencies (path)); - // HACK: Hard-code hacks for UrhoSharp. - if (path.Name == "Urho.dll") - nativeDependencies.AddRange (GetUrhoSharpDependencies (path)); - - if (path.Name == "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.dll") - nativeDependencies.AddRange (GetKestrelLibuvDependencies (path)); + var dllMap = new DllMap (CompilationConfiguration.Runtime); + + // Is there a config file next to the assembly? Use that. + // TODO: Check user prefs path or something so that users can write DllMaps + var configPath = path + ".config"; + if (File.Exists (configPath)) + dllMap.Load (configPath); + else { + switch (path.Name) { + case "SkiaSharp.dll": + dllMap.LoadXml (skiaSharpDllMap); + break; + case "UrhoSharp.dll": + dllMap.LoadXml (urhoSharpDllMap); + break; + case "Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.dll": + dllMap.LoadXml (GetKestrelDllMap (path)); + break; + case "Microsoft.ML.CpuMath.dll": + case "Microsoft.ML.FastTree.dll": + dllMap.LoadXml (msmlDllMap); + break; + } + } -#endif + nativeDependencies.AddRange ( + dllMap.Select (mapping => + new NativeDependency ( + mapping.Key.LibraryName, + path.ParentDirectory.Combine(mapping.Value.LibraryName).FullPath))); return resolvedAssembly.WithExternalDependencies (nativeDependencies); } -#if false - - IEnumerable GetKestrelLibuvDependencies (FilePath path) + string GetKestrelDllMap (FilePath path) { var packageDirectoryPath = path.ParentDirectory.ParentDirectory.ParentDirectory; @@ -98,144 +155,9 @@ IEnumerable GetKestrelLibuvDependencies (FilePath path) .FindBestMatch (libuvVersions); var libuvPackagePath = libuvPackagesPath.Combine (libuvDependencyVersion.ToString ()); - - var architecture = agentArchitecture.ToString ().ToLowerInvariant (); - var isMac = false; - - switch (HostEnvironment.OS) { - case HostOS.Windows: - break; - case HostOS.macOS: - isMac = true; - break; - default: - yield break; - } - - string runtimeName, nativeLibName; - switch (AgentType) { - case AgentType.WPF: - // We need the win7- library here. - nativeLibName = "libuv.dll"; - runtimeName = $"win-{architecture}"; - break; - case AgentType.Console: - case AgentType.MacNet45: - case AgentType.MacMobile: - case AgentType.DotNetCore: - nativeLibName = isMac ? "libuv.dylib" : "libuv.dll"; - runtimeName = isMac ? "osx" : $"win-{architecture}"; - break; - default: - yield break; - } - - var nativeLibraryPath = libuvPackagePath.Combine ( - "runtimes", - runtimeName, - "native", - nativeLibName - ); - - if (nativeLibraryPath.FileExists) - yield return new NativeDependency (nativeLibraryPath.Name, nativeLibraryPath); - } - - IEnumerable GetSkiaSharpDependencies (FilePath path) - { - var packageDirectoryPath = path.ParentDirectory.ParentDirectory.ParentDirectory; - - var architecture = agentArchitecture.ToString ().ToLowerInvariant (); - var isMac = false; - - switch (HostEnvironment.OS) { - case HostOS.Windows: - break; - case HostOS.macOS: - isMac = true; - break; - default: - yield break; - } - - string runtimeName, nativeLibName; - switch (AgentType) { - case AgentType.WPF: - // We need the win7- library here. - nativeLibName = "libSkiaSharp.dll"; - runtimeName = $"win7-{architecture}"; - break; - case AgentType.Console: - case AgentType.MacNet45: - case AgentType.MacMobile: - case AgentType.DotNetCore: - nativeLibName = isMac ? "libSkiaSharp.dylib" : "libSkiaSharp.dll"; - runtimeName = isMac ? "osx" : $"win7-{architecture}"; - break; - default: - yield break; - } - - var nativeLibraryPath = packageDirectoryPath.Combine ( - "runtimes", - runtimeName, - "native", - nativeLibName - ); - - if (nativeLibraryPath.FileExists) - yield return new NativeDependency (nativeLibraryPath.Name, nativeLibraryPath); + return string.Format (libuvDllMap, libuvPackagePath); } - IEnumerable GetUrhoSharpDependencies (FilePath path) - { - var packageDirectoryPath = path.ParentDirectory.ParentDirectory.ParentDirectory; - - var architecture = agentArchitecture == Architecture.X64 ? "64" : "32"; - var isMac = false; - - switch (HostEnvironment.OS) { - case HostOS.Windows: - break; - case HostOS.macOS: - isMac = true; - break; - default: - yield break; - } - - var nativeLibName = isMac ? "libmono-urho.dylib" : "mono-urho.dll"; - - string runtimeName; - switch (AgentType) { - case AgentType.WPF: - runtimeName = $"Win{architecture}"; - break; - case AgentType.MacNet45: - case AgentType.MacMobile: - case AgentType.Console: - case AgentType.DotNetCore: - runtimeName = isMac ? "Mac" : $"Win{architecture}"; - break; - case AgentType.Android: - nativeLibName = "libmono-urho.so"; - yield break; - default: - yield break; - } - - var nativeLibraryPath = packageDirectoryPath.Combine ( - "native", - runtimeName, - nativeLibName - ); - - if (nativeLibraryPath.FileExists) - yield return new NativeDependency (nativeLibraryPath.Name, nativeLibraryPath); - } - -#endif - IEnumerable GetEmbeddedFrameworks ( ResolveOperation resolveOperation, FilePath path, diff --git a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/WorkspaceConfiguration.cs b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/WorkspaceConfiguration.cs index 4a462dd89..80ec7552b 100644 --- a/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/WorkspaceConfiguration.cs +++ b/CodeAnalysis/Xamarin.Interactive.CodeAnalysis/CodeAnalysis/WorkspaceConfiguration.cs @@ -152,7 +152,7 @@ static Type ResolveHostObjectType ( return typeof (object); } - using (var assemblyContext = new EvaluationAssemblyContext ()) { + using (var assemblyContext = new NetFxEvaluationAssemblyContext ()) { string globalStateAssemblyCachePath = null; if (configuration.GlobalStateType.Assembly.Content.PEImage != null) globalStateAssemblyCachePath =