// 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.IO; using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace RunTests.Cache { internal sealed class AssemblyUtil { /// /// The path where binaries need to be loaded from. /// internal string BinariesPath { get; } internal AssemblyUtil(string binariesPath) { BinariesPath = binariesPath; } /// /// There are some DLLs whose abscence is expected and should not be considered an error. These /// are assemblies which are either light up components or are a part of the VS reference graph /// which are never deployed for our tests. /// /// The key here though is to be very explicit about DLLs which are okay to be absent. In the past /// we had build issues which failed to properly deploy important binaries like MS.CA and hence /// produced bad content cache keys. /// internal bool IsKnownMissingAssembly(AssemblyName name) { switch (name.Name) { case "System.Runtime.Loader": // This light up probing is done by the scripting layer. return true; case "Microsoft.VisualStudio.CodeAnalysis": case "Microsoft.VisualStudio.CodeAnalysis.Sdk": case "Microsoft.VisualStudio.TeamSystem.Common": case "Microsoft.VisualStudio.Repository": case "Microsoft.VisualStudio.DeveloperTools": case "Microsoft.VisualStudio.Diagnostics.Assert": case "Microsoft.VisualStudio.Diagrams.View.Interfaces": case "Microsoft.VisualStudio.Shell.ViewManager": case "Microsoft.VisualStudio.VCProjectEngine": case "Microsoft.VisualStudio.VirtualTreeGrid": // These are assemblies which are a part of the tranisitive build graph but are known to // not be a part of our testing code. return true; default: return false; } } /// /// Get all of the values referenced by the specified assembly. /// internal List GetReferencedAssemblies(string assemblyPath) { using (var stream = File.OpenRead(assemblyPath)) using (var peReader = new PEReader(stream)) { var metadataReader = peReader.GetMetadataReader(); var list = new List(); foreach (var handle in metadataReader.AssemblyReferences) { var reference = metadataReader.GetAssemblyReference(handle); var name = new AssemblyName(); name.Name = metadataReader.GetString(reference.Name); name.Version = reference.Version; name.CultureName = metadataReader.GetString(reference.Culture); var keyOrToken = metadataReader.GetBlobContent(reference.PublicKeyOrToken); if (0 != (reference.Flags & AssemblyFlags.PublicKey)) { name.SetPublicKey(keyOrToken.ToArray()); } else if (!keyOrToken.IsEmpty) { name.SetPublicKeyToken(keyOrToken.ToArray()); } list.Add(name); } return list; } } /// /// Get the path for the given value. /// /// /// This implementation assumes that we are running on the desktop runtime without any /// hidden probing paths. Hence if the assembly isn't in the application directory then /// it must be in the GAC. This is a fine assumption for now as we only run this on the /// desktop runtime but if caching is ever moved to CoreClr this will need to be revisited. /// /// In particular need to consider the ramifications if the tool and tests run on a /// different runtime. /// internal bool TryGetAssemblyPath(AssemblyName name, out string assemblyPath) { Assembly assembly; try { // An assembly of the appropriate version in the GAC will win before any // local assembly. Must consider it first. assembly = Assembly.Load(name); if (assembly.GlobalAssemblyCache) { assemblyPath = assembly.Location; return true; } } catch { // It's okay and expected for probing to fail here. assembly = null; } var dllPath = Path.Combine(BinariesPath, $"{name.Name}.dll"); if (File.Exists(dllPath)) { assemblyPath = dllPath; return true; } var exePath = Path.Combine(BinariesPath, $"{name.Name}.exe"); if (File.Exists(exePath)) { assemblyPath = exePath; return true; } if (assembly != null) { assemblyPath = assembly.Location; return true; } assemblyPath = null; return false; } } }