Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/NuGetUtility/NuGetUtility.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
<PackageReference Include="Microsoft.Build" ExcludeAssets="runtime" Version="18.0.*" />
<PackageReference Include="Microsoft.Build" ExcludeAssets="runtime" Version="18.*" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
<PackageReference Include="System.IO.Hashing" Version="10.0.3" />
<PackageReference Include="System.Collections.Immutable" Version="10.0.3" />
<PackageReference Include="Microsoft.Build" ExcludeAssets="runtime" Version="18.0.*" />
<PackageReference Include="Microsoft.Build" ExcludeAssets="runtime" Version="18.*" />
<PackageReference Include="PolySharp" Version="1.15.*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
177 changes: 103 additions & 74 deletions src/NuGetUtility/ReferencedPackagesReader/ReferencedPackageReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,87 +69,29 @@ private bool TryGetInstalledPackagesFromAssetsFile(bool includeTransitive,
[NotNullWhen(true)] out IEnumerable<PackageIdentity>? installedPackages)
{
installedPackages = null;
if (!TryLoadAssetsFile(project, out ILockFile? assetsFile, out string? assetsPath))
if (!TryLoadAssetsFile(project, out ILockFile? assetsFile))
{
return false;
}

var referencedLibraries = new HashSet<ILockFileLibrary>();
List<ILockFileTarget> selectedTargets;
string? normalizedRequestedTargetFramework = NormalizeTargetFrameworkOrNull(targetFramework);
Dictionary<string, HashSet<string>> publishFalsePackagesByFramework = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
Dictionary<string, string[]> directDependenciesByFramework = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
Dictionary<string, Dictionary<string, HashSet<string>>> packageDependenciesByFramework = new Dictionary<string, Dictionary<string, HashSet<string>>>(StringComparer.OrdinalIgnoreCase);
Dictionary<string, HashSet<string>> recursiveExclusionsByInput = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal);
List<ILockFileTarget> selectedTargets = GetSelectedTargets(assetsFile, normalizedRequestedTargetFramework, targetFramework);

if (normalizedRequestedTargetFramework is not null)
{
selectedTargets = assetsFile.Targets!
.Where(t => _nuGetFrameworkUtility.IsEquivalent(normalizedRequestedTargetFramework, t.TargetFramework))
.ToList();
if (!selectedTargets.Any())
{
throw new ReferencedPackageReaderException($"Target framework {targetFramework} not found.");
}
}
else
{
selectedTargets = assetsFile.Targets!.ToList();
}
HashSet<ILockFileLibrary> referencedLibraries = new HashSet<ILockFileLibrary>();
PublishExclusionContext publishExclusionContext = new PublishExclusionContext(normalizedRequestedTargetFramework);

foreach (ILockFileTarget target in selectedTargets)
{
HashSet<ILockFileLibrary> targetReferencedLibraries =
new HashSet<ILockFileLibrary>(GetReferencedLibrariesForTarget(includeTransitive, assetsFile, target));
HashSet<ILockFileLibrary> targetReferencedLibraries = [.. GetReferencedLibrariesForTarget(includeTransitive, assetsFile, target)];

if (excludePublishFalse)
{
string targetFrameworkForPublishMetadata = normalizedRequestedTargetFramework ?? _nuGetFrameworkUtility.Normalize(target.TargetFramework);
string targetFrameworkCacheKey = targetFrameworkForPublishMetadata ?? string.Empty;

// Remove packages with Publish=false metadata from the evaluated PackageReferences for this target only.
if (!publishFalsePackagesByFramework.TryGetValue(targetFrameworkCacheKey, out HashSet<string>? cachedPublishFalsePackages))
{
cachedPublishFalsePackages = GetPackagesExcludedFromPublish(project, targetFrameworkForPublishMetadata);
publishFalsePackagesByFramework[targetFrameworkCacheKey] = cachedPublishFalsePackages;
}

HashSet<string> excludedPackages = new HashSet<string>(cachedPublishFalsePackages, StringComparer.OrdinalIgnoreCase);
if (includeTransitive && excludedPackages.Any())
{
if (!directDependenciesByFramework.TryGetValue(targetFrameworkCacheKey, out string[]? directDependenciesForFramework))
{
directDependenciesForFramework = GetDirectDependenciesForTargets(assetsFile, new[] { target }).ToArray();
directDependenciesByFramework[targetFrameworkCacheKey] = directDependenciesForFramework;
}

if (!packageDependenciesByFramework.TryGetValue(targetFrameworkCacheKey, out Dictionary<string, HashSet<string>>? packageDependencies))
{
packageDependencies = _assetsPackageDependencyReader.GetPackageDependenciesForTargetFramework(
assetsPath!,
targetFrameworkCacheKey);
packageDependenciesByFramework[targetFrameworkCacheKey] = packageDependencies;
}

if (packageDependencies.Count > 0)
{
string recursiveExclusionCacheKey = BuildExclusionCacheKey(
targetFrameworkCacheKey,
directDependenciesForFramework,
excludedPackages);

if (!recursiveExclusionsByInput.TryGetValue(recursiveExclusionCacheKey, out HashSet<string>? recursivelyExcludedPackages))
{
recursivelyExcludedPackages = GetPackagesExcludedFromPublishDependencyPaths(
packageDependencies,
directDependenciesForFramework,
excludedPackages);
recursiveExclusionsByInput[recursiveExclusionCacheKey] = recursivelyExcludedPackages;
}

excludedPackages.UnionWith(recursivelyExcludedPackages);
}
}
HashSet<string> excludedPackages = GetExcludedPackagesForTarget(
project,
assetsFile,
target,
includeTransitive,
publishExclusionContext);

targetReferencedLibraries.RemoveWhere(library => excludedPackages.Contains(library.Name));
}
Expand All @@ -161,6 +103,85 @@ private bool TryGetInstalledPackagesFromAssetsFile(bool includeTransitive,
return true;
}

private List<ILockFileTarget> GetSelectedTargets(ILockFile assetsFile,
string? normalizedRequestedTargetFramework,
string? targetFramework)
{
if (normalizedRequestedTargetFramework is null)
{
return assetsFile.Targets.ToList();
}

List<ILockFileTarget> selectedTargets = assetsFile.Targets
.Where(t => _nuGetFrameworkUtility.IsEquivalent(normalizedRequestedTargetFramework, t.TargetFramework))
.ToList();
if (!selectedTargets.Any())
{
throw new ReferencedPackageReaderException($"Target framework {targetFramework} not found.");
}

return selectedTargets;
}

private HashSet<string> GetExcludedPackagesForTarget(IProject project,
ILockFile assetsFile,
ILockFileTarget target,
bool includeTransitive,
PublishExclusionContext context)
{
string targetFrameworkForPublishMetadata = context.NormalizedRequestedTargetFramework ?? _nuGetFrameworkUtility.Normalize(target.TargetFramework);
string targetFrameworkCacheKey = targetFrameworkForPublishMetadata ?? string.Empty;

// Remove packages with Publish=false metadata from the evaluated PackageReferences for this target only.
if (!context.PublishFalsePackagesByFramework.TryGetValue(targetFrameworkCacheKey, out HashSet<string>? cachedPublishFalsePackages))
{
cachedPublishFalsePackages = GetPackagesExcludedFromPublish(project, targetFrameworkForPublishMetadata);
context.PublishFalsePackagesByFramework[targetFrameworkCacheKey] = cachedPublishFalsePackages;
}

HashSet<string> excludedPackages = new HashSet<string>(cachedPublishFalsePackages, StringComparer.OrdinalIgnoreCase);
if (!includeTransitive || !excludedPackages.Any())
{
return excludedPackages;
}

if (!context.DirectDependenciesByFramework.TryGetValue(targetFrameworkCacheKey, out HashSet<string>? directDependenciesForFramework))
{
directDependenciesForFramework = GetDirectDependenciesForTargets(assetsFile, [target]);
context.DirectDependenciesByFramework[targetFrameworkCacheKey] = directDependenciesForFramework;
}

if (!context.PackageDependenciesByFramework.TryGetValue(targetFrameworkCacheKey, out Dictionary<string, HashSet<string>>? packageDependencies))
{
packageDependencies = _assetsPackageDependencyReader.GetPackageDependenciesForTargetFramework(
assetsFile,
targetFrameworkCacheKey);
context.PackageDependenciesByFramework[targetFrameworkCacheKey] = packageDependencies;
}

if (packageDependencies.Count == 0)
{
return excludedPackages;
}

string recursiveExclusionCacheKey = BuildExclusionCacheKey(
targetFrameworkCacheKey,
directDependenciesForFramework,
excludedPackages);

if (!context.RecursiveExclusionsByInput.TryGetValue(recursiveExclusionCacheKey, out HashSet<string>? recursivelyExcludedPackages))
{
recursivelyExcludedPackages = GetPackagesExcludedFromPublishDependencyPaths(
packageDependencies,
directDependenciesForFramework,
excludedPackages);
context.RecursiveExclusionsByInput[recursiveExclusionCacheKey] = recursivelyExcludedPackages;
}

excludedPackages.UnionWith(recursivelyExcludedPackages);
return excludedPackages;
}

private static IEnumerable<ILockFileLibrary> GetReferencedLibrariesForTarget(bool includeTransitive,
ILockFile assetsFile,
ILockFileTarget target)
Expand Down Expand Up @@ -191,7 +212,7 @@ private static ITargetFrameworkInformation GetTargetFrameworkInformation(ILockFi
}
}

private static IEnumerable<string> GetDirectDependenciesForTargets(ILockFile assetsFile,
private static HashSet<string> GetDirectDependenciesForTargets(ILockFile assetsFile,
IEnumerable<ILockFileTarget> selectedTargets)
{
HashSet<string> directDependencies = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
Expand Down Expand Up @@ -288,13 +309,11 @@ private static HashSet<string> GetReachablePackages(Dictionary<string, HashSet<s
}

private bool TryLoadAssetsFile(IProject project,
[NotNullWhen(true)] out ILockFile? assetsFile,
out string? assetsPath)
[NotNullWhen(true)] out ILockFile? assetsFile)
{
if (!project.TryGetAssetsPath(out string projectAssetsPath))
{
assetsFile = null;
assetsPath = null;
return false;
}

Expand All @@ -311,7 +330,6 @@ private bool TryLoadAssetsFile(IProject project,
$"Failed to validate project assets for project {project.FullPath}");
}

assetsPath = projectAssetsPath;
return true;
}

Expand All @@ -334,5 +352,16 @@ private static HashSet<string> GetPackagesExcludedFromPublish(IProject project,

return excludedPackages;
}

private sealed record PublishExclusionContext(string? NormalizedRequestedTargetFramework)
{
public Dictionary<string, HashSet<string>> PublishFalsePackagesByFramework { get; } = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);

public Dictionary<string, HashSet<string>> DirectDependenciesByFramework { get; } = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);

public Dictionary<string, Dictionary<string, HashSet<string>>> PackageDependenciesByFramework { get; } = new Dictionary<string, Dictionary<string, HashSet<string>>>(StringComparer.OrdinalIgnoreCase);

public Dictionary<string, HashSet<string>> RecursiveExclusionsByInput { get; } = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root

namespace NuGetUtility.Wrapper.NuGetWrapper.Packaging.Core
{
public interface IPackageDependency
{
string Id { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root

using NuGet.Packaging.Core;

namespace NuGetUtility.Wrapper.NuGetWrapper.Packaging.Core
{
internal class WrappedPackageDependency : IPackageDependency
{
public WrappedPackageDependency(PackageDependency dependency)
{
Id = dependency.Id;
}
public string Id { get; }
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The license conditions are provided in the LICENSE file located in the project root

using System.Diagnostics;
using NuGet.ProjectModel;
using NuGetUtility.Wrapper.NuGetWrapper.Frameworks;

namespace NuGetUtility.Wrapper.NuGetWrapper.ProjectModel
Expand Down Expand Up @@ -42,33 +41,22 @@ public AssetsPackageDependencyReader(INuGetFrameworkUtility nuGetFrameworkUtilit
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="assetsPath"/> or <paramref name="normalizedTargetFramework"/> is <see langword="null"/>.
/// </exception>
public Dictionary<string, HashSet<string>> GetPackageDependenciesForTargetFramework(string assetsPath, string normalizedTargetFramework)
public Dictionary<string, HashSet<string>> GetPackageDependenciesForTargetFramework(ILockFile lockFile, string normalizedTargetFramework)
{
if (assetsPath is null)
{
throw new ArgumentNullException(nameof(assetsPath));
}

if (normalizedTargetFramework is null)
{
throw new ArgumentNullException(nameof(normalizedTargetFramework));
}

if (!File.Exists(assetsPath))
{
return new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
}

try
{
LockFile lockFile = new LockFileFormat().Read(assetsPath);
return BuildDependencyMapFromAssetsFile(lockFile, normalizedTargetFramework);
}
catch (IOException exception)
{
Trace.TraceWarning(
"Failed to analyze transitive Publish=false exclusions due to I/O error. AssetsPath={0}, TargetFramework={1}, Exception={2}",
assetsPath,
lockFile.Path,
normalizedTargetFramework,
exception);
return new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
Expand All @@ -77,25 +65,25 @@ public Dictionary<string, HashSet<string>> GetPackageDependenciesForTargetFramew
{
Trace.TraceWarning(
"Failed to analyze transitive Publish=false exclusions due to invalid assets data. AssetsPath={0}, TargetFramework={1}, Exception={2}",
assetsPath,
lockFile.Path,
normalizedTargetFramework,
exception);
return new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
}
}

private Dictionary<string, HashSet<string>> BuildDependencyMapFromAssetsFile(LockFile lockFile, string requestedTargetFramework)
private Dictionary<string, HashSet<string>> BuildDependencyMapFromAssetsFile(ILockFile lockFile, string requestedTargetFramework)
{
Dictionary<string, HashSet<string>> packageDependencies = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);

foreach (LockFileTarget target in lockFile.Targets)
foreach (ILockFileTarget target in lockFile.Targets)
{
if (!_nuGetFrameworkUtility.IsEquivalent(requestedTargetFramework, new WrappedNuGetFramework(target.TargetFramework)))
if (!_nuGetFrameworkUtility.IsEquivalent(requestedTargetFramework, target.TargetFramework))
{
continue;
}

foreach (LockFileTargetLibrary library in target.Libraries)
foreach (ILockFileTargetLibrary library in target.Libraries)
{
if (!string.Equals(library.Type, PackageTypeIdentifier, StringComparison.OrdinalIgnoreCase))
{
Expand All @@ -120,9 +108,8 @@ private Dictionary<string, HashSet<string>> BuildDependencyMapFromAssetsFile(Loc
packageDependencies[packageNameValue] = dependencies;
}

foreach (NuGet.Packaging.Core.PackageDependency dependency in library.Dependencies)
foreach (string dependencyName in library.Dependencies.Select(d => d.Id))
{
string dependencyName = dependency.Id;
dependencies.Add(dependencyName);
if (!packageDependencies.ContainsKey(dependencyName))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ namespace NuGetUtility.Wrapper.NuGetWrapper.ProjectModel
{
public interface IAssetsPackageDependencyReader
{
Dictionary<string, HashSet<string>> GetPackageDependenciesForTargetFramework(string assetsPath, string normalizedTargetFramework);
Dictionary<string, HashSet<string>> GetPackageDependenciesForTargetFramework(ILockFile lockFile, string normalizedTargetFramework);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public interface ILockFile
{
bool TryGetErrors(out string[] errors);
IPackageSpec PackageSpec { get; }
IEnumerable<ILockFileTarget>? Targets { get; }
IEnumerable<ILockFileTarget> Targets { get; }
string Path { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ namespace NuGetUtility.Wrapper.NuGetWrapper.ProjectModel
public interface ILockFileTarget
{
INuGetFramework TargetFramework { get; }
IEnumerable<ILockFileLibrary> Libraries { get; }
IEnumerable<ILockFileTargetLibrary> Libraries { get; }
}
}
Loading
Loading