in src/NuGet.Core/NuGet.Commands/RestoreCommand/CompatibilityChecker.cs [39:227]
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>();
// Verify framework assets also as part of runtime assets validation.
foreach (var node in graph.Flattened)
{
// 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));
}
if (!IsPackageTypeCompatible(compatibilityData))
{
var issue = CompatibilityIssue.IncompatiblePackageType(
new PackageIdentity(node.Key.Name, node.Key.Version),
graph.Framework,
graph.RuntimeIdentifier);
issues.Add(issue);
await _log.LogAsync(GetErrorMessage(NuGetLogCode.NU1213, 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);
}