提交 fb1b764b 编写于 作者: A Aaron Bockover 提交者: Tomáš Matoušek

MonoGlobalAssemblyCache: faster resolves + resolve facade assemblies (#39369)

* MonoGlobalAssemblyCache: actually read assembly names from assembly files

new AssemblyName(path) becomes AssemblyName.GetAssemblyName(path)

This fixes the GlobalAssemblyCacheTests when running on Mono.

cf. https://github.com/dotnet/roslyn/pull/39369#issuecomment-545970358

* MonoGlobalAssemblyCache: faster resolves + resolve Facades

1. Don't special case mscorlib.dll - we already will have a reference
   to it, and the changes in (2) allow it to still resolve. Remove
   GetCorlibPaths.

2. Have GetCacAssemblyPaths bail early if it finds an assembly that
   lives alongside mscorlib.dll, or in the Facades directory. This
   These assemblies in Mono are considered "core" and will always
   symlink directly to the GAC. Resolve facade assemblies as well now.
   The Mono distribution of csi has shipped with a response file that
   explicitly referenced Facades/netstandard.dll and
   Facades/System.Runtime.dll and this change obsoletes that. As a
   consequence, local builds of csi.exe now work.

3. Minor perf work around converting a public key token to a string by
   deferring that conversion until only necessary, and use byte.ToString
   instead of AppendFormat. Also use a PooledStringBuilder.

4. Tuple -> ValueTuple

* MetadataShadowwCopyProviderTests: run on Mono
上级 e2ce8630
......@@ -17,54 +17,51 @@ namespace Microsoft.CodeAnalysis
/// </summary>
internal sealed class MonoGlobalAssemblyCache : GlobalAssemblyCache
{
public static readonly ImmutableArray<string> RootLocations;
private static readonly string s_corlibDirectory;
private static readonly string s_gacDirectory;
static MonoGlobalAssemblyCache()
{
RootLocations = ImmutableArray.Create(GetMonoCachePath());
}
var corlibAssemblyFile = typeof(object).Assembly.Location;
s_corlibDirectory = Path.GetDirectoryName(corlibAssemblyFile);
private static string GetMonoCachePath()
{
string file = typeof(Uri).GetTypeInfo().Assembly.Location;
return Directory.GetParent(Path.GetDirectoryName(file)).Parent.FullName;
var systemAssemblyFile = typeof(Uri).Assembly.Location;
s_gacDirectory = Directory.GetParent(Path.GetDirectoryName(systemAssemblyFile)).Parent.FullName;
}
private static IEnumerable<string> GetCorlibPaths(Version version)
{
string corlibPath = typeof(object).GetTypeInfo().Assembly.Location;
var corlibParentDir = Directory.GetParent(corlibPath).Parent;
var corlibPaths = new List<string>();
private static AssemblyName CreateAssemblyNameFromFile(string path)
=> AssemblyName.GetAssemblyName(path);
foreach (var corlibDir in corlibParentDir.GetDirectories())
private static IEnumerable<string> GetGacAssemblyPaths(string gacPath, string name, Version version, byte[] publicKeyTokenBytes)
{
var path = Path.Combine(corlibDir.FullName, "mscorlib.dll");
if (!File.Exists(path))
{
continue;
}
var fileName = name + ".dll";
var name = new AssemblyName(path);
if (version != null && name.Version != version)
// First check to see if the assembly lives alongside mscorlib.dll.
var corlibFriendPath = Path.Combine(s_corlibDirectory, fileName);
if (!File.Exists(corlibFriendPath))
{
continue;
// If not, check the Facades directory (e.g. this is where netstandard.dll will live)
corlibFriendPath = Path.Combine(s_corlibDirectory, "Facades", fileName);
}
corlibPaths.Add(path);
// 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;
}
return corlibPaths;
}
var publicKeyToken = ToHexString(publicKeyTokenBytes);
private static IEnumerable<string> GetGacAssemblyPaths(string gacPath, string name, Version version, string publicKeyToken)
{
// 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, name + ".dll");
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)
{
......@@ -83,7 +80,7 @@ private static IEnumerable<string> GetGacAssemblyPaths(string gacPath, string na
continue;
}
var assemblyPath = Path.Combine(assemblyDir.ToString(), name + ".dll");
var assemblyPath = Path.Combine(assemblyDir.ToString(), fileName);
if (File.Exists(assemblyPath))
{
yield return assemblyPath;
......@@ -91,35 +88,19 @@ private static IEnumerable<string> GetGacAssemblyPaths(string gacPath, string na
}
}
private static IEnumerable<Tuple<AssemblyIdentity, string>> GetAssemblyIdentitiesAndPaths(AssemblyName name, ImmutableArray<ProcessorArchitecture> architectureFilter)
private static IEnumerable<(AssemblyIdentity Identity, string Path)> GetAssemblyIdentitiesAndPaths(AssemblyName name, ImmutableArray<ProcessorArchitecture> 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);
return GetAssemblyIdentitiesAndPaths(name.Name, name.Version, name.GetPublicKeyToken(), architectureFilter);
}
publicKeyToken = sb.ToString();
}
return GetAssemblyIdentitiesAndPaths(name.Name, name.Version, publicKeyToken, architectureFilter);
}
private static IEnumerable<Tuple<AssemblyIdentity, string>> GetAssemblyIdentitiesAndPaths(string name, Version version, string publicKeyToken, ImmutableArray<ProcessorArchitecture> architectureFilter)
{
foreach (string gacPath in RootLocations)
private static IEnumerable<(AssemblyIdentity Identity, string Path)> GetAssemblyIdentitiesAndPaths(string name, Version version, byte[] publicKeyToken, ImmutableArray<ProcessorArchitecture> architectureFilter)
{
var assemblyPaths = (name == "mscorlib") ?
GetCorlibPaths(version) :
GetGacAssemblyPaths(gacPath, name, version, publicKeyToken);
var assemblyPaths = GetGacAssemblyPaths(s_gacDirectory, name, version, publicKeyToken);
foreach (var assemblyPath in assemblyPaths)
{
......@@ -128,7 +109,7 @@ private static IEnumerable<string> GetGacAssemblyPaths(string gacPath, string na
continue;
}
var gacAssemblyName = new AssemblyName(assemblyPath);
var gacAssemblyName = CreateAssemblyNameFromFile(assemblyPath);
if (gacAssemblyName.ProcessorArchitecture != ProcessorArchitecture.None &&
architectureFilter != default(ImmutableArray<ProcessorArchitecture>) &&
......@@ -144,8 +125,7 @@ private static IEnumerable<string> GetGacAssemblyPaths(string gacPath, string na
gacAssemblyName.CultureName,
ImmutableArray.Create(gacAssemblyName.GetPublicKeyToken()));
yield return new Tuple<AssemblyIdentity, string>(assemblyIdentity, assemblyPath);
}
yield return (assemblyIdentity, assemblyPath);
}
}
......@@ -172,7 +152,7 @@ public override IEnumerable<AssemblyIdentity> GetAssemblyIdentities(string parti
public override IEnumerable<string> GetAssemblySimpleNames(ImmutableArray<ProcessorArchitecture> architectureFilter = default(ImmutableArray<ProcessorArchitecture>))
{
return GetAssemblyIdentitiesAndPaths(name: null, version: null, publicKeyToken: null, architectureFilter: architectureFilter).
Select(identityAndPath => identityAndPath.Item1.Name).Distinct();
Select(identityAndPath => identityAndPath.Identity.Name).Distinct();
}
public override AssemblyIdentity ResolvePartialName(
......@@ -196,14 +176,14 @@ public override IEnumerable<string> GetAssemblySimpleNames(ImmutableArray<Proces
foreach (var identityAndPath in GetAssemblyIdentitiesAndPaths(assemblyName, architectureFilter))
{
var assemblyPath = identityAndPath.Item2;
var assemblyPath = identityAndPath.Path;
if (!File.Exists(assemblyPath))
{
continue;
}
var gacAssemblyName = new AssemblyName(assemblyPath);
var gacAssemblyName = CreateAssemblyNameFromFile(assemblyPath);
isBestMatch = cultureName == null || gacAssemblyName.CultureName == cultureName;
bool isBetterMatch = location == null || isBestMatch;
......@@ -211,7 +191,7 @@ public override IEnumerable<string> GetAssemblySimpleNames(ImmutableArray<Proces
if (isBetterMatch)
{
location = assemblyPath;
assemblyIdentity = identityAndPath.Item1;
assemblyIdentity = identityAndPath.Identity;
}
if (isBestMatch)
......@@ -222,5 +202,21 @@ public override IEnumerable<string> GetAssemblySimpleNames(ImmutableArray<Proces
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();
}
}
}
......@@ -81,6 +81,22 @@ public void GetAssemblyIdentities()
Assert.Equal(0, names.Length);
}
[MonoOnlyFact("https://github.com/dotnet/roslyn/pull/39369")]
public void GetFacadeAssemblyIdentities()
{
var gac = GlobalAssemblyCache.Instance;
AssemblyIdentity[] names;
// One netstandard.dll should resolve from Facades on Mono
names = gac.GetAssemblyIdentities(new AssemblyName("netstandard")).ToArray();
Assert.Collection(names, name =>
{
Assert.Equal("netstandard", name.Name);
Assert.True(name.Version >= new Version("2.0.0.0"), "netstandard version must be >= 2.0.0.0");
});
}
[ClrOnlyFact(ClrOnlyReason.Fusion)]
public void AssemblyAndGacLocation()
{
......
......@@ -12,6 +12,8 @@
using System.Runtime.InteropServices;
using System.Globalization;
using static Roslyn.Utilities.PlatformInformation;
namespace Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests
{
// TODO: clean up and move to portable tests
......@@ -20,7 +22,9 @@ public class MetadataShadowCopyProviderTests : TestBase
{
private readonly MetadataShadowCopyProvider _provider;
private static readonly ImmutableArray<string> s_systemNoShadowCopyDirectories = ImmutableArray.Create(
private static readonly ImmutableArray<string> s_systemNoShadowCopyDirectories = IsRunningOnMono
? ImmutableArray<string>.Empty
: ImmutableArray.Create(
FileUtilities.NormalizeDirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.Windows)),
FileUtilities.NormalizeDirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)),
FileUtilities.NormalizeDirectoryPath(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)),
......@@ -58,14 +62,14 @@ public void Errors()
Assert.Throws<ArgumentException>(() => _provider.SuppressShadowCopy(@"\bar.dll"));
Assert.Throws<ArgumentException>(() => _provider.SuppressShadowCopy(@"../bar.dll"));
Assert.Throws<ArgumentOutOfRangeException>(() => _provider.GetMetadataShadowCopy(@"c:\goo.dll", (MetadataImageKind)Byte.MaxValue));
Assert.Throws<ArgumentOutOfRangeException>(() => _provider.GetMetadataShadowCopy(IsRunningOnMono ? "/goo.dll" : @"c:\goo.dll", (MetadataImageKind)Byte.MaxValue));
Assert.Throws<ArgumentNullException>(() => _provider.GetMetadataShadowCopy(null, MetadataImageKind.Assembly));
Assert.Throws<ArgumentException>(() => _provider.GetMetadataShadowCopy("c:goo.dll", MetadataImageKind.Assembly));
Assert.Throws<ArgumentException>(() => _provider.GetMetadataShadowCopy("bar.dll", MetadataImageKind.Assembly));
Assert.Throws<ArgumentException>(() => _provider.GetMetadataShadowCopy(@"\bar.dll", MetadataImageKind.Assembly));
Assert.Throws<ArgumentException>(() => _provider.GetMetadataShadowCopy(@"../bar.dll", MetadataImageKind.Assembly));
Assert.Throws<ArgumentOutOfRangeException>(() => _provider.GetMetadata(@"c:\goo.dll", (MetadataImageKind)Byte.MaxValue));
Assert.Throws<ArgumentOutOfRangeException>(() => _provider.GetMetadata(IsRunningOnMono ? "/goo.dll" : @"c:\goo.dll", (MetadataImageKind)Byte.MaxValue));
Assert.Throws<ArgumentNullException>(() => _provider.GetMetadata(null, MetadataImageKind.Assembly));
Assert.Throws<ArgumentException>(() => _provider.GetMetadata("c:goo.dll", MetadataImageKind.Assembly));
}
......@@ -100,7 +104,7 @@ public void SuppressCopy1()
Assert.Null(sc1);
}
[Fact]
[ClrOnlyFact(ClrOnlyReason.Fusion)]
public void SuppressCopy_Framework()
{
// framework assemblies not copied:
......@@ -147,9 +151,12 @@ public void Modules()
{
Assert.True(_provider.IsShadowCopy(sc));
if (!IsRunningOnMono)
{
// files should be locked:
Assert.Throws<IOException>(() => File.Delete(sc));
}
}
// should get the same metadata:
var metadata2 = _provider.GetMetadata(path0, MetadataImageKind.Assembly) as AssemblyMetadata;
......@@ -223,7 +230,7 @@ public void XmlDocComments_SpecificCulture()
// Greek culture
provider = CreateProvider(elGR);
sc = provider.GetMetadataShadowCopy(dll.Path, MetadataImageKind.Assembly);
Assert.Equal(Path.Combine(Path.GetDirectoryName(sc.PrimaryModule.FullPath), @"el-GR\a.xml"), sc.DocumentationFile.FullPath);
Assert.Equal(Path.Combine(Path.GetDirectoryName(sc.PrimaryModule.FullPath), @"el-GR", "a.xml"), sc.DocumentationFile.FullPath);
Assert.Equal("Greek", File.ReadAllText(sc.DocumentationFile.FullPath));
// Arabic culture (culture specific docs not found, use invariant)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册