Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ResolvePackageReferences: PackageDependenciesDesignTime inherits transitive package DiagnosticLevel #42546

Merged
merged 2 commits into from
Aug 14, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ public void ItShouldIgnoreAllDependenciesWithTypeNotEqualToPackageOrUnresolved()

Assert.Equal(2, task.PackageDependenciesDesignTime.Count());

// Verify only
// Verify only
// top.package1 is type 'package'
// top.package2 is type 'unresolved'
//
//
// top.package3 is type 'unknown'. Should not appear in the list
var item1 = task.PackageDependenciesDesignTime[0];
Assert.Equal("top.package1/1.0.0", item1.ItemSpec);
Expand Down Expand Up @@ -372,14 +372,38 @@ public void ItShouldOnlyReturnTopLevelPackages()

var item1 = task.PackageDependenciesDesignTime[0];
Assert.Equal("top.package1/1.0.0", item1.ItemSpec);
Assert.Equal("Error", item1.GetMetadata("DiagnosticLevel"));

var item2 = task.PackageDependenciesDesignTime[1];
Assert.Equal("top.package2/1.0.0", item2.ItemSpec);
Assert.Equal(string.Empty, item2.GetMetadata("DiagnosticLevel"));

var item3 = task.PackageDependenciesDesignTime[2];
Assert.Equal("top.package3/1.0.0", item3.ItemSpec);
Assert.Equal("Warning", item3.GetMetadata("DiagnosticLevel"));
}

/// <summary>
/// Create a NuGet assets file for a project with 3 direct references, and 3 transitive references.
/// </summary>
/// <param name="testRoot">Path to the NuGet global packages folder that the assets file will refer to.</param>
/// <param name="package2Type">Whether "top.package2" is a "package" (default) or "project" reference.</param>
/// <param name="package3Type">Whether "top.package2" is a "package" (default) or "project" reference.</param>
/// <returns>The JSON string representing the project.</returns>
/// <remarks>
/// The reference structure is as follows: <br/>
/// <c>proj<br/>
/// + top.package1<br/>
/// | + dependent.package1<br/>
/// | | + dependent.package3<br/>
/// | + dependent.package2<br/>
/// + top.package2<br/>
/// + top.package2<br/>
/// - + dependent.package1<br/>
/// - - + dependent.package3<br/>
/// </c>
/// dependent.package2 has an error message, and dependent.package3 has a warning.
/// </remarks>
private string CreateBasicProjectAssetsFile(string testRoot, string package2Type = "package", string package3Type = "package")
{
var json =
Expand Down Expand Up @@ -587,7 +611,29 @@ private string CreateBasicProjectAssetsFile(string testRoot, string package2Type
}
}
}
}
},
"logs": [
{
"code": "NU1001",
"level": "Error",
"warningLevel": 2,
"message": "some warning message",
"libraryId": "dependent.package2",
"targetGraphs": [
"net6.0"
]
},
{
"code": "NU1002",
"level": "Warning",
"warningLevel": 1,
"message": "some warning message",
"libraryId": "dependent.package3",
"targetGraphs": [
"net6.0"
]
}
]
}
""";
return json.Replace("PACKAGE2_TYPE", package2Type)
Expand Down
104 changes: 86 additions & 18 deletions src/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageAssets.cs
zivkan marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,7 @@ private void WritePackageDependenciesDesignTime()

// Scan PackageDependencies to build the set of packages in our target.
var allowItemSpecs = GetPackageDependencies();
var diagnosticLevels = GetPackageDiagnosticLevels();

foreach (var package in _lockFile.Libraries)
{
Expand Down Expand Up @@ -1477,8 +1478,11 @@ private void WritePackageDependenciesDesignTime()
: itemPath) ?? string.Empty;
WriteMetadata(MetadataKeys.Path, path);

string itemDiagnosticLevel = GetPackageDiagnosticLevel(package);
var diagnosticLevel = itemDiagnosticLevel ?? string.Empty;
string diagnosticLevel = string.Empty;
if (diagnosticLevels?.TryGetValue(package.Name, out LogLevel level) ?? false)
{
diagnosticLevel = level.ToString();
}
WriteMetadata(MetadataKeys.DiagnosticLevel, diagnosticLevel);
}
}
Expand Down Expand Up @@ -1506,28 +1510,92 @@ HashSet<string> GetPackageDependencies()

static string GetPackageId(LockFileTargetLibrary package) => $"{package.Name}/{package.Version.ToNormalizedString()}";

string GetPackageDiagnosticLevel(LockFileLibrary package)
Dictionary<string, LogLevel> GetPackageDiagnosticLevels()
zivkan marked this conversation as resolved.
Show resolved Hide resolved
{
string target = _task.TargetFramework ?? "";
if (_lockFile.LogMessages.Count == 0)
{
return null;
}

var packageReverseDependencies = GetReverseDependencies();
var result = new Dictionary<string, LogLevel>();
for (int i = 0; i < _lockFile.LogMessages.Count; i++)
{
var message = _lockFile.LogMessages[i];
if (string.IsNullOrEmpty(message.LibraryId)) continue;

var messages = _lockFile.LogMessages.Where(log =>
log.LibraryId == package.Name &&
log.TargetGraphs.Any(tg =>
if (message.TargetGraphs is null || message.TargetGraphs.Count == 0 || message.TargetGraphs.Any(ForCurrentTargetFramework))
{
var parts = tg.Split(LockFile.DirectorySeparatorChar);
var parsedTargetGraph = NuGetFramework.Parse(parts[0]);
var alias = _lockFile.PackageSpec.TargetFrameworks
.FirstOrDefault(tf => tf.FrameworkName == parsedTargetGraph)
?.TargetAlias ?? tg;
return alias == target;
}));

if (!messages.Any())
ApplyDiagnosticLevel(message.LibraryId, message.Level, result, packageReverseDependencies);
}
}

return result;

Dictionary<string, HashSet<string>> GetReverseDependencies()
{
return string.Empty;
var packageReverseDependencies = new Dictionary<string, HashSet<string>>(_compileTimeTarget.Libraries.Count, StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < _compileTimeTarget.Libraries.Count; i++)
{
var parentPackage = _compileTimeTarget.Libraries[i];
if (string.IsNullOrEmpty(parentPackage.Name)) continue;

if (!packageReverseDependencies.ContainsKey(parentPackage.Name))
{
packageReverseDependencies[parentPackage.Name] = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}

for (int j = 0; j < parentPackage.Dependencies.Count; j++)
{
var dependency = parentPackage.Dependencies[j].Id;

if (!packageReverseDependencies.TryGetValue(dependency, out HashSet<string> parentPackages))
{
parentPackages = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
packageReverseDependencies[dependency] = parentPackages;
}

parentPackages.Add(parentPackage.Name);
}
}
return packageReverseDependencies;
}

bool ForCurrentTargetFramework(string targetFramework)
{
var parts = targetFramework.Split(LockFile.DirectorySeparatorChar);
var parsedTargetGraph = NuGetFramework.Parse(parts[0]);
var alias = _lockFile.PackageSpec.TargetFrameworks
.FirstOrDefault(tf => tf.FrameworkName == parsedTargetGraph)
?.TargetAlias ?? targetFramework;
return alias == _task.TargetFramework;
}

return messages.Max(log => log.Level).ToString();
void ApplyDiagnosticLevel(string package, LogLevel messageLevel, Dictionary<string, LogLevel> diagnosticLevels, Dictionary<string, HashSet<string>> reverseDependencies)
{
if (!reverseDependencies.TryGetValue(package, out HashSet<string> parentPackages))
{
// The package is not used in the current TargetFramework
return;
}

if (diagnosticLevels.TryGetValue(package, out LogLevel cachedLevel))
{
// Only continue if we need to increase the level
if (cachedLevel >= messageLevel)
{
return;
}
}

diagnosticLevels[package] = messageLevel;

// Flow changes upwards, towards the direct PackageReference
foreach (var parentPackage in parentPackages)
{
ApplyDiagnosticLevel(parentPackage, messageLevel, diagnosticLevels, reverseDependencies);
}
}
}

static DependencyType GetDependencyType(string dependencyTypeString)
Expand Down
Loading