internal async Task GetInstalledAndTransitivePackagesAsync()

in src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/Projects/PackageReferenceProject.cs [120:288]


        internal async Task<ProjectPackages> GetInstalledAndTransitivePackagesAsync(bool includeTransitivePackages, bool includeTransitiveOrigins, CancellationToken token)
        {
            token.ThrowIfCancellationRequested();

            (PackageSpec packageSpec, string assetsFilePath) = await GetCurrentPackageSpecAndAssetsFilePathSafeAsync(token);

            if (packageSpec == null) // null means project is not nominated
            {
                IsInstalledAndTransitiveComputationNeeded = true;

                return EmptyProjectPackages;
            }

            IList<LockFileTarget> targetsList = null;
            T installedPackages;
            T transitivePackages = default;
            Dictionary<string, TransitiveEntry> transitiveOrigins = default;
            if (IsInstalledAndTransitiveComputationNeeded)
            {
                // clear the transitive packages cache, since we don't know when a dependency has been removed
                installedPackages = new T();
                transitivePackages = new T();
                transitiveOrigins = new Dictionary<string, TransitiveEntry>();
                targetsList = await GetTargetsListAsync(assetsFilePath, token);
            }
            else
            {
                if (InstalledPackages == null)
                {
                    installedPackages = new T();
                }
                else
                {
                    // Make a copy of the caches to prevent concurrency issues.
                    lock (_installedAndTransitivePackagesLock)
                    {
                        installedPackages = GetCollectionCopy(InstalledPackages);
                    }
                }

                if (includeTransitivePackages)
                {
                    if (TransitivePackages == null)
                    {
                        transitivePackages = new T();
                        transitiveOrigins = new Dictionary<string, TransitiveEntry>();
                    }
                    else
                    {
                        // Make a copy of the caches to prevent concurrency issues.
                        lock (_installedAndTransitivePackagesLock)
                        {
                            transitivePackages = GetCollectionCopy(TransitivePackages);
                        }
                    }
                }
            }

            // get installed packages
            List<PackageReference> calculatedInstalledPackages = packageSpec
                .TargetFrameworks
                .SelectMany(f => ResolvedInstalledPackagesList(f.Dependencies, f.FrameworkName, targetsList, installedPackages))
                .GroupBy(p => p.PackageIdentity)
                .Select(g => g.OrderBy(p => p.TargetFramework, FrameworkSorter).First())
                .ToList();

            // get transitive packages
            IEnumerable<PackageReference> calculatedTransitivePackages = Enumerable.Empty<PackageReference>();
            if (includeTransitivePackages)
            {
                if (targetsList == null)
                {
                    targetsList = await GetTargetsListAsync(assetsFilePath, token);
                }

                calculatedTransitivePackages = packageSpec
                    .TargetFrameworks
                    .SelectMany(f => ResolvedTransitivePackagesList(f.FrameworkName, targetsList, installedPackages, transitivePackages))
                    .GroupBy(p => p.PackageIdentity)
                    .Select(g => g.OrderBy(p => p.TargetFramework, FrameworkSorter).First());
            }

            IEnumerable<TransitivePackageReference> transitivePackagesWithOrigins = Enumerable.Empty<TransitivePackageReference>();
            if (includeTransitivePackages)
            {
                if (includeTransitiveOrigins)
                {
                    // Compute Transitive Origins
                    if (IsInstalledAndTransitiveComputationNeeded // Cache invalidation
                        || TransitiveOriginsCache == null // If any data race left the cache as null
                        || (!TransitiveOriginsCache.Any() && calculatedTransitivePackages.Any())) // We have transitive packages, but no transitive origins and the call is requesting transitive origins
                    {
                        // Special case: Installed and Transitive lists (<see cref="InstalledPackages" />, <see cref="TransitivePackages" /> respectively) are populated,
                        // but Transitive Origins Cache <see cref="TransitiveOriginsCache" /> is not populated.
                        // Then, we need targets section from project.assets.json file on disk to populate Transitive Origins cache
                        if (targetsList == null)
                        {
                            targetsList = await GetTargetsListAsync(assetsFilePath, token);
                        }

                        // If the project has project references, we need to compute transitive origins for their packages
                        List<PackageReference> projectReferences = packageSpec
                            .TargetFrameworks
                            .SelectMany(f => GetProjectPackageReferences(f.FrameworkName, targetsList))
                            .GroupBy(p => p.PackageIdentity)
                            .Select(g => g.OrderBy(p => p.TargetFramework, FrameworkSorter).First())
                            .ToList();

                        List<PackageReference> calculatedLibraryReferences = new List<PackageReference>(projectReferences);
                        calculatedLibraryReferences.AddRange(calculatedInstalledPackages);

                        // Compute Transitive Origins
                        transitiveOrigins = calculatedTransitivePackages.Any() ? ComputeTransitivePackageOrigins(calculatedLibraryReferences, targetsList, token) : new Dictionary<string, TransitiveEntry>();
                    }
                    else
                    {
                        lock (_transitiveOriginsLock)
                        {
                            // Make a copy of the cache to prevent concurrency issues.
                            transitiveOrigins = new Dictionary<string, TransitiveEntry>(TransitiveOriginsCache);
                        }
                    }

                    // 4. Return cached result for specific transitive dependency
                    transitivePackagesWithOrigins = calculatedTransitivePackages
                        .Select(packageRef =>
                        {
                            transitiveOrigins.TryGetValue(packageRef.PackageIdentity.Id, out TransitiveEntry cacheEntry);
                            return MergeTransitiveOrigin(packageRef, cacheEntry);
                        });

                    lock (_transitiveOriginsLock)
                    {
                        TransitiveOriginsCache = transitiveOrigins;
                    }
                }
                else
                {
                    // Get Transitive packages without Transitive Origins
                    transitivePackagesWithOrigins = calculatedTransitivePackages
                        .Select(packageRef => new TransitivePackageReference(packageRef));
                }
            }

            List<TransitivePackageReference> transitivePkgsResult = transitivePackagesWithOrigins.ToList(); // Materialize results before setting IsInstalledAndTransitiveComputationNeeded flag to false

            // Refresh cache
            lock (_installedAndTransitivePackagesLock)
            {
                InstalledPackages = installedPackages;
            }
            // if includeTransitivePackages, update the cache with the new transitive packages information
            // or if IsInstalledAndTransitiveComputationNeeded, clear the transitive packages cache, since we don't know when a dependency has been removed
            if (includeTransitivePackages || IsInstalledAndTransitiveComputationNeeded)
            {
                lock (_transitiveOriginsLock)
                {
                    TransitiveOriginsCache = transitiveOrigins;
                }
                lock (_installedAndTransitivePackagesLock)
                {
                    TransitivePackages = transitivePackages;
                }
            }

            IsInstalledAndTransitiveComputationNeeded = false;

            return new ProjectPackages(calculatedInstalledPackages, transitivePkgsResult);
        }