| File: DefaultExtensionDependencyChecker.cs | Web Access |
| Project: src\sdk\src\RazorSdk\Tool\Microsoft.NET.Sdk.Razor.Tool.csproj (rzc) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using Microsoft.CodeAnalysis; namespace Microsoft.NET.Sdk.Razor.Tool { internal class DefaultExtensionDependencyChecker : ExtensionDependencyChecker { // These are treated as prefixes. So `Microsoft.CodeAnalysis.Razor` would be assumed to work. private static readonly string[] DefaultIgnoredAssemblies = new string[] { "mscorlib", "netstandard", "System", "Microsoft.CodeAnalysis", "Microsoft.AspNetCore.Razor", "Microsoft.Extensions.ObjectPool" }; private readonly ExtensionAssemblyLoader _loader; private readonly TextWriter _output; private readonly TextWriter _error; private readonly string[] _ignoredAssemblies; public DefaultExtensionDependencyChecker( ExtensionAssemblyLoader loader, TextWriter output, TextWriter error, string[] ignoredAssemblies = null) { _loader = loader; _output = output; _error = error; _ignoredAssemblies = ignoredAssemblies ?? DefaultIgnoredAssemblies; } public override bool Check(IEnumerable<string> assmblyFilePaths) { try { return CheckCore(assmblyFilePaths); } catch (Exception ex) { _error.WriteLine("Exception performing Extension dependency check:"); _error.WriteLine(ex.ToString()); return false; } } private bool CheckCore(IEnumerable<string> assemblyFilePaths) { var items = assemblyFilePaths.Select(a => ExtensionVerificationItem.Create(a)).ToArray(); var assemblies = new HashSet<AssemblyIdentity>(items.Select(i => i.Identity)); for (var i = 0; i < items.Length; i++) { var item = items[i]; _output.WriteLine($"Verifying assembly at {item.FilePath}"); if (!Path.IsPathRooted(item.FilePath)) { _error.WriteLine($"The file path '{item.FilePath}' is not a rooted path. File paths must be absolute and fully-qualified."); return false; } foreach (var reference in item.References) { if (_ignoredAssemblies.Any(n => reference.Name.StartsWith(n, StringComparison.Ordinal))) { // This is on the allow list, keep going. continue; } if (assemblies.Contains(reference)) { // This was also provided as a dependency, keep going. continue; } // If we get here we can't resolve this assembly. This is an error. _error.WriteLine($"Extension assembly '{item.Identity.Name}' depends on '{reference.ToString()} which is missing."); return false; } } // Assuming we get this far, the set of assemblies we have is at least a coherent set (barring // version conflicts). Register all of the paths with the loader so they can find each other by // name. for (var i = 0; i < items.Length; i++) { _loader.AddAssemblyLocation(items[i].FilePath); } // Now try to load everything. This has the side effect of resolving all of these items // in the loader's caches. for (var i = 0; i < items.Length; i++) { var item = items[i]; item.Assembly = _loader.LoadFromPath(item.FilePath); } // Third, check that the MVIDs of the files on disk match the MVIDs of the loaded assemblies. for (var i = 0; i < items.Length; i++) { var item = items[i]; if (item.Mvid != item.Assembly.ManifestModule.ModuleVersionId) { _error.WriteLine($"Extension assembly '{item.Identity.Name}' at '{item.FilePath}' has a different ModuleVersionId than loaded assembly '{item.Assembly.FullName}'"); return false; } } return true; } private class ExtensionVerificationItem { public static ExtensionVerificationItem Create(string filePath) { using (var peReader = new PEReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))) { var metadataReader = peReader.GetMetadataReader(); var identity = metadataReader.GetAssemblyIdentity(); var mvid = metadataReader.GetGuid(metadataReader.GetModuleDefinition().Mvid); var references = metadataReader.GetReferencedAssembliesOrThrow(); return new ExtensionVerificationItem(filePath, identity, mvid, references.ToArray()); } } private ExtensionVerificationItem(string filePath, AssemblyIdentity identity, Guid mvid, AssemblyIdentity[] references) { FilePath = filePath; Identity = identity; Mvid = mvid; References = references; } public string FilePath { get; } public Assembly Assembly { get; set; } public AssemblyIdentity Identity { get; } public Guid Mvid { get; } public IReadOnlyList<AssemblyIdentity> References { get; } } } }