diff --git a/src/Compilers/Shared/GlobalAssemblyCacheHelpers/ClrGlobalAssemblyCache.cs b/src/Compilers/Shared/GlobalAssemblyCacheHelpers/ClrGlobalAssemblyCache.cs new file mode 100644 index 0000000000000..49eebbe538431 --- /dev/null +++ b/src/Compilers/Shared/GlobalAssemblyCacheHelpers/ClrGlobalAssemblyCache.cs @@ -0,0 +1,251 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + using ASM_CACHE = GlobalAssemblyCacheLocation.ASM_CACHE; + + /// + /// Provides APIs to enumerate and look up assemblies stored in the Global Assembly Cache. + /// + internal sealed class ClrGlobalAssemblyCache : GlobalAssemblyCache + { + #region Interop + + private const int MAX_PATH = 260; + + [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("21b8916c-f28e-11d2-a473-00c04f8ef448")] + private interface IAssemblyEnum + { + [PreserveSig] + int GetNextAssembly(out FusionAssemblyIdentity.IApplicationContext ppAppCtx, out FusionAssemblyIdentity.IAssemblyName ppName, uint dwFlags); + + [PreserveSig] + int Reset(); + + [PreserveSig] + int Clone(out IAssemblyEnum ppEnum); + } + + [ComImport, Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IAssemblyCache + { + void UninstallAssembly(); + + void QueryAssemblyInfo(uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, ref ASSEMBLY_INFO pAsmInfo); + + void CreateAssemblyCacheItem(); + void CreateAssemblyScavenger(); + void InstallAssembly(); + } + + [StructLayout(LayoutKind.Sequential)] + private unsafe struct ASSEMBLY_INFO + { + public uint cbAssemblyInfo; + public readonly uint dwAssemblyFlags; + public readonly ulong uliAssemblySizeInKB; + public char* pszCurrentAssemblyPathBuf; + public uint cchBuf; + } + + [DllImport("clr", PreserveSig = true)] + private static extern int CreateAssemblyEnum(out IAssemblyEnum ppEnum, FusionAssemblyIdentity.IApplicationContext pAppCtx, FusionAssemblyIdentity.IAssemblyName pName, ASM_CACHE dwFlags, IntPtr pvReserved); + + [DllImport("clr", PreserveSig = false)] + private static extern void CreateAssemblyCache(out IAssemblyCache ppAsmCache, uint dwReserved); + + #endregion + + /// + /// Enumerates assemblies in the GAC returning those that match given partial name and + /// architecture. + /// + /// Optional partial name. + /// Optional architecture filter. + public override IEnumerable GetAssemblyIdentities(AssemblyName partialName, ImmutableArray architectureFilter = default(ImmutableArray)) + { + return GetAssemblyIdentities(FusionAssemblyIdentity.ToAssemblyNameObject(partialName), architectureFilter); + } + + /// + /// Enumerates assemblies in the GAC returning those that match given partial name and + /// architecture. + /// + /// The optional partial name. + /// The optional architecture filter. + public override IEnumerable GetAssemblyIdentities(string partialName = null, ImmutableArray architectureFilter = default(ImmutableArray)) + { + FusionAssemblyIdentity.IAssemblyName nameObj; + if (partialName != null) + { + nameObj = FusionAssemblyIdentity.ToAssemblyNameObject(partialName); + if (nameObj == null) + { + return SpecializedCollections.EmptyEnumerable(); + } + } + else + { + nameObj = null; + } + + return GetAssemblyIdentities(nameObj, architectureFilter); + } + + /// + /// Enumerates assemblies in the GAC returning their simple names. + /// + /// Optional architecture filter. + /// Unique simple names of GAC assemblies. + public override IEnumerable GetAssemblySimpleNames(ImmutableArray architectureFilter = default(ImmutableArray)) + { + var q = from nameObject in GetAssemblyObjects(partialNameFilter: null, architectureFilter: architectureFilter) + select FusionAssemblyIdentity.GetName(nameObject); + return q.Distinct(); + } + + private static IEnumerable GetAssemblyIdentities( + FusionAssemblyIdentity.IAssemblyName partialName, + ImmutableArray architectureFilter) + { + return from nameObject in GetAssemblyObjects(partialName, architectureFilter) + select FusionAssemblyIdentity.ToAssemblyIdentity(nameObject); + } + + private const int S_OK = 0; + private const int S_FALSE = 1; + + // Internal for testing. + internal static IEnumerable GetAssemblyObjects( + FusionAssemblyIdentity.IAssemblyName partialNameFilter, + ImmutableArray architectureFilter) + { + IAssemblyEnum enumerator; + FusionAssemblyIdentity.IApplicationContext applicationContext = null; + + int hr = CreateAssemblyEnum(out enumerator, applicationContext, partialNameFilter, ASM_CACHE.GAC, IntPtr.Zero); + if (hr == S_FALSE) + { + // no assembly found + yield break; + } + else if (hr != S_OK) + { + Exception e = Marshal.GetExceptionForHR(hr); + if (e is FileNotFoundException) + { + // invalid assembly name: + yield break; + } + else if (e != null) + { + throw e; + } + else + { + // for some reason it might happen that CreateAssemblyEnum returns non-zero HR that doesn't correspond to any exception: +#if SCRIPTING + throw new ArgumentException(Microsoft.CodeAnalysis.Scripting.ScriptingResources.InvalidAssemblyName); +#else + throw new ArgumentException(Microsoft.CodeAnalysis.WorkspaceDesktopResources.InvalidAssemblyName); +#endif + } + } + + while (true) + { + FusionAssemblyIdentity.IAssemblyName nameObject; + + hr = enumerator.GetNextAssembly(out applicationContext, out nameObject, 0); + if (hr != 0) + { + if (hr < 0) + { + Marshal.ThrowExceptionForHR(hr); + } + + break; + } + + if (!architectureFilter.IsDefault) + { + var assemblyArchitecture = FusionAssemblyIdentity.GetProcessorArchitecture(nameObject); + if (!architectureFilter.Contains(assemblyArchitecture)) + { + continue; + } + } + + yield return nameObject; + } + } + + public override AssemblyIdentity ResolvePartialName( + string displayName, + out string location, + ImmutableArray architectureFilter, + CultureInfo preferredCulture) + { + if (displayName == null) + { + throw new ArgumentNullException(nameof(displayName)); + } + + location = null; + FusionAssemblyIdentity.IAssemblyName nameObject = FusionAssemblyIdentity.ToAssemblyNameObject(displayName); + if (nameObject == null) + { + return null; + } + + var candidates = GetAssemblyObjects(nameObject, architectureFilter); + string cultureName = (preferredCulture != null && !preferredCulture.IsNeutralCulture) ? preferredCulture.Name : null; + + var bestMatch = FusionAssemblyIdentity.GetBestMatch(candidates, cultureName); + if (bestMatch == null) + { + return null; + } + + location = GetAssemblyLocation(bestMatch); + return FusionAssemblyIdentity.ToAssemblyIdentity(bestMatch); + } + + internal static unsafe string GetAssemblyLocation(FusionAssemblyIdentity.IAssemblyName nameObject) + { + // NAME | VERSION | CULTURE | PUBLIC_KEY_TOKEN | RETARGET | PROCESSORARCHITECTURE + string fullName = FusionAssemblyIdentity.GetDisplayName(nameObject, FusionAssemblyIdentity.ASM_DISPLAYF.FULL); + + fixed (char* p = new char[MAX_PATH]) + { + ASSEMBLY_INFO info = new ASSEMBLY_INFO + { + cbAssemblyInfo = (uint)Marshal.SizeOf(), + pszCurrentAssemblyPathBuf = p, + cchBuf = MAX_PATH + }; + + IAssemblyCache assemblyCacheObject; + CreateAssemblyCache(out assemblyCacheObject, 0); + assemblyCacheObject.QueryAssemblyInfo(0, fullName, ref info); + Debug.Assert(info.pszCurrentAssemblyPathBuf != null); + Debug.Assert(info.pszCurrentAssemblyPathBuf[info.cchBuf - 1] == '\0'); + + var result = Marshal.PtrToStringUni((IntPtr)info.pszCurrentAssemblyPathBuf, (int)info.cchBuf - 1); + Debug.Assert(result.IndexOf('\0') == -1); + return result; + } + } + } +} diff --git a/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GacFileResolver.cs b/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GacFileResolver.cs index 1624aedcfa4e1..27010ecac5315 100644 --- a/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GacFileResolver.cs +++ b/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GacFileResolver.cs @@ -61,7 +61,7 @@ public GacFileResolver( public string Resolve(string assemblyName) { string path; - GlobalAssemblyCache.ResolvePartialName(assemblyName, out path, Architectures, this.PreferredCulture); + GlobalAssemblyCache.Instance.ResolvePartialName(assemblyName, out path, Architectures, this.PreferredCulture); return File.Exists(path) ? path : null; } diff --git a/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GlobalAssemblyCache.cs b/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GlobalAssemblyCache.cs index 8654849aaf336..2f9a44adff86e 100644 --- a/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GlobalAssemblyCache.cs +++ b/src/Compilers/Shared/GlobalAssemblyCacheHelpers/GlobalAssemblyCache.cs @@ -3,87 +3,44 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { - using static GlobalAssemblyCacheLocation; - /// /// Provides APIs to enumerate and look up assemblies stored in the Global Assembly Cache. /// - internal static class GlobalAssemblyCache + internal abstract class GlobalAssemblyCache { + internal static readonly GlobalAssemblyCache Instance = CreateInstance(); + + private static GlobalAssemblyCache CreateInstance() + { + if (Type.GetType("Mono.Runtime") != null) + { + return new MonoGlobalAssemblyCache(); + } + else + { + return new ClrGlobalAssemblyCache(); + } + } + /// - /// Represents the current Processor architecture + /// Represents the current Processor architecture. /// public static readonly ImmutableArray CurrentArchitectures = (IntPtr.Size == 4) ? ImmutableArray.Create(ProcessorArchitecture.None, ProcessorArchitecture.MSIL, ProcessorArchitecture.X86) : ImmutableArray.Create(ProcessorArchitecture.None, ProcessorArchitecture.MSIL, ProcessorArchitecture.Amd64); - #region Interop - - private const int MAX_PATH = 260; - - [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("21b8916c-f28e-11d2-a473-00c04f8ef448")] - private interface IAssemblyEnum - { - [PreserveSig] - int GetNextAssembly(out FusionAssemblyIdentity.IApplicationContext ppAppCtx, out FusionAssemblyIdentity.IAssemblyName ppName, uint dwFlags); - - [PreserveSig] - int Reset(); - - [PreserveSig] - int Clone(out IAssemblyEnum ppEnum); - } - - [ComImport, Guid("e707dcde-d1cd-11d2-bab9-00c04f8eceae"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - private interface IAssemblyCache - { - void UninstallAssembly(); - - void QueryAssemblyInfo(uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, ref ASSEMBLY_INFO pAsmInfo); - - void CreateAssemblyCacheItem(); - void CreateAssemblyScavenger(); - void InstallAssembly(); - } - - [StructLayout(LayoutKind.Sequential)] - private unsafe struct ASSEMBLY_INFO - { - public uint cbAssemblyInfo; - public readonly uint dwAssemblyFlags; - public readonly ulong uliAssemblySizeInKB; - public char* pszCurrentAssemblyPathBuf; - public uint cchBuf; - } - - [DllImport("clr", PreserveSig = true)] - private static extern int CreateAssemblyEnum(out IAssemblyEnum ppEnum, FusionAssemblyIdentity.IApplicationContext pAppCtx, FusionAssemblyIdentity.IAssemblyName pName, ASM_CACHE dwFlags, IntPtr pvReserved); - - [DllImport("clr", PreserveSig = false)] - private static extern void CreateAssemblyCache(out IAssemblyCache ppAsmCache, uint dwReserved); - - #endregion - /// /// Enumerates assemblies in the GAC returning those that match given partial name and /// architecture. /// /// Optional partial name. /// Optional architecture filter. - public static IEnumerable GetAssemblyIdentities(AssemblyName partialName, ImmutableArray architectureFilter = default(ImmutableArray)) - { - return GetAssemblyIdentities(FusionAssemblyIdentity.ToAssemblyNameObject(partialName), architectureFilter); - } + public abstract IEnumerable GetAssemblyIdentities(AssemblyName partialName, ImmutableArray architectureFilter = default(ImmutableArray)); /// /// Enumerates assemblies in the GAC returning those that match given partial name and @@ -91,112 +48,14 @@ private unsafe struct ASSEMBLY_INFO /// /// The optional partial name. /// The optional architecture filter. - public static IEnumerable GetAssemblyIdentities(string partialName = null, ImmutableArray architectureFilter = default(ImmutableArray)) - { - FusionAssemblyIdentity.IAssemblyName nameObj; - if (partialName != null) - { - nameObj = FusionAssemblyIdentity.ToAssemblyNameObject(partialName); - if (nameObj == null) - { - return SpecializedCollections.EmptyEnumerable(); - } - } - else - { - nameObj = null; - } - - return GetAssemblyIdentities(nameObj, architectureFilter); - } + public abstract IEnumerable GetAssemblyIdentities(string partialName = null, ImmutableArray architectureFilter = default(ImmutableArray)); /// /// Enumerates assemblies in the GAC returning their simple names. /// /// Optional architecture filter. /// Unique simple names of GAC assemblies. - public static IEnumerable GetAssemblySimpleNames(ImmutableArray architectureFilter = default(ImmutableArray)) - { - var q = from nameObject in GetAssemblyObjects(partialNameFilter: null, architectureFilter: architectureFilter) - select FusionAssemblyIdentity.GetName(nameObject); - return q.Distinct(); - } - - private static IEnumerable GetAssemblyIdentities( - FusionAssemblyIdentity.IAssemblyName partialName, - ImmutableArray architectureFilter) - { - return from nameObject in GetAssemblyObjects(partialName, architectureFilter) - select FusionAssemblyIdentity.ToAssemblyIdentity(nameObject); - } - - private const int S_OK = 0; - private const int S_FALSE = 1; - - // Internal for testing. - internal static IEnumerable GetAssemblyObjects( - FusionAssemblyIdentity.IAssemblyName partialNameFilter, - ImmutableArray architectureFilter) - { - IAssemblyEnum enumerator; - FusionAssemblyIdentity.IApplicationContext applicationContext = null; - - int hr = CreateAssemblyEnum(out enumerator, applicationContext, partialNameFilter, ASM_CACHE.GAC, IntPtr.Zero); - if (hr == S_FALSE) - { - // no assembly found - yield break; - } - else if (hr != S_OK) - { - Exception e = Marshal.GetExceptionForHR(hr); - if (e is FileNotFoundException) - { - // invalid assembly name: - yield break; - } - else if (e != null) - { - throw e; - } - else - { - // for some reason it might happen that CreateAssemblyEnum returns non-zero HR that doesn't correspond to any exception: -#if SCRIPTING - throw new ArgumentException(Microsoft.CodeAnalysis.Scripting.ScriptingResources.InvalidAssemblyName); -#else - throw new ArgumentException(Microsoft.CodeAnalysis.WorkspaceDesktopResources.InvalidAssemblyName); -#endif - } - } - - while (true) - { - FusionAssemblyIdentity.IAssemblyName nameObject; - - hr = enumerator.GetNextAssembly(out applicationContext, out nameObject, 0); - if (hr != 0) - { - if (hr < 0) - { - Marshal.ThrowExceptionForHR(hr); - } - - break; - } - - if (!architectureFilter.IsDefault) - { - var assemblyArchitecture = FusionAssemblyIdentity.GetProcessorArchitecture(nameObject); - if (!architectureFilter.Contains(assemblyArchitecture)) - { - continue; - } - } - - yield return nameObject; - } - } + public abstract IEnumerable GetAssemblySimpleNames(ImmutableArray architectureFilter = default(ImmutableArray)); /// /// Looks up specified partial assembly name in the GAC and returns the best matching . @@ -206,13 +65,13 @@ private static IEnumerable GetAssemblyIdentities( /// The optional preferred culture information /// An assembly identity or null, if can't be resolved. /// is null. - public static AssemblyIdentity ResolvePartialName( + public AssemblyIdentity ResolvePartialName( string displayName, ImmutableArray architectureFilter = default(ImmutableArray), CultureInfo preferredCulture = null) { string location; - return ResolvePartialName(displayName, architectureFilter, preferredCulture, out location, resolveLocation: false); + return ResolvePartialName(displayName, out location, architectureFilter, preferredCulture); } /// @@ -224,75 +83,10 @@ public static AssemblyIdentity ResolvePartialName( /// The optional preferred culture information /// An assembly identity or null, if can't be resolved. /// is null. - public static AssemblyIdentity ResolvePartialName( + public abstract AssemblyIdentity ResolvePartialName( string displayName, out string location, ImmutableArray architectureFilter = default(ImmutableArray), - CultureInfo preferredCulture = null) - { - return ResolvePartialName(displayName, architectureFilter, preferredCulture, out location, resolveLocation: true); - } - - private static AssemblyIdentity ResolvePartialName( - string displayName, - ImmutableArray architectureFilter, - CultureInfo preferredCulture, - out string location, - bool resolveLocation) - { - if (displayName == null) - { - throw new ArgumentNullException(nameof(displayName)); - } - - location = null; - FusionAssemblyIdentity.IAssemblyName nameObject = FusionAssemblyIdentity.ToAssemblyNameObject(displayName); - if (nameObject == null) - { - return null; - } - - var candidates = GetAssemblyObjects(nameObject, architectureFilter); - string cultureName = (preferredCulture != null && !preferredCulture.IsNeutralCulture) ? preferredCulture.Name : null; - - var bestMatch = FusionAssemblyIdentity.GetBestMatch(candidates, cultureName); - if (bestMatch == null) - { - return null; - } - - if (resolveLocation) - { - location = GetAssemblyLocation(bestMatch); - } - - return FusionAssemblyIdentity.ToAssemblyIdentity(bestMatch); - } - - internal static unsafe string GetAssemblyLocation(FusionAssemblyIdentity.IAssemblyName nameObject) - { - // NAME | VERSION | CULTURE | PUBLIC_KEY_TOKEN | RETARGET | PROCESSORARCHITECTURE - string fullName = FusionAssemblyIdentity.GetDisplayName(nameObject, FusionAssemblyIdentity.ASM_DISPLAYF.FULL); - - fixed (char* p = new char[MAX_PATH]) - { - ASSEMBLY_INFO info = new ASSEMBLY_INFO - { - cbAssemblyInfo = (uint)Marshal.SizeOf(), - pszCurrentAssemblyPathBuf = p, - cchBuf = MAX_PATH - }; - - IAssemblyCache assemblyCacheObject; - CreateAssemblyCache(out assemblyCacheObject, 0); - assemblyCacheObject.QueryAssemblyInfo(0, fullName, ref info); - Debug.Assert(info.pszCurrentAssemblyPathBuf != null); - Debug.Assert(info.pszCurrentAssemblyPathBuf[info.cchBuf - 1] == '\0'); - - var result = Marshal.PtrToStringUni((IntPtr)info.pszCurrentAssemblyPathBuf, (int)info.cchBuf - 1); - Debug.Assert(result.IndexOf('\0') == -1); - return result; - } - } + CultureInfo preferredCulture = null); } } diff --git a/src/Compilers/Shared/GlobalAssemblyCacheHelpers/MonoGlobalAssemblyCache.cs b/src/Compilers/Shared/GlobalAssemblyCacheHelpers/MonoGlobalAssemblyCache.cs new file mode 100644 index 0000000000000..33cf016c7ddff --- /dev/null +++ b/src/Compilers/Shared/GlobalAssemblyCacheHelpers/MonoGlobalAssemblyCache.cs @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis +{ + /// + /// Provides APIs to enumerate and look up assemblies stored in the Global Assembly Cache. + /// + internal sealed class MonoGlobalAssemblyCache : GlobalAssemblyCache + { + public static readonly ImmutableArray RootLocations; + + static MonoGlobalAssemblyCache() + { + RootLocations = ImmutableArray.Create(GetMonoCachePath()); + } + + private static string GetMonoCachePath() + { + string file = CorLightup.Desktop.GetAssemblyLocation(typeof(Uri).GetTypeInfo().Assembly); + return Directory.GetParent(Path.GetDirectoryName(file)).Parent.FullName; + } + + private static IEnumerable GetCorlibPaths(Version version) + { + string corlibPath = CorLightup.Desktop.GetAssemblyLocation(typeof(object).GetTypeInfo().Assembly); + var corlibParentDir = Directory.GetParent(corlibPath).Parent; + + var corlibPaths = new List(); + + foreach (var corlibDir in corlibParentDir.GetDirectories()) + { + var path = Path.Combine(corlibDir.FullName, "mscorlib.dll"); + if (!File.Exists(path)) + { + continue; + } + + var name = new AssemblyName(path); + if (version != null && name.Version != version) + { + continue; + } + + corlibPaths.Add(path); + } + + return corlibPaths; + } + + private static IEnumerable GetGacAssemblyPaths(string gacPath, string name, Version version, string publicKeyToken) + { + if (version != null && publicKeyToken != null) + { + yield return Path.Combine(gacPath, name, version + "__" + publicKeyToken, name + ".dll"); + yield break; + } + + var gacAssemblyRootDir = new DirectoryInfo(Path.Combine(gacPath, name)); + if (!gacAssemblyRootDir.Exists) + { + yield break; + } + + foreach (var assemblyDir in gacAssemblyRootDir.GetDirectories()) + { + if (version != null && !assemblyDir.Name.StartsWith(version.ToString(), StringComparison.Ordinal)) + { + continue; + } + + if (publicKeyToken != null && !assemblyDir.Name.EndsWith(publicKeyToken, StringComparison.Ordinal)) + { + continue; + } + + var assemblyPath = Path.Combine(assemblyDir.ToString(), name + ".dll"); + if (File.Exists(assemblyPath)) + { + yield return assemblyPath; + } + } + } + + private static IEnumerable> GetAssemblyIdentitiesAndPaths(AssemblyName name, ImmutableArray architectureFilter) + { + if (name == null) + { + return GetAssemblyIdentitiesAndPaths(null, null, null, architectureFilter); + } + + string publicKeyToken = null; + if (name.GetPublicKeyToken() != null) + { + var sb = new StringBuilder(); + foreach (var b in name.GetPublicKeyToken()) + { + sb.AppendFormat("{0:x2}", b); + } + + publicKeyToken = sb.ToString(); + } + + return GetAssemblyIdentitiesAndPaths(name.Name, name.Version, publicKeyToken, architectureFilter); + } + + private static IEnumerable> GetAssemblyIdentitiesAndPaths(string name, Version version, string publicKeyToken, ImmutableArray architectureFilter) + { + foreach (string gacPath in RootLocations) + { + var assemblyPaths = (name == "mscorlib") ? + GetCorlibPaths(version) : + GetGacAssemblyPaths(gacPath, name, version, publicKeyToken); + + foreach (var assemblyPath in assemblyPaths) + { + if (!File.Exists(assemblyPath)) + { + continue; + } + + var gacAssemblyName = new AssemblyName(assemblyPath); + + if (gacAssemblyName.ProcessorArchitecture != ProcessorArchitecture.None && + architectureFilter != default(ImmutableArray) && + architectureFilter.Length > 0 && + !architectureFilter.Contains(gacAssemblyName.ProcessorArchitecture)) + { + continue; + } + + var assemblyIdentity = new AssemblyIdentity( + gacAssemblyName.Name, + gacAssemblyName.Version, + gacAssemblyName.CultureName, + ImmutableArray.Create(gacAssemblyName.GetPublicKeyToken())); + + yield return new Tuple(assemblyIdentity, assemblyPath); + } + } + } + + public override IEnumerable GetAssemblyIdentities(AssemblyName partialName, ImmutableArray architectureFilter = default(ImmutableArray)) + { + return GetAssemblyIdentitiesAndPaths(partialName, architectureFilter).Select(identityAndPath => identityAndPath.Item1); + } + + public override IEnumerable GetAssemblyIdentities(string partialName = null, ImmutableArray architectureFilter = default(ImmutableArray)) + { + AssemblyName name; + try + { + name = (partialName == null) ? null : new AssemblyName(partialName); + } + catch + { + return SpecializedCollections.EmptyEnumerable(); + } + + return GetAssemblyIdentities(name, architectureFilter); + } + + public override IEnumerable GetAssemblySimpleNames(ImmutableArray architectureFilter = default(ImmutableArray)) + { + return GetAssemblyIdentitiesAndPaths(name: null, version: null, publicKeyToken: null, architectureFilter: architectureFilter). + Select(identityAndPath => identityAndPath.Item1.Name).Distinct(); + } + + public override AssemblyIdentity ResolvePartialName( + string displayName, + out string location, + ImmutableArray architectureFilter, + CultureInfo preferredCulture) + { + if (displayName == null) + { + throw new ArgumentNullException(nameof(displayName)); + } + + string cultureName = (preferredCulture != null && !preferredCulture.IsNeutralCulture) ? preferredCulture.Name : null; + + var assemblyName = new AssemblyName(displayName); + AssemblyIdentity assemblyIdentity = null; + + location = null; + bool isBestMatch = false; + + foreach (var identityAndPath in GetAssemblyIdentitiesAndPaths(assemblyName, architectureFilter)) + { + var assemblyPath = identityAndPath.Item2; + + if (!File.Exists(assemblyPath)) + { + continue; + } + + var gacAssemblyName = new AssemblyName(assemblyPath); + + isBestMatch = cultureName == null || gacAssemblyName.CultureName == cultureName; + bool isBetterMatch = location == null || isBestMatch; + + if (isBetterMatch) + { + location = assemblyPath; + assemblyIdentity = identityAndPath.Item1; + } + + if (isBestMatch) + { + break; + } + } + + return assemblyIdentity; + } + } +} \ No newline at end of file diff --git a/src/Interactive/EditorFeatures/Core/Completion/GlobalAssemblyCacheCompletionHelper.cs b/src/Interactive/EditorFeatures/Core/Completion/GlobalAssemblyCacheCompletionHelper.cs index 4f4afae311edc..f6bc1362a4791 100644 --- a/src/Interactive/EditorFeatures/Core/Completion/GlobalAssemblyCacheCompletionHelper.cs +++ b/src/Interactive/EditorFeatures/Core/Completion/GlobalAssemblyCacheCompletionHelper.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Editor.Completion.FileSystem internal sealed class GlobalAssemblyCacheCompletionHelper { private static readonly Lazy> s_lazyAssemblySimpleNames = - new Lazy>(() => GlobalAssemblyCache.GetAssemblySimpleNames().ToList()); + new Lazy>(() => GlobalAssemblyCache.Instance.GetAssemblySimpleNames().ToList()); private readonly CompletionListProvider _completionProvider; private readonly TextSpan _textChangeSpan; private readonly CompletionItemRules _itemRules; @@ -54,7 +54,7 @@ private IEnumerable GetCompletionsWorker(string pathSoFar) select new CompletionItem( _completionProvider, displayName, _textChangeSpan, - descriptionFactory: c => Task.FromResult(GlobalAssemblyCache.ResolvePartialName(displayName).GetDisplayName().ToSymbolDisplayParts()), + descriptionFactory: c => Task.FromResult(GlobalAssemblyCache.Instance.ResolvePartialName(displayName).GetDisplayName().ToSymbolDisplayParts()), glyph: Glyph.Assembly, rules: _itemRules); } @@ -62,7 +62,7 @@ private IEnumerable GetCompletionsWorker(string pathSoFar) private IEnumerable GetAssemblyIdentities(string pathSoFar) { - return IOUtilities.PerformIO(() => GlobalAssemblyCache.GetAssemblyIdentities(pathSoFar), + return IOUtilities.PerformIO(() => GlobalAssemblyCache.Instance.GetAssemblyIdentities(pathSoFar), SpecializedCollections.EmptyEnumerable()); } } diff --git a/src/Scripting/CSharpTest.Desktop/InteractiveSessionTests.cs b/src/Scripting/CSharpTest.Desktop/InteractiveSessionTests.cs index 780a0b18c0fb2..46f98ed239f58 100644 --- a/src/Scripting/CSharpTest.Desktop/InteractiveSessionTests.cs +++ b/src/Scripting/CSharpTest.Desktop/InteractiveSessionTests.cs @@ -233,8 +233,8 @@ public void References2() private static Lazy IsSystemV2AndV4Available = new Lazy(() => { string path; - return GlobalAssemblyCache.ResolvePartialName("System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", out path) != null && - GlobalAssemblyCache.ResolvePartialName("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", out path) != null; + return GlobalAssemblyCache.Instance.ResolvePartialName("System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", out path) != null && + GlobalAssemblyCache.Instance.ResolvePartialName("System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", out path) != null; }); [Fact] diff --git a/src/Scripting/Core/Scripting.csproj b/src/Scripting/Core/Scripting.csproj index 2ea92c0dfe28c..9462438a752ca 100644 --- a/src/Scripting/Core/Scripting.csproj +++ b/src/Scripting/Core/Scripting.csproj @@ -48,6 +48,9 @@ Resolvers\RelativePathResolver.cs + + Resolvers\ClrGlobalAssemblyCache.cs + Resolvers\FusionAssemblyIdentity.cs @@ -60,6 +63,9 @@ Resolvers\GlobalAssemblyCache.Location.cs + + Resolvers\MonoGlobalAssemblyCache.cs + @@ -144,4 +150,4 @@ - + \ No newline at end of file diff --git a/src/Scripting/CoreTest.Desktop/GlobalAssemblyCacheTests.cs b/src/Scripting/CoreTest.Desktop/GlobalAssemblyCacheTests.cs index 0bf0e39dc8e42..7eb250b8e1642 100644 --- a/src/Scripting/CoreTest.Desktop/GlobalAssemblyCacheTests.cs +++ b/src/Scripting/CoreTest.Desktop/GlobalAssemblyCacheTests.cs @@ -4,42 +4,43 @@ using System.IO; using System.Linq; using System.Reflection; -using Microsoft.CodeAnalysis.Text; using Xunit; using System.Collections.Immutable; -using Microsoft.CodeAnalysis.Scripting.Hosting; +using Roslyn.Test.Utilities; namespace Microsoft.CodeAnalysis.UnitTests { public class GlobalAssemblyCacheTests { - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/6179")] + [MonoOnlyFact("https://github.com/dotnet/roslyn/issues/6179")] public void GetAssemblyIdentities() { + var gac = GlobalAssemblyCache.Instance; + AssemblyIdentity[] names; - names = GlobalAssemblyCache.GetAssemblyIdentities(new AssemblyName("mscorlib")).ToArray(); + names = gac.GetAssemblyIdentities(new AssemblyName("mscorlib")).ToArray(); Assert.True(names.Length >= 1, "At least 1 mscorlib"); foreach (var name in names) { Assert.Equal("mscorlib", name.Name); } - names = GlobalAssemblyCache.GetAssemblyIdentities(new AssemblyName("mscorlib"), ImmutableArray.Create(ProcessorArchitecture.MSIL, ProcessorArchitecture.X86)).ToArray(); + names = gac.GetAssemblyIdentities(new AssemblyName("mscorlib"), ImmutableArray.Create(ProcessorArchitecture.MSIL, ProcessorArchitecture.X86)).ToArray(); Assert.True(names.Length >= 1, "At least one 32bit mscorlib"); foreach (var name in names) { Assert.Equal("mscorlib", name.Name); } - names = GlobalAssemblyCache.GetAssemblyIdentities("mscorlib").ToArray(); + names = gac.GetAssemblyIdentities("mscorlib").ToArray(); Assert.True(names.Length >= 1, "At least 1 mscorlib"); foreach (var name in names) { Assert.Equal("mscorlib", name.Name); } - names = GlobalAssemblyCache.GetAssemblyIdentities("System.Core, Version=4.0.0.0").ToArray(); + names = gac.GetAssemblyIdentities("System.Core, Version=4.0.0.0").ToArray(); Assert.True(names.Length >= 1, "At least System.Core"); foreach (var name in names) { @@ -48,7 +49,7 @@ public void GetAssemblyIdentities() Assert.True(name.GetDisplayName().Contains("PublicKeyToken=b77a5c561934e089"), "PublicKeyToken matches"); } - names = GlobalAssemblyCache.GetAssemblyIdentities("System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").ToArray(); + names = gac.GetAssemblyIdentities("System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").ToArray(); Assert.True(names.Length >= 1, "At least System.Core"); foreach (var name in names) { @@ -58,7 +59,7 @@ public void GetAssemblyIdentities() var n = new AssemblyName("System.Core"); n.Version = new Version(4, 0, 0, 0); n.SetPublicKeyToken(new byte[] { 0xb7, 0x7a, 0x5c, 0x56, 0x19, 0x34, 0xe0, 0x89 }); - names = GlobalAssemblyCache.GetAssemblyIdentities(n).ToArray(); + names = gac.GetAssemblyIdentities(n).ToArray(); Assert.True(names.Length >= 1, "At least System.Core"); foreach (var name in names) @@ -66,30 +67,30 @@ public void GetAssemblyIdentities() Assert.Equal("System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", name.GetDisplayName()); } - names = GlobalAssemblyCache.GetAssemblyIdentities("x\u0002").ToArray(); + names = gac.GetAssemblyIdentities("x\u0002").ToArray(); Assert.Equal(0, names.Length); - names = GlobalAssemblyCache.GetAssemblyIdentities("\0").ToArray(); + names = gac.GetAssemblyIdentities("\0").ToArray(); Assert.Equal(0, names.Length); - names = GlobalAssemblyCache.GetAssemblyIdentities("xxxx\0xxxxx").ToArray(); + names = gac.GetAssemblyIdentities("xxxx\0xxxxx").ToArray(); Assert.Equal(0, names.Length); // fusion API CreateAssemblyEnum returns S_FALSE for this name - names = GlobalAssemblyCache.GetAssemblyIdentities("nonexistingassemblyname" + Guid.NewGuid().ToString()).ToArray(); + names = gac.GetAssemblyIdentities("nonexistingassemblyname" + Guid.NewGuid().ToString()).ToArray(); Assert.Equal(0, names.Length); } - [Fact] + [ClrOnlyFact(ClrOnlyReason.Fusion)] public void AssemblyAndGacLocation() { - var names = GlobalAssemblyCache.GetAssemblyObjects(partialNameFilter: null, architectureFilter: default(ImmutableArray)).ToArray(); + var names = ClrGlobalAssemblyCache.GetAssemblyObjects(partialNameFilter: null, architectureFilter: default(ImmutableArray)).ToArray(); Assert.True(names.Length > 100, "There are at least 100 assemblies in the GAC"); var gacLocationsUpper = GlobalAssemblyCacheLocation.RootLocations.Select(location => location.ToUpper()); foreach (var name in names) { - string location = GlobalAssemblyCache.GetAssemblyLocation(name); + string location = ClrGlobalAssemblyCache.GetAssemblyLocation(name); Assert.NotNull(location); Assert.True(gacLocationsUpper.Any(gac => location.StartsWith(gac, StringComparison.OrdinalIgnoreCase)), "Path within some GAC root"); Assert.Equal(Path.GetFullPath(location), location); diff --git a/src/Test/Utilities/Shared/Assert/ClrOnlyFactAttribute.cs b/src/Test/Utilities/Shared/Assert/ClrOnlyFactAttribute.cs index e71d31f8b5e50..469af122e8492 100644 --- a/src/Test/Utilities/Shared/Assert/ClrOnlyFactAttribute.cs +++ b/src/Test/Utilities/Shared/Assert/ClrOnlyFactAttribute.cs @@ -32,6 +32,8 @@ public enum ClrOnlyReason // Can't sign. Signing, + + Fusion, } public sealed class ClrOnlyFactAttribute : FactAttribute @@ -62,9 +64,22 @@ private static string GetSkipReason(ClrOnlyReason reason) return "Can't sign assemblies in this scenario"; case ClrOnlyReason.DocumentationComment: return "Documentation comment compiler can't run this test on Mono"; + case ClrOnlyReason.Fusion: + return "Fusion not available on Mono"; default: return "Test supported only on CLR"; } } } + + public sealed class MonoOnlyFactAttribute : FactAttribute + { + public MonoOnlyFactAttribute(string reason) + { + if (!MonoHelpers.IsRunningOnMono()) + { + Skip = reason; + } + } + } }