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