|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
namespace Microsoft.AspNetCore.Razor.Tasks
{
/// <summary>
/// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively.
/// </summary>
public class ReferenceResolver
{
private readonly HashSet<string> _mvcAssemblies;
private readonly IReadOnlyList<AssemblyItem> _assemblyItems;
private readonly Dictionary<AssemblyItem, Classification> _classifications;
public ReferenceResolver(IReadOnlyList<string> targetAssemblies, IReadOnlyList<AssemblyItem> assemblyItems)
{
_mvcAssemblies = new HashSet<string>(targetAssemblies, StringComparer.Ordinal);
_assemblyItems = assemblyItems;
_classifications = new Dictionary<AssemblyItem, Classification>();
Lookup = new Dictionary<string, AssemblyItem>(StringComparer.Ordinal);
foreach (var item in assemblyItems)
{
Lookup[item.AssemblyName] = item;
}
}
protected Dictionary<string, AssemblyItem> Lookup { get; }
public IReadOnlyList<string> ResolveAssemblies()
{
var applicationParts = new List<string>();
foreach (var item in _assemblyItems)
{
var classification = Resolve(item);
if (classification == Classification.ReferencesMvc)
{
applicationParts.Add(item.AssemblyName);
}
}
return applicationParts;
}
private Classification Resolve(AssemblyItem assemblyItem)
{
if (_classifications.TryGetValue(assemblyItem, out var classification))
{
return classification;
}
// Initialize the dictionary with a value to short-circuit recursive references.
classification = Classification.Unknown;
_classifications[assemblyItem] = classification;
if (assemblyItem.Path == null)
{
// We encountered a dependency that isn't part of this assembly's dependency set. We'll see if it happens to be an MVC assembly
// since that's the only useful determination we can make given the assembly name.
classification = _mvcAssemblies.Contains(assemblyItem.AssemblyName) ?
Classification.IsMvc :
Classification.DoesNotReferenceMvc;
}
else if (assemblyItem.IsFrameworkReference)
{
// We do not allow transitive references to MVC via a framework reference to count.
// e.g. depending on Microsoft.AspNetCore.SomeThingNewThatDependsOnMvc would not result in an assembly being treated as
// referencing MVC.
classification = _mvcAssemblies.Contains(assemblyItem.AssemblyName) ?
Classification.IsMvc :
Classification.DoesNotReferenceMvc;
}
else if (_mvcAssemblies.Contains(assemblyItem.AssemblyName))
{
classification = Classification.IsMvc;
}
else
{
classification = Classification.DoesNotReferenceMvc;
foreach (var reference in GetReferences(assemblyItem.Path))
{
var referenceClassification = Resolve(reference);
if (referenceClassification == Classification.IsMvc || referenceClassification == Classification.ReferencesMvc)
{
classification = Classification.ReferencesMvc;
break;
}
}
}
Debug.Assert(classification != Classification.Unknown);
_classifications[assemblyItem] = classification;
return classification;
}
protected virtual IReadOnlyList<AssemblyItem> GetReferences(string file)
{
try
{
if (!File.Exists(file))
{
throw new ReferenceAssemblyNotFoundException(file);
}
using var peReader = new PEReader(File.OpenRead(file));
if (!peReader.HasMetadata)
{
return Array.Empty<AssemblyItem>(); // not a managed assembly
}
var metadataReader = peReader.GetMetadataReader();
var references = new List<AssemblyItem>(metadataReader.AssemblyReferences.Count);
foreach (var handle in metadataReader.AssemblyReferences)
{
var reference = metadataReader.GetAssemblyReference(handle);
var referenceName = metadataReader.GetString(reference.Name);
if (!Lookup.TryGetValue(referenceName, out var assemblyItem))
{
// A dependency references an item that isn't referenced by this project.
// We'll construct an item for so that we can calculate the classification based on it's name.
assemblyItem = new AssemblyItem
{
AssemblyName = referenceName,
};
Lookup[referenceName] = assemblyItem;
}
references.Add(assemblyItem);
}
return references;
}
catch (BadImageFormatException)
{
// not a PE file, or invalid metadata
}
return Array.Empty<AssemblyItem>(); // not a managed assembly
}
protected enum Classification
{
Unknown,
DoesNotReferenceMvc,
ReferencesMvc,
IsMvc,
}
}
}
|