private async Task VerifyDotnetToolCompatibilityChecks(CompatibilityData compatibilityData, GraphItem <RemoteResolveResult> node, RestoreTargetGraph graph, List <CompatibilityIssue> issues) { var containsDotnetToolPackageType = compatibilityData.TargetLibrary.PackageType.Contains(PackageType.DotnetTool); if (compatibilityData.TargetLibrary.PackageType.Count != 1 && containsDotnetToolPackageType) { var issue = CompatibilityIssue.ToolsPackageWithExtraPackageTypes( new PackageIdentity(node.Key.Name, node.Key.Version)); issues.Add(issue); await _log.LogAsync(GetErrorMessage(NuGetLogCode.NU1204, issue, graph)); } if (containsDotnetToolPackageType && !(HasCompatibleToolsAssets(compatibilityData.TargetLibrary) || !compatibilityData.Files.Any(p => p.StartsWith("tools/", StringComparison.OrdinalIgnoreCase)))) { var available = GetAvailableFrameworkRuntimePairs(compatibilityData, graph); var issue = CompatibilityIssue.IncompatibleToolsPackage( new PackageIdentity(node.Key.Name, node.Key.Version), graph.Framework, graph.RuntimeIdentifier, available); issues.Add(issue); await _log.LogAsync(GetErrorMessage(NuGetLogCode.NU1202, issue, graph)); } if (ProjectStyle.DotnetToolReference == compatibilityData.PackageSpec.RestoreMetadata?.ProjectStyle) { // If the package is not autoreferenced or a tool package if (!containsDotnetToolPackageType && compatibilityData.PackageSpec.GetAllPackageDependencies().Where(e => !e.AutoReferenced).Any(e => e.Name.Equals(compatibilityData.TargetLibrary.Name, StringComparison.OrdinalIgnoreCase))) { var issue = CompatibilityIssue.IncompatiblePackageWithDotnetTool(new PackageIdentity(node.Key.Name, node.Key.Version)); issues.Add(issue); await _log.LogAsync(GetErrorMessage(NuGetLogCode.NU1212, issue, graph)); } } else { if (containsDotnetToolPackageType) { var issue = CompatibilityIssue.IncompatiblePackageWithDotnetTool(new PackageIdentity(node.Key.Name, node.Key.Version)); issues.Add(issue); await _log.LogAsync(GetErrorMessage(NuGetLogCode.NU1212, issue, graph)); } } }
internal async Task <CompatibilityCheckResult> CheckAsync( RestoreTargetGraph graph, Dictionary <string, LibraryIncludeFlags> includeFlags, PackageSpec packageSpec) { // The Compatibility Check is designed to alert the user to cases where packages are not behaving as they would // expect, due to compatibility issues. // // During this check, we scan all packages for a given restore graph and check the following conditions // (using an example TxM 'foo' and an example Runtime ID 'bar'): // // * If any package provides a "ref/foo/Thingy.dll", there MUST be a matching "lib/foo/Thingy.dll" or // "runtimes/bar/lib/foo/Thingy.dll" provided by a package in the graph. // * All packages that contain Managed Assemblies must provide assemblies for 'foo'. If a package // contains any of 'ref/' folders, 'lib/' folders, or framework assemblies, it must provide at least // one of those for the 'foo' framework. Otherwise, the package is intending to provide managed assemblies // but it does not support the target platform. If a package contains only 'content/', 'build/', 'tools/' or // other NuGet convention folders, it is exempt from this check. Thus, content-only packages are always considered // compatible, regardless of if they actually provide useful content. // // It is up to callers to invoke the compatibility check on the graphs they wish to check, but the general behavior in // the restore command is to invoke a compatibility check for each of: // // * The Targets (TxMs) defined in the project.json, with no Runtimes // * All combinations of TxMs and Runtimes defined in the project.json // * Additional (TxMs, Runtime) pairs defined by the "supports" mechanism in project.json var runtimeAssemblies = new HashSet <string>(StringComparer.OrdinalIgnoreCase); var compileAssemblies = new Dictionary <string, LibraryIdentity>(StringComparer.OrdinalIgnoreCase); var issues = new List <CompatibilityIssue>(); if (packageSpec.RestoreMetadata?.ProjectStyle == ProjectStyle.DotnetToolReference) { // Autoreferenced packages are allowed. Currently they're using Microsoft.NET.Platforms as an auto-ref package if (packageSpec.GetAllPackageDependencies().Where(e => !e.AutoReferenced).Count() != 1) { // Create issue var issue = CompatibilityIssue.IncompatibleProjectType( new PackageIdentity(packageSpec.Name, packageSpec.Version)); issues.Add(issue); await _log.LogAsync(GetErrorMessage(NuGetLogCode.NU1211, issue, graph)); } } // Verify framework assets also as part of runtime assets validation. foreach (var node in graph.Flattened) { await _log.LogAsync(LogLevel.Debug, string.Format(CultureInfo.CurrentCulture, Strings.Log_CheckingPackageCompatibility, node.Key.Name, node.Key.Version, graph.Name)); // Check project compatibility if (node.Key.Type == LibraryType.Project) { // Get the full library var localMatch = node.Data?.Match as LocalMatch; if (localMatch == null || !IsProjectFrameworkCompatible(localMatch.LocalLibrary)) { var available = new List <NuGetFramework>(); // If the project info is available find all available frameworks if (localMatch?.LocalLibrary != null) { available = GetProjectFrameworks(localMatch.LocalLibrary); } // Create issue var issue = CompatibilityIssue.IncompatibleProject( new PackageIdentity(node.Key.Name, node.Key.Version), graph.Framework, graph.RuntimeIdentifier, available); issues.Add(issue); await _log.LogAsync(GetErrorMessage(NuGetLogCode.NU1201, issue, graph)); } // Skip further checks on projects continue; } // Find the include/exclude flags for this package LibraryIncludeFlags packageIncludeFlags; if (!includeFlags.TryGetValue(node.Key.Name, out packageIncludeFlags)) { packageIncludeFlags = LibraryIncludeFlags.All; } // If the package has compile and runtime assets excluded the compatibility check // is not needed. Packages with no ref or lib entries are considered // compatible in IsCompatible. if ((packageIncludeFlags & (LibraryIncludeFlags.Compile | LibraryIncludeFlags.Runtime)) == LibraryIncludeFlags.None) { continue; } var compatibilityData = GetCompatibilityData(graph, node.Key, packageSpec); if (compatibilityData == null) { continue; } if (!IsPackageCompatible(compatibilityData)) { var available = GetPackageFrameworks(compatibilityData, graph); var issue = CompatibilityIssue.IncompatiblePackage( new PackageIdentity(node.Key.Name, node.Key.Version), graph.Framework, graph.RuntimeIdentifier, available); issues.Add(issue); await _log.LogAsync(GetErrorMessage(NuGetLogCode.NU1202, issue, graph)); } await VerifyDotnetToolCompatibilityChecks(compatibilityData, node, graph, issues); // Check for matching ref/libs if we're checking a runtime-specific graph var targetLibrary = compatibilityData.TargetLibrary; if (_validateRuntimeAssets && !string.IsNullOrEmpty(graph.RuntimeIdentifier)) { // Skip runtime checks for packages that have runtime references excluded, // this allows compile only packages that do not have runtimes for the // graph RID to be used. if ((packageIncludeFlags & LibraryIncludeFlags.Runtime) == LibraryIncludeFlags.Runtime) { // Scan the package for ref assemblies foreach (var compile in targetLibrary.CompileTimeAssemblies .Where(p => Path.GetExtension(p.Path) .Equals(".dll", StringComparison.OrdinalIgnoreCase))) { var name = Path.GetFileNameWithoutExtension(compile.Path); // If we haven't already started tracking this compile-time assembly, AND there isn't already a runtime-loadable version if (!compileAssemblies.ContainsKey(name) && !runtimeAssemblies.Contains(name)) { // Track this assembly as potentially compile-time-only compileAssemblies.Add(name, node.Key); } } // Match up runtime assemblies foreach (var runtime in targetLibrary.RuntimeAssemblies .Where(p => Path.GetExtension(p.Path) .Equals(".dll", StringComparison.OrdinalIgnoreCase))) { var name = Path.GetFileNameWithoutExtension(runtime.Path); // If there was a compile-time-only assembly under this name... if (compileAssemblies.ContainsKey(name)) { // Remove it, we've found a matching runtime ref compileAssemblies.Remove(name); } // Track this assembly as having a runtime assembly runtimeAssemblies.Add(name); // Fix for NuGet/Home#752 - Consider ".ni.dll" (native image/ngen) files matches for ref/ assemblies if (name.EndsWith(".ni", StringComparison.OrdinalIgnoreCase)) { var withoutNi = name.Substring(0, name.Length - 3); if (compileAssemblies.ContainsKey(withoutNi)) { compileAssemblies.Remove(withoutNi); } runtimeAssemblies.Add(withoutNi); } } } } } // Generate errors for un-matched reference assemblies, if we're checking a runtime-specific graph if (_validateRuntimeAssets && !string.IsNullOrEmpty(graph.RuntimeIdentifier)) { foreach (var compile in compileAssemblies) { var issue = CompatibilityIssue.ReferenceAssemblyNotImplemented( compile.Key, new PackageIdentity(compile.Value.Name, compile.Value.Version), graph.Framework, graph.RuntimeIdentifier); issues.Add(issue); await _log.LogAsync(GetErrorMessage(NuGetLogCode.NU1203, issue, graph)); } } return(new CompatibilityCheckResult(graph, issues)); }
/// <summary> /// Create an error message for the given issue. /// </summary> private static RestoreLogMessage GetErrorMessage(NuGetLogCode logCode, CompatibilityIssue issue, RestoreTargetGraph graph) { return(RestoreLogMessage.CreateError(logCode, issue.Format(), issue.Package.Id, graph.TargetGraphName)); }