Skip to content

Commit

Permalink
introduce types to make the code easier to read
Browse files Browse the repository at this point in the history
  • Loading branch information
brettfo committed Sep 24, 2024
1 parent 3ff2a8f commit 0a38a99
Showing 1 changed file with 112 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ public class NuGetMSBuildBinaryLogComponentDetector : FileComponentDetector
["ResolvedIjwHostPack"] = ("NuGetPackageId", "NuGetPackageVersion"),
};

// the items listed below represent top-level property names that correspond to well-known packages
private static readonly Dictionary<string, string> PropertyPackageNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
// the items listed below represent top-level property names that correspond to well-known components
private static readonly Dictionary<string, string> ComponentPropertyNames = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["NETCoreSdkVersion"] = ".NET SDK",
};
Expand All @@ -64,7 +64,7 @@ public NuGetMSBuildBinaryLogComponentDetector(

public override int Version { get; } = 1;

private static void ProcessResolvedPackageReference(Dictionary<string, HashSet<string>> topLevelDependencies, Dictionary<string, Dictionary<string, Dictionary<int, HashSet<string>>>> projectResolvedDependencies, NamedNode node)
private static void ProcessResolvedComponentReference(ProjectPathToTopLevelComponents topLevelComponents, ProjectPathToComponents projectResolvedComponents, NamedNode node)
{
var doRemoveOperation = node is RemoveItem;
var doAddOperation = node is AddItem;
Expand All @@ -75,21 +75,17 @@ private static void ProcessResolvedPackageReference(Dictionary<string, HashSet<s
{
foreach (var child in node.Children.OfType<Item>())
{
var packageName = child.Name;
if (!topLevelDependencies.TryGetValue(projectEvaluation.ProjectFile, out var topLevel))
{
topLevel = new(StringComparer.OrdinalIgnoreCase);
topLevelDependencies[projectEvaluation.ProjectFile] = topLevel;
}
var componentName = child.Name;
var topLevel = topLevelComponents.GetComponentNames(projectEvaluation.ProjectFile);

if (doRemoveOperation)
{
topLevel.Remove(packageName);
topLevel.Remove(componentName);
}

Check warning on line 84 in src/Microsoft.ComponentDetection.Detectors/nuget/NuGetMSBuildBinaryLogComponentDetector.cs

View check run for this annotation

Codecov / codecov/patch

src/Microsoft.ComponentDetection.Detectors/nuget/NuGetMSBuildBinaryLogComponentDetector.cs#L82-L84

Added lines #L82 - L84 were not covered by tests

if (doAddOperation)
{
topLevel.Add(packageName);
topLevel.Add(componentName);
}
}
}
Expand All @@ -103,39 +99,25 @@ private static void ProcessResolvedPackageReference(Dictionary<string, HashSet<s
{
foreach (var child in node.Children.OfType<Item>())
{
var packageName = GetChildMetadataValue(child, nameMetadata);
var packageVersion = GetChildMetadataValue(child, versionMetadata);
if (packageName is not null && packageVersion is not null)
var componentName = GetChildMetadataValue(child, nameMetadata);
var componentVersion = GetChildMetadataValue(child, versionMetadata);
if (componentName is not null && componentVersion is not null)
{
var project = originalProject;
while (project is not null)
{
if (!projectResolvedDependencies.TryGetValue(project.ProjectFile, out var projectDependencies))
{
projectDependencies = new(StringComparer.OrdinalIgnoreCase);
projectResolvedDependencies[project.ProjectFile] = projectDependencies;
}

if (!projectDependencies.TryGetValue(packageName, out var packageVersionsPerEvaluation))
{
packageVersionsPerEvaluation = new();
projectDependencies[packageName] = packageVersionsPerEvaluation;
}

if (!packageVersionsPerEvaluation.TryGetValue(project.EvaluationId, out var packageVersions))
{
packageVersions = new(StringComparer.OrdinalIgnoreCase);
packageVersionsPerEvaluation[project.EvaluationId] = packageVersions;
}
var components = projectResolvedComponents.GetComponents(project.ProjectFile);
var evaluatedVersions = components.GetEvaluatedVersions(componentName);
var componentVersions = evaluatedVersions.GetComponentVersions(project.EvaluationId);

if (doRemoveOperation)
{
packageVersions.Remove(packageVersion);
componentVersions.Remove(componentVersion);
}

if (doAddOperation)
{
packageVersions.Add(packageVersion);
componentVersions.Add(componentVersion);
}

project = project.GetNearestParent<Project>();
Expand All @@ -146,9 +128,9 @@ private static void ProcessResolvedPackageReference(Dictionary<string, HashSet<s
}
}

private static void ProcessProjectProperty(Dictionary<string, Dictionary<string, Dictionary<int, HashSet<string>>>> projectResolvedDependencies, Property node)
private static void ProcessProjectProperty(ProjectPathToComponents projectResolvedComponents, Property node)
{
if (PropertyPackageNames.TryGetValue(node.Name, out var packageName))
if (ComponentPropertyNames.TryGetValue(node.Name, out var packageName))
{
string projectFile;
int evaluationId;
Expand All @@ -169,26 +151,12 @@ private static void ProcessProjectProperty(Dictionary<string, Dictionary<string,

if (projectFile is not null)
{
var packageVersion = node.Value;
if (!projectResolvedDependencies.TryGetValue(projectFile, out var projectDependencies))
{
projectDependencies = new(StringComparer.OrdinalIgnoreCase);
projectResolvedDependencies[projectFile] = projectDependencies;
}

if (!projectDependencies.TryGetValue(packageName, out var packageVersionsPerEvaluationId))
{
packageVersionsPerEvaluationId = new();
projectDependencies[packageName] = packageVersionsPerEvaluationId;
}

if (!packageVersionsPerEvaluationId.TryGetValue(evaluationId, out var packageVersions))
{
packageVersions = new(StringComparer.OrdinalIgnoreCase);
packageVersionsPerEvaluationId[evaluationId] = packageVersions;
}
var componentVersion = node.Value;
var components = projectResolvedComponents.GetComponents(projectFile);
var evaluatedVersions = components.GetEvaluatedVersions(packageName);
var componentVersions = evaluatedVersions.GetComponentVersions(evaluationId);

packageVersions.Add(packageVersion);
componentVersions.Add(componentVersion);
}
}
}
Expand Down Expand Up @@ -247,45 +215,45 @@ protected override Task OnDetectionFinishedAsync()
private void ProcessBinLog(Build buildRoot, ISingleFileComponentRecorder componentRecorder)
{
// maps a project path to a set of resolved dependencies
var projectTopLevelDependencies = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
var projectResolvedDependencies = new Dictionary<string, Dictionary<string, Dictionary<int, HashSet<string>>>>(StringComparer.OrdinalIgnoreCase);
var projectTopLevelComponents = new ProjectPathToTopLevelComponents();
var projectResolvedComponents = new ProjectPathToComponents();
buildRoot.VisitAllChildren<BaseNode>(node =>
{
switch (node)
{
case NamedNode namedNode when namedNode is AddItem or RemoveItem:
ProcessResolvedPackageReference(projectTopLevelDependencies, projectResolvedDependencies, namedNode);
ProcessResolvedComponentReference(projectTopLevelComponents, projectResolvedComponents, namedNode);
break;
case Property property when property.Parent is Folder folder && folder.Name == "Properties":
ProcessProjectProperty(projectResolvedDependencies, property);
ProcessProjectProperty(projectResolvedComponents, property);
break;
default:
break;
}
});

// dependencies were resolved per project, we need to re-arrange them to be per package/version
var projectsPerPackage = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
foreach (var projectPath in projectResolvedDependencies.Keys.OrderBy(p => p))
var projectsPerComponent = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
foreach (var projectPath in projectResolvedComponents.Keys.OrderBy(p => p))
{
if (Path.GetExtension(projectPath).Equals(".sln", StringComparison.OrdinalIgnoreCase))
{
// don't report solution files
continue;
}

var projectDependencies = projectResolvedDependencies[projectPath];
foreach (var (packageName, packageVersionsPerEvaluationid) in projectDependencies.OrderBy(p => p.Key))
var projectComponents = projectResolvedComponents[projectPath];
foreach (var (componentName, componentVersionsPerEvaluationid) in projectComponents.OrderBy(p => p.Key))
{
foreach (var packageVersions in packageVersionsPerEvaluationid.OrderBy(p => p.Key).Select(kvp => kvp.Value))
foreach (var componentVersions in componentVersionsPerEvaluationid.OrderBy(p => p.Key).Select(kvp => kvp.Value))
{
foreach (var packageVersion in packageVersions.OrderBy(v => v))
foreach (var componentVersion in componentVersions.OrderBy(v => v))
{
var key = $"{packageName}/{packageVersion}";
if (!projectsPerPackage.TryGetValue(key, out var projectPaths))
var key = $"{componentName}/{componentVersion}";
if (!projectsPerComponent.TryGetValue(key, out var projectPaths))
{
projectPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
projectsPerPackage[key] = projectPaths;
projectsPerComponent[key] = projectPaths;
}

projectPaths.Add(projectPath);
Expand All @@ -295,13 +263,13 @@ private void ProcessBinLog(Build buildRoot, ISingleFileComponentRecorder compone
}

// report it all
foreach (var packageNameAndVersion in projectsPerPackage.Keys.OrderBy(p => p))
foreach (var componentNameAndVersion in projectsPerComponent.Keys.OrderBy(p => p))
{
var projectPaths = projectsPerPackage[packageNameAndVersion];
var parts = packageNameAndVersion.Split('/', 2);
var packageName = parts[0];
var packageVersion = parts[1];
var component = new NuGetComponent(packageName, packageVersion);
var projectPaths = projectsPerComponent[componentNameAndVersion];
var parts = componentNameAndVersion.Split('/', 2);
var componentName = parts[0];
var componentVersion = parts[1];
var component = new NuGetComponent(componentName, componentVersion);
var libraryComponent = new DetectedComponent(component);
foreach (var projectPath in projectPaths)
{
Expand All @@ -311,4 +279,75 @@ private void ProcessBinLog(Build buildRoot, ISingleFileComponentRecorder compone
componentRecorder.RegisterUsage(libraryComponent);
}
}

// To make the above code easier to read, some helper types are added here. Without these, the code above would contain a type of:
// Dictionary<string, Dictionary<string, Dictionary<int, HashSet<string>>>>
// which isn't very descriptive.
private abstract class KeyedCollection<TKey, TValue> : Dictionary<TKey, TValue>
where TKey : notnull
{
protected KeyedCollection()
: base()
{
}

protected KeyedCollection(IEqualityComparer<TKey> comparer)
: base(comparer)
{
}

protected TValue GetOrAdd(TKey key, Func<TValue> valueFactory)
{
if (!this.TryGetValue(key, out var value))
{
value = valueFactory();
this[key] = value;
}

return value;
}
}

// Represents a collection of top-level components for a given project path.
private class ProjectPathToTopLevelComponents : KeyedCollection<string, HashSet<string>>
{
public HashSet<string> GetComponentNames(string projectPath) => this.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase));
}

// Represents a collection of evaluated components for a given project path.
private class ProjectPathToComponents : KeyedCollection<string, ComponentNameToEvaluatedVersions>
{
public ProjectPathToComponents()
: base(StringComparer.OrdinalIgnoreCase)
{
}

public ComponentNameToEvaluatedVersions GetComponents(string projectPath) => this.GetOrAdd(projectPath, () => new());
}

// Represents a collection of evaluated components for a given component name.
private class ComponentNameToEvaluatedVersions : KeyedCollection<string, EvaluationIdToComponentVersions>
{
public ComponentNameToEvaluatedVersions()
: base(StringComparer.OrdinalIgnoreCase)
{
}

public EvaluationIdToComponentVersions GetEvaluatedVersions(string componentName) => this.GetOrAdd(componentName, () => new());
}

// Represents a collection of component versions for a given evaluation id.
private class EvaluationIdToComponentVersions : KeyedCollection<int, ComponentVersions>
{
public ComponentVersions GetComponentVersions(int evaluationId) => this.GetOrAdd(evaluationId, () => new());
}

// Represents a collection of version strings.
private class ComponentVersions : HashSet<string>
{
public ComponentVersions()
: base(StringComparer.OrdinalIgnoreCase)
{
}
}
}

0 comments on commit 0a38a99

Please sign in to comment.