|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
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
{
/// <summary>
/// Provides APIs to enumerate and look up assemblies stored in the Global Assembly Cache.
/// </summary>
internal sealed class MonoGlobalAssemblyCache : GlobalAssemblyCache
{
private static readonly string s_corlibDirectory;
private static readonly string s_gacDirectory;
static MonoGlobalAssemblyCache()
{
var corlibAssemblyFile = typeof(object).Assembly.Location;
s_corlibDirectory = Path.GetDirectoryName(corlibAssemblyFile);
var systemAssemblyFile = typeof(Uri).Assembly.Location;
s_gacDirectory = Directory.GetParent(Path.GetDirectoryName(systemAssemblyFile)).Parent.FullName;
}
private static AssemblyName CreateAssemblyNameFromFile(string path)
=> AssemblyName.GetAssemblyName(path);
private static IEnumerable<string> GetGacAssemblyPaths(string gacPath, string name, Version version, byte[] publicKeyTokenBytes)
{
var fileName = name + ".dll";
// First check to see if the assembly lives alongside mscorlib.dll.
var corlibFriendPath = Path.Combine(s_corlibDirectory, fileName);
if (!File.Exists(corlibFriendPath))
{
// If not, check the Facades directory (e.g. this is where netstandard.dll will live)
corlibFriendPath = Path.Combine(s_corlibDirectory, "Facades", fileName);
}
// Yield and bail early if we find anything - it'll either be a Facade assembly or a
// symlink into the GAC so we can avoid the more exhaustive work below.
if (File.Exists(corlibFriendPath))
{
yield return corlibFriendPath;
yield break;
}
var publicKeyToken = ToHexString(publicKeyTokenBytes);
// Another bail fast attempt to peek directly into the GAC if we have version and public key
if (version != null && publicKeyToken != null)
{
yield return Path.Combine(gacPath, name, version + "__" + publicKeyToken, fileName);
yield break;
}
// Otherwise we need to iterate the GAC in the file system to find a match
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(), fileName);
if (File.Exists(assemblyPath))
{
yield return assemblyPath;
}
}
}
private static IEnumerable<(AssemblyIdentity Identity, string Path)> GetAssemblyIdentitiesAndPaths(AssemblyName name, ImmutableArray<ProcessorArchitecture> architectureFilter)
{
if (name == null)
{
return GetAssemblyIdentitiesAndPaths(null, null, null, architectureFilter);
}
return GetAssemblyIdentitiesAndPaths(name.Name, name.Version, name.GetPublicKeyToken(), architectureFilter);
}
private static IEnumerable<(AssemblyIdentity Identity, string Path)> GetAssemblyIdentitiesAndPaths(string name, Version version, byte[] publicKeyToken, ImmutableArray<ProcessorArchitecture> architectureFilter)
{
var assemblyPaths = GetGacAssemblyPaths(s_gacDirectory, name, version, publicKeyToken);
foreach (var assemblyPath in assemblyPaths)
{
if (!File.Exists(assemblyPath))
{
continue;
}
var gacAssemblyName = CreateAssemblyNameFromFile(assemblyPath);
#pragma warning disable SYSLIB0037
// warning SYSLIB0037: 'AssemblyName.ProcessorArchitecture' is obsolete: 'AssemblyName members HashAlgorithm, ProcessorArchitecture, and VersionCompatibility are obsolete and not supported.'
if (gacAssemblyName.ProcessorArchitecture != ProcessorArchitecture.None &&
architectureFilter != default(ImmutableArray<ProcessorArchitecture>) &&
architectureFilter.Length > 0 &&
!architectureFilter.Contains(gacAssemblyName.ProcessorArchitecture))
{
continue;
}
#pragma warning restore SYSLIB0037
var assemblyIdentity = new AssemblyIdentity(
gacAssemblyName.Name,
gacAssemblyName.Version,
gacAssemblyName.CultureName,
ImmutableArray.Create(gacAssemblyName.GetPublicKeyToken()));
yield return (assemblyIdentity, assemblyPath);
}
}
public override IEnumerable<AssemblyIdentity> GetAssemblyIdentities(AssemblyName partialName, ImmutableArray<ProcessorArchitecture> architectureFilter = default(ImmutableArray<ProcessorArchitecture>))
{
return GetAssemblyIdentitiesAndPaths(partialName, architectureFilter).Select(identityAndPath => identityAndPath.Item1);
}
public override IEnumerable<AssemblyIdentity> GetAssemblyIdentities(string partialName = null, ImmutableArray<ProcessorArchitecture> architectureFilter = default(ImmutableArray<ProcessorArchitecture>))
{
AssemblyName name;
try
{
name = (partialName == null) ? null : new AssemblyName(partialName);
}
catch
{
return SpecializedCollections.EmptyEnumerable<AssemblyIdentity>();
}
return GetAssemblyIdentities(name, architectureFilter);
}
public override IEnumerable<string> GetAssemblySimpleNames(ImmutableArray<ProcessorArchitecture> architectureFilter = default(ImmutableArray<ProcessorArchitecture>))
{
return GetAssemblyIdentitiesAndPaths(name: null, version: null, publicKeyToken: null, architectureFilter: architectureFilter).
Select(identityAndPath => identityAndPath.Identity.Name).Distinct();
}
public override AssemblyIdentity ResolvePartialName(
string displayName,
out string location,
ImmutableArray<ProcessorArchitecture> 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.Path;
if (!File.Exists(assemblyPath))
{
continue;
}
var gacAssemblyName = CreateAssemblyNameFromFile(assemblyPath);
isBestMatch = cultureName == null || gacAssemblyName.CultureName == cultureName;
bool isBetterMatch = location == null || isBestMatch;
if (isBetterMatch)
{
location = assemblyPath;
assemblyIdentity = identityAndPath.Identity;
}
if (isBestMatch)
{
break;
}
}
return assemblyIdentity;
}
private static string ToHexString(byte[] bytes)
{
if (bytes == null)
{
return null;
}
var sb = PooledObjects.PooledStringBuilder.GetInstance();
foreach (var b in bytes)
{
sb.Builder.Append(b.ToString("x2"));
}
return sb.ToStringAndFree();
}
}
}
|