/// <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));
        }
Example #2
0
        private static bool BuildTargetFrameworkNode(PackageSpec packageSpec, KeyValuePair <string, JToken> targetFramework, string filePath)
        {
            var frameworkName = GetFramework(targetFramework.Key);

            var properties          = targetFramework.Value.Value <JObject>();
            var assetTargetFallback = GetBoolOrFalse(properties, "assetTargetFallback", filePath);

            var importFrameworks = GetImports(properties, packageSpec);

            // If a fallback framework exists, update the framework to contain both.
            var updatedFramework = frameworkName;

            if (importFrameworks.Count != 0)
            {
                if (assetTargetFallback)
                {
                    updatedFramework = new AssetTargetFallbackFramework(frameworkName, importFrameworks);
                }
                else
                {
                    updatedFramework = new FallbackFramework(frameworkName, importFrameworks);
                }
            }

            var targetFrameworkInformation = new TargetFrameworkInformation
            {
                FrameworkName              = updatedFramework,
                Dependencies               = new List <LibraryDependency>(),
                Imports                    = importFrameworks,
                Warn                       = GetWarnSetting(properties),
                AssetTargetFallback        = assetTargetFallback,
                RuntimeIdentifierGraphPath = GetRuntimeIdentifierGraphPath(properties)
            };

            PopulateDependencies(
                packageSpec.FilePath,
                targetFrameworkInformation.Dependencies,
                properties,
                "dependencies",
                isGacOrFrameworkReference: false);

            PopulateDownloadDependencies(
                targetFrameworkInformation.DownloadDependencies,
                properties,
                packageSpec.FilePath);

            var frameworkAssemblies = new List <LibraryDependency>();

            PopulateDependencies(
                packageSpec.FilePath,
                frameworkAssemblies,
                properties,
                "frameworkAssemblies",
                isGacOrFrameworkReference: true);

            PopulateFrameworkReferences(
                targetFrameworkInformation.FrameworkReferences,
                properties,
                "frameworkReferences",
                packageSpec.FilePath);

            frameworkAssemblies.ForEach(d => targetFrameworkInformation.Dependencies.Add(d));

            packageSpec.TargetFrameworks.Add(targetFrameworkInformation);

            return(true);
        }
Example #3
0
        private static bool BuildTargetFrameworkNode(PackageSpec packageSpec, KeyValuePair<string, JToken> targetFramework)
        {
            var frameworkName = GetFramework(targetFramework.Key);

            // If it's not unsupported then keep it
            if (frameworkName == NuGetFramework.UnsupportedFramework)
            {
                // REVIEW: Should we skip unsupported target frameworks
                return false;
            }

            var targetFrameworkInformation = new TargetFrameworkInformation
                {
                    FrameworkName = frameworkName,
                    Dependencies = new List<LibraryDependency>()
                };

            var properties = targetFramework.Value.Value<JObject>();

            PopulateDependencies(
                packageSpec.FilePath,
                targetFrameworkInformation.Dependencies,
                properties,
                "dependencies",
                isGacOrFrameworkReference: false);

            var frameworkAssemblies = new List<LibraryDependency>();
            PopulateDependencies(
                packageSpec.FilePath,
                frameworkAssemblies,
                properties,
                "frameworkAssemblies",
                isGacOrFrameworkReference: true);

            frameworkAssemblies.ForEach(d => targetFrameworkInformation.Dependencies.Add(d));

            packageSpec.TargetFrameworks.Add(targetFrameworkInformation);

            return true;
        }