/// <summary> /// The lock file will get invalidated if one or more of the below are true /// 1. The target frameworks list of the current project was updated. /// 2. The runtime list of the current project waw updated. /// 3. The packages of the current project were updated. /// 4. The packages of the dependent projects were updated. /// 5. The framework list of the dependent projects were updated with frameworks incompatible with the main project framework. /// 6. If the version of the <paramref name="nuGetLockFile"/> is larger than the current tools <see cref="PackagesLockFileFormat.PackagesLockFileVersion"/>. /// </summary> /// <param name="dgSpec">The <see cref="DependencyGraphSpec"/> for the new project defintion.</param> /// <param name="nuGetLockFile">The current <see cref="PackagesLockFile"/>.</param> /// <returns>True if the lock file is valid false otherwise. </returns> public static bool IsLockFileStillValid(DependencyGraphSpec dgSpec, PackagesLockFile nuGetLockFile) { // Current tools know how to read only previous formats including the current if (PackagesLockFileFormat.PackagesLockFileVersion < nuGetLockFile.Version) { return(false); } var uniqueName = dgSpec.Restore.First(); var project = dgSpec.GetProjectSpec(uniqueName); // Validate all the direct dependencies var lockFileFrameworks = nuGetLockFile.Targets .Where(t => t.TargetFramework != null) .Select(t => t.TargetFramework) .Distinct(); if (project.TargetFrameworks.Count != lockFileFrameworks.Count()) { return(false); } 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. return(false); } var directDependencies = target.Dependencies.Where(dep => dep.Type == PackageDependencyType.Direct); if (HasProjectDependencyChanged(framework.Dependencies, directDependencies)) { // lock file is out of sync return(false); } var transitiveDependenciesEnforcedByCentralVersions = target.Dependencies.Where(dep => dep.Type == PackageDependencyType.CentralTransitive).ToList(); var transitiveDependencies = target.Dependencies.Where(dep => dep.Type == PackageDependencyType.Transitive).ToList(); if (HasProjectTransitiveDependencyChanged(framework.CentralPackageVersions, transitiveDependenciesEnforcedByCentralVersions, transitiveDependencies)) { // lock file is out of sync return(false); } } // 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)) { return(false); } // Validate all P2P references foreach (var framework in project.RestoreMetadata.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. return(false); } var queue = new Queue <Tuple <string, string> >(); var visitedP2PReference = new HashSet <string>(); foreach (var projectReference in framework.ProjectReferences) { if (visitedP2PReference.Add(projectReference.ProjectUniqueName)) { var 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) { // project dependency doesn't exist in lock file. return(false); } var p2pSpec = dgSpec.GetProjectSpec(p2pUniqueName); // The package spec not found in the dg spec. This could mean that the project does not exist anymore. 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, framework.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) { if (HasP2PDependencyChanged(p2pSpecTargetFrameworkInformation.Dependencies, p2pSpecProjectRestoreMetadataFrameworkInfo.ProjectReferences, projectDependency, dgSpec)) { // P2P transitive package dependencies have changed return(false); } 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. { return(false); } } else { return(false); } } else { return(false); } } } } } return(true); }
private static bool HasP2PDependencyChanged(IEnumerable <LibraryDependency> newDependencies, IEnumerable <ProjectRestoreReference> projectRestoreReferences, LockFileDependency projectDependency, DependencyGraphSpec dgSpec) { if (projectDependency == null) { // project dependency doesn't exists in lock file so it's out of sync. return(true); } // If the count is not the same, something has changed. // Otherwise we N^2 walk below determines whether anything has changed. var transitivelyFlowingDependencies = newDependencies.Where( dep => dep.LibraryRange.TypeConstraint == LibraryDependencyTarget.Package && dep.SuppressParent != LibraryIncludeFlags.All); var transitivelyFlowingProjectReferences = projectRestoreReferences.Where(e => e.PrivateAssets != LibraryIncludeFlags.All); if (transitivelyFlowingDependencies.Count() + transitivelyFlowingProjectReferences.Count() != projectDependency.Dependencies.Count) { return(true); } foreach (var dependency in transitivelyFlowingDependencies) { var matchedP2PLibrary = projectDependency.Dependencies.FirstOrDefault(dep => StringComparer.OrdinalIgnoreCase.Equals(dep.Id, dependency.Name)); if (matchedP2PLibrary == null || !EqualityUtility.EqualsWithNullCheck(matchedP2PLibrary.VersionRange, dependency.LibraryRange.VersionRange)) { // P2P dependency has changed and lock file is out of sync. return(true); } } foreach (var dependency in transitivelyFlowingProjectReferences) { var referenceSpec = dgSpec.GetProjectSpec(dependency.ProjectUniqueName); var matchedP2PLibrary = projectDependency.Dependencies.FirstOrDefault(dep => StringComparer.OrdinalIgnoreCase.Equals(dep.Id, referenceSpec.Name)); if (matchedP2PLibrary == null) // Do not check the version for the projects, or else https://github.com/nuget/home/issues/7935 { // P2P dependency has changed and lock file is out of sync. return(true); } } // no dependency changed. Lock file is still valid. return(false); }
public static bool IsLockFileStillValid(DependencyGraphSpec dgSpec, PackagesLockFile nuGetLockFile) { var uniqueName = dgSpec.Restore.First(); var project = dgSpec.GetProjectSpec(uniqueName); // Validate all the direct dependencies 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. return(false); } var directDependencies = target.Dependencies.Where(dep => dep.Type == PackageDependencyType.Direct); if (HasProjectDependencyChanged(framework.Dependencies, directDependencies)) { // lock file is out of sync return(false); } } // Validate all P2P references foreach (var framework in project.RestoreMetadata.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. return(false); } var queue = new Queue <Tuple <string, string> >(); var visitedP2PReference = new HashSet <string>(); foreach (var projectReference in framework.ProjectReferences) { if (visitedP2PReference.Add(projectReference.ProjectUniqueName)) { var 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) { // project dependency doesn't exist in lock file. return(false); } var p2pSpec = dgSpec.GetProjectSpec(p2pUniqueName); // The package spec not found in the dg spec. This could mean that the project does not exist anymore. if (p2pSpec != null) { // This does not consider ATF. var p2pSpecTargetFrameworkInformation = NuGetFrameworkUtility.GetNearest(p2pSpec.TargetFrameworks, framework.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) { if (HasP2PDependencyChanged(p2pSpecTargetFrameworkInformation.Dependencies, p2pSpecProjectRestoreMetadataFrameworkInfo.ProjectReferences, projectDependency, dgSpec)) { // P2P transitive package dependencies have changed return(false); } foreach (var reference in p2pSpecProjectRestoreMetadataFrameworkInfo.ProjectReferences) { if (visitedP2PReference.Add(reference.ProjectUniqueName)) { var referenceSpec = dgSpec.GetProjectSpec(reference.ProjectUniqueName); queue.Enqueue(new Tuple <string, string>(referenceSpec.Name, reference.ProjectUniqueName)); } } } else // This should never happen. { return(false); } } else { return(false); } } else { return(false); } } } } } return(true); }
/// <summary> /// The lock file will get invalidated if one or more of the below are true /// 1. The target frameworks list of the current project was updated. /// 2. The runtime list of the current project waw updated. /// 3. The packages of the current project were updated. /// 4. The packages of the dependent projects were updated. /// 5. The framework list of the dependent projects were updated with frameworks incompatible with the main project framework. /// 6. If the version of the <paramref name="nuGetLockFile"/> is larger than the current tools <see cref="PackagesLockFileFormat.PackagesLockFileVersion"/>. /// </summary> /// <param name="dgSpec">The <see cref="DependencyGraphSpec"/> for the new project defintion.</param> /// <param name="nuGetLockFile">The current <see cref="PackagesLockFile"/>.</param> /// <returns>Returns LockFileValidityWithInvalidReasons object with IsValid set to true if the lock file is valid false otherwise. /// The second return type is a localized message that indicates in further detail the reason for the inconsistency.</returns> 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)); 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, 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)); }
public static bool IsLockFileStillValid(DependencyGraphSpec dgSpec, PackagesLockFile nuGetLockFile) { return(IsLockFileValid(dgSpec, nuGetLockFile).IsValid); }
public static bool IsLockFileStillValid(DependencyGraphSpec dgSpec, PackagesLockFile nuGetLockFile) { var uniqueName = dgSpec.Restore.First(); var project = dgSpec.GetProjectSpec(uniqueName); // Validate all the direct dependencies 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. return(false); } var directDependencies = target.Dependencies.Where(dep => dep.Type == PackageDependencyType.Direct); if (HasProjectDependencyChanged(framework.Dependencies, directDependencies)) { // lock file is out of sync return(false); } } // Validate all P2P references foreach (var framework in project.RestoreMetadata.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. return(false); } var queue = new Queue <string>(); var visitedP2PReference = new HashSet <string>(); foreach (var projectReference in framework.ProjectReferences) { if (visitedP2PReference.Add(projectReference.ProjectUniqueName)) { queue.Enqueue(projectReference.ProjectUniqueName); while (queue.Count > 0) { var p2pUniqueName = queue.Dequeue(); var p2pProjectName = Path.GetFileNameWithoutExtension(p2pUniqueName); var projectDependency = target.Dependencies.FirstOrDefault( dep => dep.Type == PackageDependencyType.Project && PathUtility.GetStringComparerBasedOnOS().Equals(dep.Id, p2pProjectName)); if (projectDependency == null) { // project dependency doesn't exist in lock file. return(false); } var p2pSpec = dgSpec.GetProjectSpec(p2pUniqueName); // ignore if this p2p packageSpec not found in DGSpec, It'll fail restore with different error at later stage if (p2pSpec != null) { var p2pSpecTarget = NuGetFrameworkUtility.GetNearest(p2pSpec.TargetFrameworks, framework.FrameworkName, e => e.FrameworkName); // ignore if compatible framework not found for p2p reference which means current project didn't // get anything transitively from this p2p if (p2pSpecTarget != null) { if (HasP2PDependencyChanged(p2pSpecTarget.Dependencies, projectDependency)) { // P2P transitive package dependencies has changed return(false); } var p2pSpecProjectRefTarget = p2pSpec.RestoreMetadata.TargetFrameworks.FirstOrDefault( t => PathUtility.GetStringComparerBasedOnOS().Equals(p2pSpecTarget.FrameworkName.GetShortFolderName(), t.FrameworkName.GetShortFolderName())); if (p2pSpecProjectRefTarget != null) { foreach (var reference in p2pSpecProjectRefTarget.ProjectReferences) { if (visitedP2PReference.Add(reference.ProjectUniqueName)) { queue.Enqueue(reference.ProjectUniqueName); } } } } } } } } } return(true); }