in src/NuGet.Core/NuGet.ProjectModel/ProjectLockFile/PackagesLockFileUtilities.cs [78:294]
public static LockFileValidationResult IsLockFileValid(DependencyGraphSpec dgSpec, PackagesLockFile nuGetLockFile)
{
if (dgSpec == null)
throw new ArgumentNullException(nameof(dgSpec));
if (nuGetLockFile == null)
throw new ArgumentNullException(nameof(nuGetLockFile));
List<string> invalidReasons = new List<string>();
// Current tools know how to read only previous formats including the current
if (PackagesLockFileFormat.PackagesLockFileVersion < nuGetLockFile.Version)
{
invalidReasons.Add(string.Format(
CultureInfo.CurrentCulture,
Strings.PackagesLockFile_IncompatibleLockFileVersion,
PackagesLockFileFormat.PackagesLockFileVersion
));
return new LockFileValidationResult(false, invalidReasons);
}
var uniqueName = dgSpec.Restore.First();
var project = dgSpec.GetProjectSpec(uniqueName);
// Validate all the direct dependencies
NuGetFramework[] lockFileFrameworks = nuGetLockFile.Targets
.Where(t => t.TargetFramework != null)
.Select(t => t.TargetFramework)
.Distinct()
.ToArray();
if (project.TargetFrameworks.Count != lockFileFrameworks.Length)
{
invalidReasons.Add(string.Format(
CultureInfo.CurrentCulture,
Strings.PackagesLockFile_MismatchedTargetFrameworks,
string.Join(",", project.TargetFrameworks.Select(e => e.FrameworkName.GetShortFolderName())),
string.Join(",", lockFileFrameworks.Select(e => e.GetShortFolderName()))
));
}
else
{
// Validate the runtimes for the current project did not change.
var projectRuntimesKeys = project.RuntimeGraph.Runtimes.Select(r => r.Key).Where(k => k != null);
var lockFileRuntimes = nuGetLockFile.Targets.Select(t => t.RuntimeIdentifier).Where(r => r != null).Distinct();
if (!projectRuntimesKeys.OrderedEquals(
lockFileRuntimes,
x => x,
StringComparer.InvariantCultureIgnoreCase,
StringComparer.InvariantCultureIgnoreCase))
{
invalidReasons.Add(string.Format(
CultureInfo.CurrentCulture,
Strings.PackagesLockFile_RuntimeIdentifiersChanged,
string.Join(";", projectRuntimesKeys.OrderBy(e => e)),
string.Join(";", lockFileRuntimes.OrderBy(e => e))
));
}
foreach (var framework in project.TargetFrameworks)
{
var target = nuGetLockFile.Targets.FirstOrDefault(
t => EqualityUtility.EqualsWithNullCheck(t.TargetFramework, framework.FrameworkName));
if (target == null)
{
// a new target found in the dgSpec so invalidate existing lock file.
invalidReasons.Add(string.Format(
CultureInfo.CurrentCulture,
Strings.PackagesLockFile_NewTargetFramework,
framework.FrameworkName.GetShortFolderName())
);
continue;
}
IEnumerable<LockFileDependency> directDependencies = target.Dependencies.Where(dep => dep.Type == PackageDependencyType.Direct);
(var hasProjectDependencyChanged, var pmessage) = HasDirectPackageDependencyChanged(framework.Dependencies, directDependencies, target.TargetFramework);
if (hasProjectDependencyChanged)
{
// lock file is out of sync
invalidReasons.Add(pmessage);
}
var transitiveDependenciesEnforcedByCentralVersions = target.Dependencies.Where(dep => dep.Type == PackageDependencyType.CentralTransitive).ToList();
var transitiveDependencies = target.Dependencies.Where(dep => dep.Type == PackageDependencyType.Transitive).ToList();
(var hasTransitiveDependencyChanged, var tmessage) = HasProjectTransitiveDependencyChanged(framework.CentralPackageVersions, transitiveDependenciesEnforcedByCentralVersions, transitiveDependencies);
if (hasTransitiveDependencyChanged)
{
// lock file is out of sync
invalidReasons.Add(tmessage);
}
}
// Validate all P2P references
foreach (var restoreMetadataFramework in project.RestoreMetadata.TargetFrameworks)
{
var target = nuGetLockFile.Targets.FirstOrDefault(
t => EqualityUtility.EqualsWithNullCheck(t.TargetFramework, restoreMetadataFramework.FrameworkName));
var targetFrameworkInformation = project.TargetFrameworks.FirstOrDefault(e => e.TargetAlias == restoreMetadataFramework.TargetAlias);
if (target == null)
continue;
var queue = new Queue<Tuple<string, string>>();
var visitedP2PReference = new HashSet<string>();
foreach (var projectReference in restoreMetadataFramework.ProjectReferences)
{
if (visitedP2PReference.Add(projectReference.ProjectUniqueName))
{
PackageSpec spec = dgSpec.GetProjectSpec(projectReference.ProjectUniqueName);
queue.Enqueue(new Tuple<string, string>(spec.Name, projectReference.ProjectUniqueName));
while (queue.Count > 0)
{
var projectNames = queue.Dequeue();
var p2pUniqueName = projectNames.Item2;
var p2pProjectName = projectNames.Item1;
var projectDependency = target.Dependencies.FirstOrDefault(
dep => dep.Type == PackageDependencyType.Project &&
StringComparer.OrdinalIgnoreCase.Equals(dep.Id, p2pProjectName));
if (projectDependency == null)
{
// new direct project dependency.
// If there are changes in the P2P2P references, they will be caught in HasP2PDependencyChanged.
invalidReasons.Add(string.Format(
CultureInfo.CurrentCulture,
Strings.PackagesLockFile_ProjectReferenceAdded,
p2pProjectName,
target.TargetFramework.GetShortFolderName()
));
continue;
}
var p2pSpec = dgSpec.GetProjectSpec(p2pUniqueName);
if (p2pSpec != null)
{
TargetFrameworkInformation p2pSpecTargetFrameworkInformation = default;
if (p2pSpec.RestoreMetadata.ProjectStyle == ProjectStyle.PackagesConfig || p2pSpec.RestoreMetadata.ProjectStyle == ProjectStyle.Unknown)
{
// Skip compat check and dependency check for non PR projects.
// Projects that are not PR do not undergo compat checks by NuGet and do not contribute anything transitively.
p2pSpecTargetFrameworkInformation = p2pSpec.TargetFrameworks.FirstOrDefault();
}
else
{
// This does not consider ATF.
p2pSpecTargetFrameworkInformation = NuGetFrameworkUtility.GetNearest(p2pSpec.TargetFrameworks, restoreMetadataFramework.FrameworkName, e => e.FrameworkName);
}
// No compatible framework found
if (p2pSpecTargetFrameworkInformation != null)
{
// We need to compare the main framework only. Ignoring fallbacks.
var p2pSpecProjectRestoreMetadataFrameworkInfo = p2pSpec.RestoreMetadata.TargetFrameworks.FirstOrDefault(
t => NuGetFramework.Comparer.Equals(p2pSpecTargetFrameworkInformation.FrameworkName, t.FrameworkName));
if (p2pSpecProjectRestoreMetadataFrameworkInfo != null)
{
(var hasChanged, var message) = HasP2PDependencyChanged(p2pSpecTargetFrameworkInformation.Dependencies, p2pSpecProjectRestoreMetadataFrameworkInfo.ProjectReferences, p2pSpecTargetFrameworkInformation.PackagesToPrune, targetFrameworkInformation.PackagesToPrune, projectDependency, dgSpec);
if (hasChanged)
{
// P2P transitive package dependencies have changed
invalidReasons.Add(message);
}
foreach (var reference in p2pSpecProjectRestoreMetadataFrameworkInfo.ProjectReferences)
{
// Do not add private assets for processing.
if (visitedP2PReference.Add(reference.ProjectUniqueName) && reference.PrivateAssets != LibraryIncludeFlags.All)
{
var referenceSpec = dgSpec.GetProjectSpec(reference.ProjectUniqueName);
queue.Enqueue(new Tuple<string, string>(referenceSpec.Name, reference.ProjectUniqueName));
}
}
}
else // This should never happen.
{
throw new Exception(string.Format(CultureInfo.CurrentCulture, Strings.PackagesLockFile_RestoreMetadataMissingTfms));
}
}
else
{
invalidReasons.Add(string.Format(
CultureInfo.CurrentCulture,
Strings.PackagesLockFile_ProjectReferenceHasNoCompatibleTargetFramework,
p2pProjectName,
restoreMetadataFramework.FrameworkName.GetShortFolderName()
));
}
}
else // This can't happen. When adding the queue, the referenceSpec HAS to be discovered. If the project is otherwise missing, it will be discovered in HasP2PDependencyChanged
{
throw new Exception(string.Format(
CultureInfo.CurrentCulture,
Strings.PackagesLockFile_UnableToLoadPackagespec,
p2pUniqueName));
}
}
}
}
}
}
bool isLockFileValid = invalidReasons.Count == 0;
return new LockFileValidationResult(isLockFileValid, invalidReasons);
}