From 6356a5a778caf2a705ee67365c1128dba4a8ba46 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 24 Jun 2016 16:25:49 -0700 Subject: [PATCH 01/13] Mimic InitializeDefaultLoadContext --- .../AssemblyInfo.cs | 1 + .../CoreCLR/CorePsAssemblyLoadContext.cs | 241 ++++++++++++------ .../utils/ClrFacade.cs | 24 +- src/powershell/Program.cs | 10 +- 4 files changed, 184 insertions(+), 92 deletions(-) diff --git a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/AssemblyInfo.cs b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/AssemblyInfo.cs index 5b6784900c..a0aa909695 100644 --- a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/AssemblyInfo.cs +++ b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/AssemblyInfo.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; using System.Reflection; [assembly:InternalsVisibleTo("System.Management.Automation,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly:InternalsVisibleTo("powershell,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly:AssemblyFileVersionAttribute("1.0.0.0")] [assembly:AssemblyVersion("1.0.0.0")] diff --git a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs index 418ec2b8a3..cdb88e0a21 100644 --- a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs @@ -10,19 +10,21 @@ using System.Collections.Generic; using System.Globalization; using System.Runtime.InteropServices; using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Runtime.Loader; namespace System.Management.Automation { /// - /// The powershell custom assembly loader implementation + /// The powershell custom AssemblyLoadContext implementation /// - internal partial class PowerShellAssemblyLoader + public partial class PowerShellAssemblyLoadContext : AssemblyLoadContext { #region Resource_Strings // We cannot use a satellite resources.dll to store resource strings for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll. This is because when retrieving resource strings, ResourceManager - // tries to load the satellite resources.dll using a probing approach, which will cause an infinite loop to PowerShellAssemblyLoader.Load(AssemblyName). + // tries to load the satellite resources.dll using a probing approach, which will cause an infinite loop to PowerShellAssemblyLoadContext.Load(AssemblyName). // Take the 'en-US' culture as an example. When retrieving resource string to construct an exception, ResourceManager calls Assembly.Load(..) in the following order to load the resource dll: // 1. Load assembly with culture 'en-US' (Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.resources, Version=3.0.0.0, Culture=en-US, PublicKeyToken=31bf3856ad364e35) // 2. Load assembly with culture 'en' (Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.resources, Version=3.0.0.0, Culture=en, PublicKeyToken=31bf3856ad364e35) @@ -32,11 +34,19 @@ namespace System.Management.Automation private const string ManifestDefinitionDoesNotMatch = "Could not load file or assembly '{0}' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference."; private const string AssemblyPathDoesNotExist = "Could not load file or assembly '{0}' or one of its dependencies. The system cannot find the file specified."; private const string InvalidAssemblyExtensionName = "Could not load file or assembly '{0}' or one of its dependencies. The file specified is not a DLL file."; + private const string AbsolutePathRequired = "Absolute path information is required."; #endregion Resource_Strings #region Constructor + /// + /// This constructor is for testability purpose only + /// + protected PowerShellAssemblyLoadContext() + { + } + /// /// Constructor /// @@ -44,7 +54,7 @@ namespace System.Management.Automation /// Base directory paths that are separated by semicolon ';'. /// They will be the default paths to probe assemblies. /// - internal PowerShellAssemblyLoader(string basePaths) + internal PowerShellAssemblyLoadContext(string basePaths) { #region Validation this.basePaths = basePaths.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); @@ -70,18 +80,12 @@ namespace System.Management.Automation // - Key: namespace qualified type name (FullName) // - Value: strong name of the TPA that contains the type represented by Key. coreClrTypeCatalog = InitializeTypeCatalog(); - - this.loadContext = AssemblyLoadContext.Default; - loadContext.Resolving += Resolve; } #endregion Constructor #region Fields - // AssemblyLoadContext used by this loader - private readonly AssemblyLoadContext loadContext; - // Serialized type catalog file private readonly object syncObj = new object(); private readonly string[] basePaths; @@ -114,18 +118,22 @@ namespace System.Management.Automation #endregion Fields + #region Events + + /// + /// Assembly load event + /// + internal event Action AssemblyLoad; + + #endregion Events + #region Protected_Internal_Methods /// - /// The global instance of PowerShellAssemblyLoader - /// - internal static PowerShellAssemblyLoader Instance { get; set; } - - /// - /// Implement the AssemblyLoadContext.Resolving event handler. Search the requested assembly in probing paths. + /// Implement the AssemblyLoadContext.Load(AssemblyName). Search the requested assembly in probing paths. /// Search the file "[assemblyName.Name][.ni].dll" in probing paths. If the file is found and it matches the requested AssemblyName, load it with LoadFromAssemblyPath. /// - internal Assembly Resolve(AssemblyLoadContext sender, AssemblyName assemblyName) + protected override Assembly Load(AssemblyName assemblyName) { // Probe the assembly cache Assembly asmLoaded; @@ -157,7 +165,7 @@ namespace System.Management.Automation if (File.Exists(asmFilePath)) { isAssemblyFileFound = true; - AssemblyName asmNameFound = AssemblyLoadContext.GetAssemblyName(asmFilePath); + AssemblyName asmNameFound = GetAssemblyName(asmFilePath); if (IsAssemblyMatching(assemblyName, asmNameFound)) { isAssemblyFileMatching = true; @@ -191,8 +199,8 @@ namespace System.Management.Automation try { asmLoaded = asmFilePath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) - ? loadContext.LoadFromNativeImagePath(asmFilePath, null) - : loadContext.LoadFromAssemblyPath(asmFilePath); + ? base.LoadFromNativeImagePath(asmFilePath, null) + : base.LoadFromAssemblyPath(asmFilePath); } // Since .NET CLI built versions of PowerShell have all the // built-in assemblies in the TPA list, the above will throw, @@ -212,46 +220,20 @@ namespace System.Management.Automation } } + // Raise AssemblyLoad event + OnAssemblyLoaded(asmLoaded); return asmLoaded; } /// - /// Load an assembly from its name. - /// - internal Assembly LoadFromAssemblyName(AssemblyName assemblyName) - { - return loadContext.LoadFromAssemblyName(assemblyName); - } - - /// - /// Load an assembly from its file path. + /// Load an IL or NI assembly from its file path. /// internal Assembly LoadFrom(string assemblyPath) { - #region Validation - if (string.IsNullOrEmpty(assemblyPath)) - { - throw new ArgumentNullException("assemblyPath"); - } - - assemblyPath = Path.GetFullPath(assemblyPath); - if (!File.Exists(assemblyPath)) - { - ThrowFileNotFoundException( - AssemblyPathDoesNotExist, - assemblyPath); - } - - if (!string.Equals(Path.GetExtension(assemblyPath), ".DLL", StringComparison.OrdinalIgnoreCase)) - { - ThrowFileLoadException( - InvalidAssemblyExtensionName, - assemblyPath); - } - #endregion Validation + ValidateAssemblyPath(assemblyPath, "assemblyPath"); Assembly asmLoaded; - AssemblyName assemblyName = AssemblyLoadContext.GetAssemblyName(assemblyPath); + AssemblyName assemblyName = GetAssemblyName(assemblyPath); // Probe the assembly cache if (TryGetAssemblyFromCache(assemblyName, out asmLoaded)) @@ -268,8 +250,8 @@ namespace System.Management.Automation { // Load the assembly through 'LoadFromNativeImagePath' or 'LoadFromAssemblyPath' asmLoaded = assemblyPath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) - ? loadContext.LoadFromNativeImagePath(assemblyPath, null) - : loadContext.LoadFromAssemblyPath(assemblyPath); + ? base.LoadFromNativeImagePath(assemblyPath, null) + : base.LoadFromAssemblyPath(assemblyPath); } // Since .NET CLI built versions of PowerShell have all the // built-in assemblies in the TPA list, the above will throw, @@ -294,6 +276,8 @@ namespace System.Management.Automation } } + // Raise AssemblyLoad event + OnAssemblyLoaded(asmLoaded); return asmLoaded; } @@ -302,9 +286,35 @@ namespace System.Management.Automation /// internal Assembly LoadFrom(Stream assembly) { - var asm = loadContext.LoadFromStream(assembly); - TryAddAssemblyToCache(asm); - return asm; + if (assembly == null) + throw new ArgumentNullException("assembly"); + + Assembly asmLoaded; + AssemblyName assemblyName = GetAssemblyName(assembly); + + // Probe the assembly cache + if (TryGetAssemblyFromCache(assemblyName, out asmLoaded)) + return asmLoaded; + + // Prepare to load the assembly + lock (syncObj) + { + // Probe the cache again in case it's already loaded + if (TryGetAssemblyFromCache(assemblyName, out asmLoaded)) + return asmLoaded; + + // Load the assembly through 'base.LoadFromStream' + asmLoaded = base.LoadFromStream(assembly); + if (asmLoaded != null) + { + // Add the loaded assembly to the cache + AssemblyCache.TryAdd(assemblyName.Name, asmLoaded); + } + } + + // Raise AssemblyLoad event + OnAssemblyLoaded(asmLoaded); + return asmLoaded; } /// @@ -330,10 +340,18 @@ namespace System.Management.Automation /// /// Try adding a new assembly to the cache /// + /// + /// This is for adding a dynamic assembly to the cache. + /// PowerShell generates dynamic assemblies by directly emitting IL, and this API + /// is to add such assemblies to the cache so that types in them are discoverable. + /// internal bool TryAddAssemblyToCache(Assembly assembly) { AssemblyName asmName = assembly.GetName(); - return AssemblyCache.TryAdd(asmName.Name, assembly); + bool success = AssemblyCache.TryAdd(asmName.Name, assembly); + // Raise AssemblyLoad event + if (success) { OnAssemblyLoaded(assembly); } + return success; } /// @@ -341,12 +359,17 @@ namespace System.Management.Automation /// internal string ProbeAssemblyFileForMetadataAnalysis(string assemblyShortName, string additionalSearchPath) { + if (string.IsNullOrEmpty(assemblyShortName)) + { + throw new ArgumentNullException("assemblyShortName"); + } + bool useAdditionalSearchPath = false; - if (!string.IsNullOrWhiteSpace(additionalSearchPath)) + if (!string.IsNullOrEmpty(additionalSearchPath)) { if (!Path.IsPathRooted(additionalSearchPath)) { - additionalSearchPath = Path.GetFullPath(additionalSearchPath); + throw new ArgumentException(AbsolutePathRequired, "additionalSearchPath"); } useAdditionalSearchPath = Directory.Exists(additionalSearchPath); } @@ -387,8 +410,74 @@ namespace System.Management.Automation } #endregion Protected_Internal_Methods - + #region Private_Methods + + /// + /// Handle the AssemblyLoad event + /// + private void OnAssemblyLoaded(Assembly assemblyLoaded) + { + Action assemblyLoadHandler = AssemblyLoad; + if (assemblyLoaded != null && assemblyLoadHandler != null) + { + try { + assemblyLoadHandler(assemblyLoaded); + } + catch { + // Catch all exceptions, same behavior as AppDomain.AssemblyLoad + } + } + } + + /// + /// Validate assembly path value for the specified parameter + /// + private void ValidateAssemblyPath(string assemblyPath, string parameterName) + { + if (string.IsNullOrEmpty(assemblyPath)) + { + throw new ArgumentNullException(parameterName); + } + + if (!Path.IsPathRooted(assemblyPath)) + { + throw new ArgumentException(AbsolutePathRequired, parameterName); + } + + if (!File.Exists(assemblyPath)) + { + ThrowFileNotFoundException( + AssemblyPathDoesNotExist, + assemblyPath); + } + + if (!string.Equals(Path.GetExtension(assemblyPath), ".DLL", StringComparison.OrdinalIgnoreCase)) + { + ThrowFileLoadException( + InvalidAssemblyExtensionName, + assemblyPath); + } + } + + /// + /// Get AssemblyName of an assembly stream + /// + private AssemblyName GetAssemblyName(Stream assembly) + { + if (assembly == null) + throw new ArgumentNullException("assembly"); + + string strongAssemblyName = null; + using (PEReader peReader = new PEReader(assembly, PEStreamOptions.LeaveOpen | PEStreamOptions.PrefetchMetadata)) + { + MetadataReader metadataReader = peReader.GetMetadataReader(); + strongAssemblyName = AssemblyMetadataHelper.GetAssemblyStrongName(metadataReader); + } + + assembly.Seek(0, SeekOrigin.Begin); + return new AssemblyName(strongAssemblyName); + } /// /// Try to get the specified assembly from cache @@ -466,25 +555,11 @@ namespace System.Management.Automation /// private Assembly GetTrustedPlatformAssembly(string tpaStrongName) { + // Load the specified TPA. If the TPA is already loaded, it will be somehow + // cached in CoreCLR runtime, and thus calling 'Assembly.Load' again won't + // cause any overhead. AssemblyName assemblyName = new AssemblyName(tpaStrongName); - - // Probe the assembly cache - Assembly asmLoaded; - if (TryGetAssemblyFromCache(assemblyName, out asmLoaded)) - return asmLoaded; - - // Prepare to load the TPA - lock (syncObj) - { - // Probe the cache again in case it's already loaded - if (TryGetAssemblyFromCache(assemblyName, out asmLoaded)) - return asmLoaded; - - // Load the specified TPA - asmLoaded = Assembly.Load(assemblyName); - AssemblyCache.TryAdd(assemblyName.Name, asmLoaded); - } - + Assembly asmLoaded = Assembly.Load(assemblyName); return asmLoaded; } @@ -510,10 +585,10 @@ namespace System.Management.Automation } /// - /// Set an instance of PowerShellAssemblyLoader to be the default Assembly Load Context. + /// Set an instance of PowerShellAssemblyLoadContext to be the default Assembly Load Context. /// This is the managed entry point for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll. /// - public class PowerShellAssemblyLoadContextInitializer + public static class PowerShellAssemblyLoadContextInitializer { // Porting note: it's much easier to send an LPStr on Linux private const UnmanagedType stringType = @@ -524,14 +599,16 @@ namespace System.Management.Automation #endif ; + public static PowerShellAssemblyLoadContext PSAsmLoadContext; + /// /// Set the default Assembly Load Context /// public static void SetPowerShellAssemblyLoadContext([MarshalAs(stringType)]string basePaths) { - if (PowerShellAssemblyLoader.Instance == null) + if (PSAsmLoadContext == null) { - PowerShellAssemblyLoader.Instance = new PowerShellAssemblyLoader(basePaths); + PSAsmLoadContext = new PowerShellAssemblyLoadContext(basePaths); } } } diff --git a/src/System.Management.Automation/utils/ClrFacade.cs b/src/System.Management.Automation/utils/ClrFacade.cs index 1f340b9ee5..f2a4b1cf62 100644 --- a/src/System.Management.Automation/utils/ClrFacade.cs +++ b/src/System.Management.Automation/utils/ClrFacade.cs @@ -97,7 +97,7 @@ namespace System.Management.Automation #if CORECLR try { - return process.SafeHandle.DangerousGetHandle(); + return process.SafeHandle.DangerousGetHandle(); } catch (InvalidOperationException) { @@ -361,17 +361,25 @@ namespace System.Management.Automation /// /// Add the AssemblyLoad handler /// - // Porting note: disabled until full solution comes - // internal static void AddAssemblyLoadHandler(Action handler) - // { - // PSAssemblyLoadContext.AssemblyLoad += handler; - // } + internal static void AddAssemblyLoadHandler(Action handler) + { + PSAssemblyLoadContext.AssemblyLoad += handler; + } - private static PowerShellAssemblyLoader PSAssemblyLoadContext + private static volatile PowerShellAssemblyLoadContext _psLoadContext; + private static PowerShellAssemblyLoadContext PSAssemblyLoadContext { get { - return PowerShellAssemblyLoader.Instance; + if (_psLoadContext == null) + { + _psLoadContext = PowerShellAssemblyLoadContextInitializer.PSAsmLoadContext; + if (_psLoadContext == null) + { + throw new InvalidOperationException(ParserStrings.InvalidAssemblyLoadContextInUse); + } + } + return _psLoadContext; } } #endif diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs index eea0477697..1bcf5b8c1b 100644 --- a/src/powershell/Program.cs +++ b/src/powershell/Program.cs @@ -3,6 +3,7 @@ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System.Management.Automation; +using System.Reflection; namespace Microsoft.PowerShell { @@ -21,9 +22,14 @@ namespace Microsoft.PowerShell { #if CORECLR // Open PowerShell has to set the ALC here, since we don't own the native host - PowerShellAssemblyLoadContextInitializer.SetPowerShellAssemblyLoadContext(string.Empty); -#endif + PowerShellAssemblyLoadContextInitializer.SetPowerShellAssemblyLoadContext(System.AppContext.BaseDirectory); + var consoleHost = PowerShellAssemblyLoadContextInitializer.PSAsmLoadContext.LoadFromAssemblyName(new AssemblyName("Microsoft.PowerShell.ConsoleHost, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")); + var unmanagedPSEntry = consoleHost.GetType("Microsoft.PowerShell.UnmanagedPSEntry", true); + var start = unmanagedPSEntry.GetMethod("Start"); + return (int)start.Invoke(null, new object[] { string.Empty, args, args.Length }); +#else return UnmanagedPSEntry.Start(string.Empty, args, args.Length); +#endif } } } From c78732d2262a182f2c76ebd74703e97667f89757 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 24 Jun 2016 16:28:37 -0700 Subject: [PATCH 02/13] Remove ClrFacade.Load Instead, rely on intercepting Assembly.Load --- .../commands/utility/AddType.cs | 8 +++--- .../engine/ExecutionContext.cs | 2 +- .../engine/InitialSessionState.cs | 2 +- .../fanin/InitialSessionStateProvider.cs | 2 +- .../help/HelpSystem.cs | 2 +- .../config/RunspaceConfigForSingleShell.cs | 2 +- .../utils/ClrFacade.cs | 26 ------------------- 7 files changed, 9 insertions(+), 35 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs index 94eb297aab..58a848aeba 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs @@ -1148,7 +1148,7 @@ namespace Microsoft.PowerShell.Commands // First try by strong name try { - loadedAssembly = ClrFacade.Load(new AssemblyName(assemblyName)); + loadedAssembly = Assembly.Load(new AssemblyName(assemblyName)); } // Generates a FileNotFoundException if you can't load the strong type. // So we'll try from the short name. @@ -2050,7 +2050,7 @@ namespace Microsoft.PowerShell.Commands // First try by strong name try { - loadedAssembly = ClrFacade.Load(assemblyName); + loadedAssembly = Assembly.Load(assemblyName); } // Generates a FileNotFoundException if you can't load the strong type. // So we'll try from the short name. @@ -2062,7 +2062,7 @@ namespace Microsoft.PowerShell.Commands // Next, try an exact match if (StrongNames.Value.ContainsKey(assemblyName)) { - return ClrFacade.Load(StrongNames.Value[assemblyName]); + return Assembly.Load(StrongNames.Value[assemblyName]); } // If the assembly name doesn't contain wildcards, return null. The caller generates an error here. @@ -2105,7 +2105,7 @@ namespace Microsoft.PowerShell.Commands return null; // Otherwise, load the assembly. - return ClrFacade.Load(matchedStrongName); + return Assembly.Load(matchedStrongName); } private static ConcurrentDictionary InitializeStrongNameDictionary() diff --git a/src/System.Management.Automation/engine/ExecutionContext.cs b/src/System.Management.Automation/engine/ExecutionContext.cs index c0db813467..5920cad069 100644 --- a/src/System.Management.Automation/engine/ExecutionContext.cs +++ b/src/System.Management.Automation/engine/ExecutionContext.cs @@ -1559,7 +1559,7 @@ namespace System.Management.Automation try { - loadedAssembly = ClrFacade.Load(new AssemblyName(assemblyString)); + loadedAssembly = Assembly.Load(new AssemblyName(assemblyString)); } catch (FileNotFoundException fileNotFound) { diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 8597d203ea..f25dfcb771 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -5674,7 +5674,7 @@ if($paths) { try { // WARNING: DUPLICATE CODE see RunspaceConfigForSingleShell - assembly = ClrFacade.Load(new AssemblyName(psSnapInInfo.AssemblyName)); + assembly = Assembly.Load(new AssemblyName(psSnapInInfo.AssemblyName)); } catch (BadImageFormatException e) { diff --git a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs index 2f37ac1c23..5b2057fcd7 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs @@ -662,7 +662,7 @@ namespace System.Management.Automation.Remoting { try { - result = ClrFacade.Load(new AssemblyName(assemblyName)); + result = Assembly.Load(new AssemblyName(assemblyName)); } catch (FileLoadException e) { diff --git a/src/System.Management.Automation/help/HelpSystem.cs b/src/System.Management.Automation/help/HelpSystem.cs index 84801679e9..ca87f9c155 100644 --- a/src/System.Management.Automation/help/HelpSystem.cs +++ b/src/System.Management.Automation/help/HelpSystem.cs @@ -667,7 +667,7 @@ namespace System.Management.Automation } else { - providerAssembly = ClrFacade.Load(providerInfo.AssemblyName); + providerAssembly = Assembly.Load(providerInfo.AssemblyName); } try diff --git a/src/System.Management.Automation/singleshell/config/RunspaceConfigForSingleShell.cs b/src/System.Management.Automation/singleshell/config/RunspaceConfigForSingleShell.cs index 0cc1f1ad6d..28b00731ea 100644 --- a/src/System.Management.Automation/singleshell/config/RunspaceConfigForSingleShell.cs +++ b/src/System.Management.Automation/singleshell/config/RunspaceConfigForSingleShell.cs @@ -618,7 +618,7 @@ namespace System.Management.Automation.Runspaces try { // WARNING: DUPLICATE CODE see InitialSessionState - assembly = ClrFacade.Load(new AssemblyName(mshsnapinInfo.AssemblyName)); + assembly = Assembly.Load(new AssemblyName(mshsnapinInfo.AssemblyName)); } catch (FileLoadException e) { diff --git a/src/System.Management.Automation/utils/ClrFacade.cs b/src/System.Management.Automation/utils/ClrFacade.cs index f2a4b1cf62..20c51cfcb3 100644 --- a/src/System.Management.Automation/utils/ClrFacade.cs +++ b/src/System.Management.Automation/utils/ClrFacade.cs @@ -279,32 +279,6 @@ namespace System.Management.Automation #endif } - /// - /// Porting note: Load assembly by name through the AssemblyLoadContext. - /// This is to ensure that the types get cached. - /// - internal static Assembly Load(AssemblyName assembly) - { -#if CORECLR - return PSAssemblyLoadContext.LoadFromAssemblyName(assembly); -#else - return Assembly.Load(assembly); -#endif - } - - /// - /// Same as the above, but overloaded for a name in a string. - /// - internal static Assembly Load(string assembly) - { -#if CORECLR - return PSAssemblyLoadContext.LoadFromAssemblyName(new AssemblyName(assembly)); -#else - return Assembly.Load(assembly); -#endif - } - - /// /// Facade for EnumBuilder.CreateTypeInfo /// From c58c7d3543c718957497509a50ad30b9c956906c Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 24 Jun 2016 16:56:43 -0700 Subject: [PATCH 03/13] Assert basePaths was not null --- .../CoreCLR/CorePsAssemblyLoadContext.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs index cdb88e0a21..4893b9c317 100644 --- a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs @@ -57,6 +57,11 @@ namespace System.Management.Automation internal PowerShellAssemblyLoadContext(string basePaths) { #region Validation + if (string.IsNullOrEmpty(basePaths)) + { + throw new ArgumentNullException("basePaths"); + } + this.basePaths = basePaths.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < this.basePaths.Length; i++) { @@ -606,6 +611,11 @@ namespace System.Management.Automation /// public static void SetPowerShellAssemblyLoadContext([MarshalAs(stringType)]string basePaths) { + if (string.IsNullOrEmpty(basePaths)) + { + throw new ArgumentNullException("basePaths"); + } + if (PSAsmLoadContext == null) { PSAsmLoadContext = new PowerShellAssemblyLoadContext(basePaths); From fdfd02ccdaad2af84c45830df315da3e2cf1c905 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Fri, 24 Jun 2016 16:56:59 -0700 Subject: [PATCH 04/13] Remove TPA list work-around --- .../CoreCLR/CorePsAssemblyLoadContext.cs | 40 ++++--------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs index 4893b9c317..5cc95c9681 100644 --- a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs @@ -201,23 +201,9 @@ namespace System.Management.Automation assemblyName.FullName); } - try - { - asmLoaded = asmFilePath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) - ? base.LoadFromNativeImagePath(asmFilePath, null) - : base.LoadFromAssemblyPath(asmFilePath); - } - // Since .NET CLI built versions of PowerShell have all the - // built-in assemblies in the TPA list, the above will throw, - // and we have to use Assembly.Load. However, we must try the - // above first, otherwise assemblies that exist outside the TPA - // list will go into a recursive loop. - catch (System.IO.FileLoadException) - { - asmLoaded = System.Reflection.Assembly.Load(assemblyName); - } - - // If it loaded, add it to the cache + asmLoaded = asmFilePath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) + ? base.LoadFromNativeImagePath(asmFilePath, null) + : base.LoadFromAssemblyPath(asmFilePath); if (asmLoaded != null) { // Add the loaded assembly to the cache @@ -251,22 +237,10 @@ namespace System.Management.Automation if (TryGetAssemblyFromCache(assemblyName, out asmLoaded)) return asmLoaded; - try - { - // Load the assembly through 'LoadFromNativeImagePath' or 'LoadFromAssemblyPath' - asmLoaded = assemblyPath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) - ? base.LoadFromNativeImagePath(assemblyPath, null) - : base.LoadFromAssemblyPath(assemblyPath); - } - // Since .NET CLI built versions of PowerShell have all the - // built-in assemblies in the TPA list, the above will throw, - // and we have to use Assembly.Load. However, we must try the - // above first, otherwise assemblies that exist outside the TPA - // list will go into a recursive loop. - catch (System.IO.FileLoadException) - { - asmLoaded = System.Reflection.Assembly.Load(assemblyName); - } + // Load the assembly through 'LoadFromNativeImagePath' or 'LoadFromAssemblyPath' + asmLoaded = assemblyPath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) + ? base.LoadFromNativeImagePath(assemblyPath, null) + : base.LoadFromAssemblyPath(assemblyPath); if (asmLoaded != null) { From 66dcd00d93ed24246de53a7a1e0b6c54d890916c Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 28 Jun 2016 09:59:22 -0700 Subject: [PATCH 05/13] Make AssemblyLoadContext a singleton and refactor --- .../CoreCLR/CorePsAssemblyLoadContext.cs | 253 ++++++++++++++---- .../project.json | 1 + .../utils/ClrFacade.cs | 11 +- src/TypeCatalogGen/TypeCatalogGen.cs | 9 +- src/powershell/Program.cs | 15 +- 5 files changed, 214 insertions(+), 75 deletions(-) diff --git a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs index 5cc95c9681..abd8221734 100644 --- a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs @@ -19,7 +19,7 @@ namespace System.Management.Automation /// /// The powershell custom AssemblyLoadContext implementation /// - public partial class PowerShellAssemblyLoadContext : AssemblyLoadContext + internal partial class PowerShellAssemblyLoadContext : AssemblyLoadContext { #region Resource_Strings @@ -30,21 +30,30 @@ namespace System.Management.Automation // 2. Load assembly with culture 'en' (Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.resources, Version=3.0.0.0, Culture=en, PublicKeyToken=31bf3856ad364e35) // When the first attempt fails, we again need to retrieve the resouce string to construct another exception, which ends up with an infinite loop. private const string BaseFolderDoesNotExist = "The base directory '{0}' does not exist."; - private const string CannotFindFileBasedOnAssemblyName = "Could not load file or assembly '{0}' or one of its dependencies. The system cannot find the file specified under any probing paths."; private const string ManifestDefinitionDoesNotMatch = "Could not load file or assembly '{0}' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference."; private const string AssemblyPathDoesNotExist = "Could not load file or assembly '{0}' or one of its dependencies. The system cannot find the file specified."; private const string InvalidAssemblyExtensionName = "Could not load file or assembly '{0}' or one of its dependencies. The file specified is not a DLL file."; private const string AbsolutePathRequired = "Absolute path information is required."; + private const string SingletonAlreadyInitialized = "The singleton of PowerShellAssemblyLoadContext has already been initialized."; + private const string UseResolvingEventHandlerOnly = "PowerShellAssemblyLoadContext was initialized to use its 'Resolving' event handler only."; #endregion Resource_Strings #region Constructor /// - /// This constructor is for testability purpose only + /// Initialize a singleton of PowerShellAssemblyLoadContext /// - protected PowerShellAssemblyLoadContext() + internal static PowerShellAssemblyLoadContext InitializeSingleton(string basePaths, bool useResolvingHandlerOnly) { + lock (syncObj) + { + if (Instance != null) + throw new InvalidOperationException(SingletonAlreadyInitialized); + + Instance = new PowerShellAssemblyLoadContext(basePaths, useResolvingHandlerOnly); + return Instance; + } } /// @@ -54,7 +63,25 @@ namespace System.Management.Automation /// Base directory paths that are separated by semicolon ';'. /// They will be the default paths to probe assemblies. /// - internal PowerShellAssemblyLoadContext(string basePaths) + /// + /// Indicate whether this instance is going to be used as a + /// full fledged ALC, or only its 'Resolve' handler is going + /// to be used. + /// + /// + /// When is true, we will register to the 'Resolving' event of the default + /// load context with our 'Resolve' method, and depend on the default load context to resolve/load assemblies for PS. + /// This mode is used when TPA list of the native host only contains .NET Core libraries. + /// In this case, TPA binder will be consulted before hitting our resolving logic. The binding order of Assembly.Load is: + /// TPA binder --> Resolving event + /// + /// When is false, we will use this instance as a full fledged load context + /// to resolve/load assemblies for PS. This mode is used when TPA list of the native host contains both .NET Core libraries + /// and PS assemblies. + /// In this case, our Load override will kick in before consulting the TPA binder. The binding order of Assembly.Load is: + /// Load override --> TPA binder --> Resolving event + /// + private PowerShellAssemblyLoadContext(string basePaths, bool useResolvingHandlerOnly) { #region Validation if (string.IsNullOrEmpty(basePaths)) @@ -82,17 +109,22 @@ namespace System.Management.Automation this.probingPaths = new List(this.basePaths); // NEXT: Initialize the CoreCLR type catalog dictionary [OrdinalIgnoreCase] - // - Key: namespace qualified type name (FullName) - // - Value: strong name of the TPA that contains the type represented by Key. coreClrTypeCatalog = InitializeTypeCatalog(); + + // LAST: Handle useResolvingHandlerOnly flag + this.useResolvingHandlerOnly = useResolvingHandlerOnly; + this.activeLoadContext = useResolvingHandlerOnly ? AssemblyLoadContext.Default : this; + if (useResolvingHandlerOnly) + AssemblyLoadContext.Default.Resolving += Resolve; } #endregion Constructor #region Fields - - // Serialized type catalog file - private readonly object syncObj = new object(); + + private readonly bool useResolvingHandlerOnly; + private readonly AssemblyLoadContext activeLoadContext; + private readonly static object syncObj = new object(); private readonly string[] basePaths; // Initially, 'probingPaths' only contains psbase path. But every time we load an assembly through 'LoadFrom(string AssemblyPath)', we // add its parent path to 'probingPaths', so that we are able to support implicit loading of an assembly from the same place where the @@ -100,7 +132,9 @@ namespace System.Management.Automation // We don't need to worry about removing any paths from 'probingPaths', because once an assembly is loaded, it won't be unloaded until // the current process exits, and thus the assembly itself and its parent folder cannot be deleted or renamed. private readonly List probingPaths; - // We use dictionary because the generated binary file by DataContractSerializer is about 39% smaller in size than using Hashtable. + // CoreCLR type catalog dictionary + // - Key: namespace qualified type name (FullName) + // - Value: strong name of the TPA that contains the type represented by Key. private readonly Dictionary coreClrTypeCatalog; private readonly string[] extensions = new string[] { ".ni.dll", ".dll" }; @@ -123,6 +157,18 @@ namespace System.Management.Automation #endregion Fields + #region Properties + + /// + /// Singleton instance of PowerShellAssemblyLoadContext + /// + public static PowerShellAssemblyLoadContext Instance + { + get; private set; + } + + #endregion Properties + #region Events /// @@ -139,6 +185,17 @@ namespace System.Management.Automation /// Search the file "[assemblyName.Name][.ni].dll" in probing paths. If the file is found and it matches the requested AssemblyName, load it with LoadFromAssemblyPath. /// protected override Assembly Load(AssemblyName assemblyName) + { + if (useResolvingHandlerOnly) + throw new NotSupportedException(UseResolvingEventHandlerOnly); + + return Resolve(this, assemblyName); + } + + /// + /// The handler for the Resolving event + /// + private Assembly Resolve(AssemblyLoadContext loadContext, AssemblyName assemblyName) { // Probe the assembly cache Assembly asmLoaded; @@ -185,25 +242,16 @@ namespace System.Management.Automation } } - // We failed to find the file specified - if (!isAssemblyFileFound) + // We failed to find the assembly file; or we found the file, but the assembly file doesn't match the request. + // In this case, return null so that other Resolving event handlers can kick in to resolve the request. + if (!isAssemblyFileFound || !isAssemblyFileMatching) { - ThrowFileNotFoundException( - CannotFindFileBasedOnAssemblyName, - assemblyName.FullName); - } - - // We found the file specified, but the found assembly doesn't match the request - if (!isAssemblyFileMatching) - { - ThrowFileLoadException( - ManifestDefinitionDoesNotMatch, - assemblyName.FullName); + return null; } asmLoaded = asmFilePath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) - ? base.LoadFromNativeImagePath(asmFilePath, null) - : base.LoadFromAssemblyPath(asmFilePath); + ? loadContext.LoadFromNativeImagePath(asmFilePath, null) + : loadContext.LoadFromAssemblyPath(asmFilePath); if (asmLoaded != null) { // Add the loaded assembly to the cache @@ -239,14 +287,14 @@ namespace System.Management.Automation // Load the assembly through 'LoadFromNativeImagePath' or 'LoadFromAssemblyPath' asmLoaded = assemblyPath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) - ? base.LoadFromNativeImagePath(assemblyPath, null) - : base.LoadFromAssemblyPath(assemblyPath); + ? activeLoadContext.LoadFromNativeImagePath(assemblyPath, null) + : activeLoadContext.LoadFromAssemblyPath(assemblyPath); if (asmLoaded != null) { // Add the loaded assembly to the cache AssemblyCache.TryAdd(assemblyName.Name, asmLoaded); - // Add the its parent path to our probing paths + // Add its parent path to our probing paths string parentPath = Path.GetDirectoryName(assemblyPath); if (!probingPaths.Contains(parentPath)) { @@ -282,8 +330,8 @@ namespace System.Management.Automation if (TryGetAssemblyFromCache(assemblyName, out asmLoaded)) return asmLoaded; - // Load the assembly through 'base.LoadFromStream' - asmLoaded = base.LoadFromStream(assembly); + // Load the assembly through 'LoadFromStream' + asmLoaded = activeLoadContext.LoadFromStream(assembly); if (asmLoaded != null) { // Add the loaded assembly to the cache @@ -534,12 +582,38 @@ namespace System.Management.Automation /// private Assembly GetTrustedPlatformAssembly(string tpaStrongName) { - // Load the specified TPA. If the TPA is already loaded, it will be somehow - // cached in CoreCLR runtime, and thus calling 'Assembly.Load' again won't - // cause any overhead. + Assembly asmLoaded; AssemblyName assemblyName = new AssemblyName(tpaStrongName); - Assembly asmLoaded = Assembly.Load(assemblyName); - return asmLoaded; + + // With the current standalone-app model of OPS, .NET Core libraries and PS assemblies are mixed together in one folder. + // So when using PSALC as a full fledged ALC in OPS, some TPAs might be loaded by our Load override. In that case, if we + // alwasy call Assembly.Load here to get a TPA, we might end up with a different Assembly instance of the the same TPA + // loaded in the default load context. We want to use the same assembly instance for type resolution in PS to avoid creating + // types and running .NET code from different assembly instances of the same DLL. Therefore, we try our cache first to see + // if the requested TPA is already loaded. If so, we use that one. If not, we load it in default context using Assembly.Load. + // Once a TPA is loaded in the default context, the same Assembly instance will always be used by custom ALC's when they attempt + // to resolve an "Assembly.Load" request for the same TPA. + // + // For in-box PS of NanoServer/IoT and the share-framework host model of OPS, we don't have the mixed libraries/assemblies + // problem, and TPAs are always resolved/loaded by the default context. In those cases, checking our cache would be unnecessary, + // but it won't cause any problems. + + // Probe the assembly cache + if (TryGetAssemblyFromCache(assemblyName, out asmLoaded)) + return asmLoaded; + + // Prepare to load the assembly + lock (syncObj) + { + // Probe the cache again in case it's already loaded + if (TryGetAssemblyFromCache(assemblyName, out asmLoaded)) + return asmLoaded; + + // The requested TPA is not loaded by PS ALC, so load it in the default load context using Assembly.Load. + // There is no need to add it to our cache. It's cached in the default context. + asmLoaded = Assembly.Load(assemblyName); + return asmLoaded; + } } /// @@ -564,36 +638,103 @@ namespace System.Management.Automation } /// - /// Set an instance of PowerShellAssemblyLoadContext to be the default Assembly Load Context. /// This is the managed entry point for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll. /// - public static class PowerShellAssemblyLoadContextInitializer + public class PowerShellAssemblyLoadContextInitializer { - // Porting note: it's much easier to send an LPStr on Linux - private const UnmanagedType stringType = - #if LINUX - UnmanagedType.LPStr - #else - UnmanagedType.LPWStr - #endif - ; - - public static PowerShellAssemblyLoadContext PSAsmLoadContext; + private static object[] EmptyArray = new object[0]; /// - /// Set the default Assembly Load Context + /// Create a singleton of PowerShellAssemblyLoadContext. + /// Then register to the Resolving event of the load context that loads this assembly. /// - public static void SetPowerShellAssemblyLoadContext([MarshalAs(stringType)]string basePaths) + /// + /// This method is to be used by native host whose TPA list doesn't include PS assemblies, such as the + /// in-box Nano powershell.exe, the PS remote WinRM plugin, in-box Nano DSC and in-box Nano SCOM agent. + /// + /// + /// Base directory paths that are separated by semicolon ';'. + /// They will be the default paths to probe assemblies. + /// + public static void SetPowerShellAssemblyLoadContext([MarshalAs(UnmanagedType.LPWStr)]string basePaths) { if (string.IsNullOrEmpty(basePaths)) - { throw new ArgumentNullException("basePaths"); - } - if (PSAsmLoadContext == null) - { - PSAsmLoadContext = new PowerShellAssemblyLoadContext(basePaths); - } + PowerShellAssemblyLoadContext.InitializeSingleton(basePaths, useResolvingHandlerOnly: true); + } + + /// + /// Create a singleton of PowerShellAssemblyLoadContext. + /// Then load the assembly containing the actual entry point using it. + /// + /// + /// Base directory paths that are separated by semicolon ';'. + /// They will be the default paths to probe assemblies. + /// + /// + /// Name of the assembly that contains the actual entry point. + /// + /// + /// The assembly that contains the actual entry point. + /// + public static Assembly InitializeAndLoadEntryAssembly(string basePaths, AssemblyName entryAssemblyName) + { + if (string.IsNullOrEmpty(basePaths)) + throw new ArgumentNullException("basePaths"); + + if (entryAssemblyName == null) + throw new ArgumentNullException("entryAssemblyName"); + + var psLoadContext = PowerShellAssemblyLoadContext.InitializeSingleton(basePaths, useResolvingHandlerOnly: false); + return psLoadContext.LoadFromAssemblyName(entryAssemblyName); + } + + /// + /// Create a singleton of PowerShellAssemblyLoadContext. + /// Then call into the actual entry point based on the given assembly name, type name, method name and arguments. + /// + /// + /// Base directory paths that are separated by semicolon ';'. + /// They will be the default paths to probe assemblies. + /// + /// + /// Name of the assembly that contains the actual entry point. + /// + /// + /// Name of the type that contains the actual entry point. + /// + /// + /// Name of the actual entry point method. + /// + /// + /// An array of arguments passed to the entry point method. + /// + /// + /// The return value of running the entry point method. + /// + public static object InitializeAndCallEntryMethod(string basePaths, AssemblyName entryAssemblyName, string entryTypeName, string entryMethodName, object[] args) + { + if (string.IsNullOrEmpty(basePaths)) + throw new ArgumentNullException("basePaths"); + + if (entryAssemblyName == null) + throw new ArgumentNullException("entryAssemblyName"); + + if (string.IsNullOrEmpty(entryTypeName)) + throw new ArgumentNullException("entryTypeName"); + + if (string.IsNullOrEmpty(entryMethodName)) + throw new ArgumentNullException("entryMethodName"); + + args = args ?? EmptyArray; + + var psLoadContext = PowerShellAssemblyLoadContext.InitializeSingleton(basePaths, useResolvingHandlerOnly: false); + var entryAssembly = psLoadContext.LoadFromAssemblyName(entryAssemblyName); + var entryType = entryAssembly.GetType(entryTypeName, throwOnError: true, ignoreCase: true); + var methodInfo = entryType.GetMethod(entryMethodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase); + + return methodInfo.Invoke(null, args); } } } diff --git a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/project.json b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/project.json index 4eedb1744c..48dd75579d 100644 --- a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/project.json +++ b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/project.json @@ -27,6 +27,7 @@ "NETStandard.Library": "1.6.0", "System.Runtime.Loader": "4.0.0", "System.Reflection.Metadata": "1.1.0", + "System.Reflection.TypeExtensions": "4.1.0", "System.Security.Cryptography.Algorithms": "4.2.0" } } diff --git a/src/System.Management.Automation/utils/ClrFacade.cs b/src/System.Management.Automation/utils/ClrFacade.cs index 20c51cfcb3..c866395ded 100644 --- a/src/System.Management.Automation/utils/ClrFacade.cs +++ b/src/System.Management.Automation/utils/ClrFacade.cs @@ -340,20 +340,11 @@ namespace System.Management.Automation PSAssemblyLoadContext.AssemblyLoad += handler; } - private static volatile PowerShellAssemblyLoadContext _psLoadContext; private static PowerShellAssemblyLoadContext PSAssemblyLoadContext { get { - if (_psLoadContext == null) - { - _psLoadContext = PowerShellAssemblyLoadContextInitializer.PSAsmLoadContext; - if (_psLoadContext == null) - { - throw new InvalidOperationException(ParserStrings.InvalidAssemblyLoadContextInUse); - } - } - return _psLoadContext; + return PowerShellAssemblyLoadContext.Instance; } } #endif diff --git a/src/TypeCatalogGen/TypeCatalogGen.cs b/src/TypeCatalogGen/TypeCatalogGen.cs index 893423c85e..b93cac0706 100644 --- a/src/TypeCatalogGen/TypeCatalogGen.cs +++ b/src/TypeCatalogGen/TypeCatalogGen.cs @@ -41,7 +41,7 @@ Usage: TypeCatalogGen.exe <{0}> <{1}> /* * Go through all reference assemblies of .NET Core and generate the type catalog -> Dictionary - * Then auto-generate the partial class 'PowerShellAssemblyLoader' that has the code to initialize the type catalog cache. + * Then auto-generate the partial class 'PowerShellAssemblyLoadContext' that has the code to initialize the type catalog cache. * * In CoreCLR, there is no way to get all loaded TPA assemblies (.NET Framework Assemblies). In order to get type based on type name, powershell needs to know what .NET * types are available and in which TPA assemblies. So we have to generate the type catalog based on the reference assemblies of .NET Core. @@ -105,7 +105,7 @@ Usage: TypeCatalogGen.exe <{0}> <{1}> } } - WritePowerShellAssemblyLoaderPartialClass(targetFilePath, typeNameToAssemblyMap); + WritePowerShellAssemblyLoadContextPartialClass(targetFilePath, typeNameToAssemblyMap); } /// @@ -272,7 +272,7 @@ Usage: TypeCatalogGen.exe <{0}> <{1}> /// /// Generate the CSharp source code that initialize the type catalog. /// - private static void WritePowerShellAssemblyLoaderPartialClass(string targetFilePath, Dictionary typeNameToAssemblyMap) + private static void WritePowerShellAssemblyLoadContextPartialClass(string targetFilePath, Dictionary typeNameToAssemblyMap) { const string SourceFormat = " typeCatalog[\"{0}\"] = \"{1}\";"; const string SourceHead = @"// @@ -284,10 +284,11 @@ Usage: TypeCatalogGen.exe <{0}> <{1}> // catalog based on the reference assemblies of .NET Core. // using System.Collections.Generic; +using System.Runtime.Loader; namespace System.Management.Automation {{ - internal partial class PowerShellAssemblyLoader + internal partial class PowerShellAssemblyLoadContext : AssemblyLoadContext {{ private Dictionary InitializeTypeCatalog() {{ diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs index 1bcf5b8c1b..be8d1537b7 100644 --- a/src/powershell/Program.cs +++ b/src/powershell/Program.cs @@ -2,6 +2,7 @@ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ +using System; using System.Management.Automation; using System.Reflection; @@ -22,11 +23,15 @@ namespace Microsoft.PowerShell { #if CORECLR // Open PowerShell has to set the ALC here, since we don't own the native host - PowerShellAssemblyLoadContextInitializer.SetPowerShellAssemblyLoadContext(System.AppContext.BaseDirectory); - var consoleHost = PowerShellAssemblyLoadContextInitializer.PSAsmLoadContext.LoadFromAssemblyName(new AssemblyName("Microsoft.PowerShell.ConsoleHost, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")); - var unmanagedPSEntry = consoleHost.GetType("Microsoft.PowerShell.UnmanagedPSEntry", true); - var start = unmanagedPSEntry.GetMethod("Start"); - return (int)start.Invoke(null, new object[] { string.Empty, args, args.Length }); + string appBase = System.IO.Path.GetDirectoryName(typeof(ManagedPSEntry).GetTypeInfo().Assembly.Location); + Console.WriteLine(" == APPBASE == {0}", appBase); + return (int)PowerShellAssemblyLoadContextInitializer. + InitializeAndCallEntryMethod( + appBase, + new AssemblyName("Microsoft.PowerShell.ConsoleHost, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"), + "Microsoft.PowerShell.UnmanagedPSEntry", + "Start", + new object[] { string.Empty, args, args.Length }); #else return UnmanagedPSEntry.Start(string.Empty, args, args.Length); #endif From ca28391b3886a1dd7d44b03496e5d90e37c09150 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 28 Jun 2016 13:29:17 -0700 Subject: [PATCH 06/13] Fix it for 'dotnet build' scenario --- .../CoreCLR/CorePsAssemblyLoadContext.cs | 27 +++++++++++++++++-- .../utils/ClrFacade.cs | 4 +-- src/powershell/Program.cs | 1 + 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs index abd8221734..7b4f7f147b 100644 --- a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs @@ -189,6 +189,7 @@ namespace System.Management.Automation if (useResolvingHandlerOnly) throw new NotSupportedException(UseResolvingEventHandlerOnly); + System.Console.WriteLine("== LC1 == Requesting: {0}", assemblyName.FullName); return Resolve(this, assemblyName); } @@ -246,6 +247,7 @@ namespace System.Management.Automation // In this case, return null so that other Resolving event handlers can kick in to resolve the request. if (!isAssemblyFileFound || !isAssemblyFileMatching) { + System.Console.WriteLine(" -- Return null"); return null; } @@ -256,6 +258,7 @@ namespace System.Management.Automation { // Add the loaded assembly to the cache AssemblyCache.TryAdd(assemblyName.Name, asmLoaded); + System.Console.WriteLine(" ++ Load succeed: {0}", asmLoaded.FullName); } } @@ -270,6 +273,7 @@ namespace System.Management.Automation internal Assembly LoadFrom(string assemblyPath) { ValidateAssemblyPath(assemblyPath, "assemblyPath"); + System.Console.WriteLine("*** LC1 *** LoadFrom {0}", assemblyPath); Assembly asmLoaded; AssemblyName assemblyName = GetAssemblyName(assemblyPath); @@ -300,6 +304,7 @@ namespace System.Management.Automation { probingPaths.Add(parentPath); } + System.Console.WriteLine(" ++ LoadFrom succeed: {0}", asmLoaded.FullName); } } @@ -436,10 +441,28 @@ namespace System.Management.Automation return coreClrTypeCatalog.Keys; } + /// + /// Set the profile optimization root on the approprite load context + /// + internal void SetProfileOptimizationRootImpl(string directoryPath) + { + if (this.useResolvingHandlerOnly) + activeLoadContext.SetProfileOptimizationRoot(directoryPath); + } + + /// + /// Start the profile optimization on the approprite load context + /// + internal void StartProfileOptimizationImpl(string profile) + { + if (this.useResolvingHandlerOnly) + activeLoadContext.StartProfileOptimization(profile); + } + #endregion Protected_Internal_Methods - + #region Private_Methods - + /// /// Handle the AssemblyLoad event /// diff --git a/src/System.Management.Automation/utils/ClrFacade.cs b/src/System.Management.Automation/utils/ClrFacade.cs index c866395ded..16d085820d 100644 --- a/src/System.Management.Automation/utils/ClrFacade.cs +++ b/src/System.Management.Automation/utils/ClrFacade.cs @@ -661,7 +661,7 @@ namespace System.Management.Automation internal static void SetProfileOptimizationRoot(string directoryPath) { #if CORECLR - System.Runtime.Loader.AssemblyLoadContext.Default.SetProfileOptimizationRoot(directoryPath); + PSAssemblyLoadContext.SetProfileOptimizationRootImpl(directoryPath); #else System.Runtime.ProfileOptimization.SetProfileRoot(directoryPath); #endif @@ -674,7 +674,7 @@ namespace System.Management.Automation internal static void StartProfileOptimization(string profile) { #if CORECLR - System.Runtime.Loader.AssemblyLoadContext.Default.StartProfileOptimization(profile); + PSAssemblyLoadContext.StartProfileOptimizationImpl(profile); #else System.Runtime.ProfileOptimization.StartProfile(profile); #endif diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs index be8d1537b7..01a958ec29 100644 --- a/src/powershell/Program.cs +++ b/src/powershell/Program.cs @@ -25,6 +25,7 @@ namespace Microsoft.PowerShell // Open PowerShell has to set the ALC here, since we don't own the native host string appBase = System.IO.Path.GetDirectoryName(typeof(ManagedPSEntry).GetTypeInfo().Assembly.Location); Console.WriteLine(" == APPBASE == {0}", appBase); + //Console.Read(); return (int)PowerShellAssemblyLoadContextInitializer. InitializeAndCallEntryMethod( appBase, From 03c99586a48af9ec8788fc6ec9541d1d977de814 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 28 Jun 2016 15:45:55 -0700 Subject: [PATCH 07/13] Update ALC to work in publish scenario --- .../CoreCLR/CorePsAssemblyLoadContext.cs | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs index 7b4f7f147b..dc4095257d 100644 --- a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs @@ -89,6 +89,8 @@ namespace System.Management.Automation throw new ArgumentNullException("basePaths"); } + trace = System.Environment.GetEnvironmentVariable("ALC_TRACE") == "1"; + this.basePaths = basePaths.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < this.basePaths.Length; i++) { @@ -137,6 +139,8 @@ namespace System.Management.Automation // - Value: strong name of the TPA that contains the type represented by Key. private readonly Dictionary coreClrTypeCatalog; private readonly string[] extensions = new string[] { ".ni.dll", ".dll" }; + private readonly bool trace; + private HashSet filterSet; /// /// Assembly cache accross the AppDomain @@ -189,7 +193,20 @@ namespace System.Management.Automation if (useResolvingHandlerOnly) throw new NotSupportedException(UseResolvingEventHandlerOnly); - System.Console.WriteLine("== LC1 == Requesting: {0}", assemblyName.FullName); + if (trace) + System.Console.WriteLine("== LC1 == Requesting: {0}", assemblyName.FullName); + if (filterSet == null) + { + filterSet = new HashSet(coreClrTypeCatalog.Values); + } + + if (filterSet.Contains(assemblyName.FullName)) + { + if (trace) + System.Console.WriteLine(" ++ Return null from Load override"); + return null; + } + return Resolve(this, assemblyName); } @@ -247,7 +264,8 @@ namespace System.Management.Automation // In this case, return null so that other Resolving event handlers can kick in to resolve the request. if (!isAssemblyFileFound || !isAssemblyFileMatching) { - System.Console.WriteLine(" -- Return null"); + if (trace) + System.Console.WriteLine(" -- Return null"); return null; } @@ -258,7 +276,8 @@ namespace System.Management.Automation { // Add the loaded assembly to the cache AssemblyCache.TryAdd(assemblyName.Name, asmLoaded); - System.Console.WriteLine(" ++ Load succeed: {0}", asmLoaded.FullName); + if (trace) + System.Console.WriteLine(" ++ Load succeed: {0}", asmLoaded.FullName); } } @@ -273,7 +292,8 @@ namespace System.Management.Automation internal Assembly LoadFrom(string assemblyPath) { ValidateAssemblyPath(assemblyPath, "assemblyPath"); - System.Console.WriteLine("*** LC1 *** LoadFrom {0}", assemblyPath); + if (trace) + System.Console.WriteLine("*** LC1 *** LoadFrom {0}", assemblyPath); Assembly asmLoaded; AssemblyName assemblyName = GetAssemblyName(assemblyPath); @@ -304,7 +324,8 @@ namespace System.Management.Automation { probingPaths.Add(parentPath); } - System.Console.WriteLine(" ++ LoadFrom succeed: {0}", asmLoaded.FullName); + if (trace) + System.Console.WriteLine(" ++ LoadFrom succeed: {0}", asmLoaded.FullName); } } From 5a29df50f11efc8506158b93f6729434c94097a4 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Tue, 28 Jun 2016 16:05:56 -0700 Subject: [PATCH 08/13] Re-enable assembly load handler in CompletionCompleters --- .../engine/CommandCompletion/CompletionCompleters.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 77c305ee7b..4200b10558 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -36,8 +36,7 @@ namespace System.Management.Automation static CompletionCompleters() { #if CORECLR - // Porting note: removed until we have full assembly loading solution - // ClrFacade.AddAssemblyLoadHandler(UpdateTypeCacheOnAssemblyLoad); + ClrFacade.AddAssemblyLoadHandler(UpdateTypeCacheOnAssemblyLoad); #else AppDomain.CurrentDomain.AssemblyLoad += UpdateTypeCacheOnAssemblyLoad; #endif From 2360d1ef466a7f873c1246d0a712b4b78e0aaf8f Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Tue, 28 Jun 2016 19:17:56 -0700 Subject: [PATCH 09/13] Filter runtime assemblies out of custom context --- .../CoreCLR/CorePsAssemblyLoadContext.cs | 19 ++++++++++++++----- src/powershell/Program.cs | 2 -- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs index dc4095257d..7ade29bbfc 100644 --- a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs @@ -8,6 +8,7 @@ using System.IO; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Runtime.InteropServices; using System.Reflection; using System.Reflection.Metadata; @@ -91,6 +92,9 @@ namespace System.Management.Automation trace = System.Environment.GetEnvironmentVariable("ALC_TRACE") == "1"; + if (trace) + Console.WriteLine(" == APPBASE == {0}", basePaths); + this.basePaths = basePaths.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < this.basePaths.Length; i++) { @@ -195,12 +199,17 @@ namespace System.Management.Automation if (trace) System.Console.WriteLine("== LC1 == Requesting: {0}", assemblyName.FullName); + if (filterSet == null) { - filterSet = new HashSet(coreClrTypeCatalog.Values); + // We exclude the assemblies included in the type catalog as there appears to be a + // bug in .NET with method resolution with system libraries are loaded by our + // context and not the default. We use the short name because some packages have + // inconsistent verions between reference and runtime assemblies. + filterSet = new HashSet(from x in coreClrTypeCatalog.Values select x.Substring(0, x.IndexOf(","))); } - - if (filterSet.Contains(assemblyName.FullName)) + + if (filterSet.Contains(assemblyName.Name)) { if (trace) System.Console.WriteLine(" ++ Return null from Load override"); @@ -463,7 +472,7 @@ namespace System.Management.Automation } /// - /// Set the profile optimization root on the approprite load context + /// Set the profile optimization root on the appropriate load context /// internal void SetProfileOptimizationRootImpl(string directoryPath) { @@ -472,7 +481,7 @@ namespace System.Management.Automation } /// - /// Start the profile optimization on the approprite load context + /// Start the profile optimization on the appropriate load context /// internal void StartProfileOptimizationImpl(string profile) { diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs index 01a958ec29..8b31e0fa2e 100644 --- a/src/powershell/Program.cs +++ b/src/powershell/Program.cs @@ -24,8 +24,6 @@ namespace Microsoft.PowerShell #if CORECLR // Open PowerShell has to set the ALC here, since we don't own the native host string appBase = System.IO.Path.GetDirectoryName(typeof(ManagedPSEntry).GetTypeInfo().Assembly.Location); - Console.WriteLine(" == APPBASE == {0}", appBase); - //Console.Read(); return (int)PowerShellAssemblyLoadContextInitializer. InitializeAndCallEntryMethod( appBase, From 4086644de7f44358ca2701694aad14626aaa8beb Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Tue, 28 Jun 2016 19:33:31 -0700 Subject: [PATCH 10/13] Disable JIT cache tests This test fails since we've disabled the JIT cache entirely. --- test/powershell/Base-Directory.Tests.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/powershell/Base-Directory.Tests.ps1 b/test/powershell/Base-Directory.Tests.ps1 index 7e517fa8c1..b25c9203e4 100644 --- a/test/powershell/Base-Directory.Tests.ps1 +++ b/test/powershell/Base-Directory.Tests.ps1 @@ -50,7 +50,8 @@ Describe "Configuration file locations" { & $powershell -noprofile { (Get-PSReadlineOption).HistorySavePath } | Should Be $expectedReadline } - It @ItArgs "JIT cache should be created correctly" { + # This feature (and thus test) has been disabled because of the AssemblyLoadContext scenario + It "JIT cache should be created correctly" -Skip { Remove-Item -ErrorAction SilentlyContinue $expectedCache & $powershell -noprofile { exit } $expectedCache | Should Exist @@ -105,7 +106,8 @@ Describe "Configuration file locations" { & $powershell -noprofile { (Get-PSReadlineOption).HistorySavePath } | Should Be $expected } - It @ItArgs "JIT cache should respect XDG_CACHE_HOME" { + # This feature (and thus test) has been disabled because of the AssemblyLoadContext scenario + It -Skip "JIT cache should respect XDG_CACHE_HOME" { $env:XDG_CACHE_HOME = $TestDrive $expected = [IO.Path]::Combine($TestDrive, "powershell", "StartupProfileData-NonInteractive") Remove-Item -ErrorAction SilentlyContinue $expected From 2e927d8155b49882756ae9016105c965c32dbff5 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Tue, 28 Jun 2016 19:56:47 -0700 Subject: [PATCH 11/13] Disable xUnit tests This is temporary. We need to rewrite the test context to ensure the AssemblyLoadContext is used properly, since the default can no longer be initialized. --- build.psm1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.psm1 b/build.psm1 index 3ffbf31885..a9f06c6284 100644 --- a/build.psm1 +++ b/build.psm1 @@ -367,6 +367,9 @@ function Start-PSPester { function Start-PSxUnit { [CmdletBinding()]param() + log "xUnit tests are currently disabled pending fixes due to API and AssemblyLoadContext changes - @andschwa" + return + if ($IsWindows) { throw "xUnit tests are only currently supported on Linux / OS X" } From 297eda9721d94206377d853d802865e7752ab5ef Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 28 Jun 2016 22:57:20 -0700 Subject: [PATCH 12/13] Filter Assembly.Load requests to let the default context load TPAs --- .../CoreCLR/CorePsAssemblyLoadContext.cs | 57 +++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs index 7ade29bbfc..5f48ed3bc1 100644 --- a/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs +++ b/src/Microsoft.PowerShell.CoreCLR.AssemblyLoadContext/CoreCLR/CorePsAssemblyLoadContext.cs @@ -8,7 +8,6 @@ using System.IO; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Runtime.InteropServices; using System.Reflection; using System.Reflection.Metadata; @@ -90,11 +89,6 @@ namespace System.Management.Automation throw new ArgumentNullException("basePaths"); } - trace = System.Environment.GetEnvironmentVariable("ALC_TRACE") == "1"; - - if (trace) - Console.WriteLine(" == APPBASE == {0}", basePaths); - this.basePaths = basePaths.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < this.basePaths.Length; i++) { @@ -119,9 +113,21 @@ namespace System.Management.Automation // LAST: Handle useResolvingHandlerOnly flag this.useResolvingHandlerOnly = useResolvingHandlerOnly; - this.activeLoadContext = useResolvingHandlerOnly ? AssemblyLoadContext.Default : this; + this.activeLoadContext = useResolvingHandlerOnly ? Default : this; if (useResolvingHandlerOnly) - AssemblyLoadContext.Default.Resolving += Resolve; + { + Default.Resolving += Resolve; + } + else + { + var tempSet = new HashSet(coreClrTypeCatalog.Values, StringComparer.OrdinalIgnoreCase); + tpaSet = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (string tpa in tempSet) + { + string shortName = tpa.Substring(0, tpa.IndexOf(',')); + tpaSet.Add(shortName); + } + } } #endregion Constructor @@ -142,9 +148,8 @@ namespace System.Management.Automation // - Key: namespace qualified type name (FullName) // - Value: strong name of the TPA that contains the type represented by Key. private readonly Dictionary coreClrTypeCatalog; + private readonly HashSet tpaSet; private readonly string[] extensions = new string[] { ".ni.dll", ".dll" }; - private readonly bool trace; - private HashSet filterSet; /// /// Assembly cache accross the AppDomain @@ -197,24 +202,12 @@ namespace System.Management.Automation if (useResolvingHandlerOnly) throw new NotSupportedException(UseResolvingEventHandlerOnly); - if (trace) - System.Console.WriteLine("== LC1 == Requesting: {0}", assemblyName.FullName); - - if (filterSet == null) - { - // We exclude the assemblies included in the type catalog as there appears to be a - // bug in .NET with method resolution with system libraries are loaded by our - // context and not the default. We use the short name because some packages have - // inconsistent verions between reference and runtime assemblies. - filterSet = new HashSet(from x in coreClrTypeCatalog.Values select x.Substring(0, x.IndexOf(","))); - } - - if (filterSet.Contains(assemblyName.Name)) - { - if (trace) - System.Console.WriteLine(" ++ Return null from Load override"); + // We let the default context load the assemblies included in the type catalog as there + // appears to be a bug in .NET with method resolution with system libraries loaded by our + // context and not the default. We use the short name because some packages have inconsistent + // verions between reference and runtime assemblies. + if (tpaSet.Contains(assemblyName.Name)) return null; - } return Resolve(this, assemblyName); } @@ -273,8 +266,6 @@ namespace System.Management.Automation // In this case, return null so that other Resolving event handlers can kick in to resolve the request. if (!isAssemblyFileFound || !isAssemblyFileMatching) { - if (trace) - System.Console.WriteLine(" -- Return null"); return null; } @@ -285,8 +276,6 @@ namespace System.Management.Automation { // Add the loaded assembly to the cache AssemblyCache.TryAdd(assemblyName.Name, asmLoaded); - if (trace) - System.Console.WriteLine(" ++ Load succeed: {0}", asmLoaded.FullName); } } @@ -301,8 +290,6 @@ namespace System.Management.Automation internal Assembly LoadFrom(string assemblyPath) { ValidateAssemblyPath(assemblyPath, "assemblyPath"); - if (trace) - System.Console.WriteLine("*** LC1 *** LoadFrom {0}", assemblyPath); Assembly asmLoaded; AssemblyName assemblyName = GetAssemblyName(assemblyPath); @@ -333,8 +320,6 @@ namespace System.Management.Automation { probingPaths.Add(parentPath); } - if (trace) - System.Console.WriteLine(" ++ LoadFrom succeed: {0}", asmLoaded.FullName); } } @@ -792,4 +777,4 @@ namespace System.Management.Automation } } -#endif +#endif \ No newline at end of file From fec2cb8b744da141b536c5c0995bcb13794290a8 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Wed, 29 Jun 2016 09:52:32 -0700 Subject: [PATCH 13/13] Use publish instead of build on Travis AppVeyor also does this, and it's the scenario our packages ship, so it's what we should test. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5b02cc149b..0c65a78a81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ before_install: - git submodule update --init -- src/Modules/Pester src/libpsl-native/test/googletest - ./tools/download.sh script: - - ulimit -n 4096; powershell -c "Import-Module ./build.psm1; Start-PSBootstrap; Start-PSBuild; Start-PSxUnit; Start-PSPester" + - ulimit -n 4096; powershell -c "Import-Module ./build.psm1; Start-PSBootstrap; Start-PSBuild -Publish; Start-PSPester; Start-PSxUnit" notifications: slack: secure: sKYd4n61+ZFzGZuWGUl8V1kN0NM16wRVOFVlNhlFCwnkrEsKROb++EvXf5uwnKuzxkhEjvPWO+UFgeshQDoR93y4s5YLfhC5JupK4nUzjPzWs208KTrh8u/x9MY8X6Ojxi85EEAiku5GzMoMlkucSStZUYwbIfnelzqdw8uoRwmm2MW4XCPwsuEuDUVghyiva0Mdx1G6MopCrK8T96WywJXT3chhfZQgVt+sQCBt9g+2kjDaObKrzG0P07IVK43ZpDgnu6AoxlyBzIx9mJH2Oa/tki3/kTO72Wcp3ps3qvmiStADamzVKR9p1VlWCLWAd6VOehxuByCGEyujpzk135Wud2DZYO+8LD6inZVhFe3Wt5pCU9BDXZppiATfMCqgXEH7nK54pEn79yHcjthRJ2+Z9ot7As2fu3RSBmTAi8nRP0fxRyX/jctR3S6P0qt0y1ynx9nzBfhmhPQW0PMVazWS/nruQIvK/3iiYXjZxM5bBwIvabmwV00EYeTdbL6ufXWNgQcG1ZWkDsi2I3vst/ytUbHwaFYg83bXWpxg9DCzJeWLVUvE5/3NfBxRAuCTot/fgTEA9IYScvrlL7Q/bT0cOt0vEM98MPf1UO+WP85uxhsRgHtwDEo+jMaL6ZFkPhlV6mmmED4NdY2//a571cLNXdnuMAze5O3TWGBG53g=