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);
}