Skip to content

Commit

Permalink
[release/9.0.1xx] ResolvePackageReferences: PackageDependenciesDesign…
Browse files Browse the repository at this point in the history
…Time inherits transitive package DiagnosticLevel (#43262)

Co-authored-by: Andy Zivkovic <Andy.Zivkovic@microsoft.com>
  • Loading branch information
github-actions[bot] and zivkan committed Sep 8, 2024
1 parent b7b4038 commit c80faea
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 21 deletions.
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
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()
{
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

0 comments on commit c80faea

Please sign in to comment.