-
Notifications
You must be signed in to change notification settings - Fork 122
Reconcile bare/rich component identities in ComponentsFound #1760
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -34,56 +34,142 @@ public TypedComponent GetComponent(string componentId) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public IEnumerable<DetectedComponent> GetDetectedComponents() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IEnumerable<DetectedComponent> detectedComponents; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (this.singleFileRecorders == null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detectedComponents = this.singleFileRecorders.Values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .SelectMany(singleFileRecorder => singleFileRecorder.GetDetectedComponents().Values) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .GroupBy(x => x.Component.Id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .Select(grouping => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // We pick a winner here -- any stateful props could get lost at this point. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var winningDetectedComponent = grouping.First(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var allComponents = this.singleFileRecorders.Values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .SelectMany(singleFileRecorder => singleFileRecorder.GetDetectedComponents().Values); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HashSet<string> mergedLicenses = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HashSet<ActorInfo> mergedSuppliers = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // When both rich and bare entries exist for the same BaseId, rich entries are used as merge targets for bare entries. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var reconciledComponents = new List<DetectedComponent>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var component in grouping.Skip(1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| winningDetectedComponent.ContainerDetailIds.UnionWith(component.ContainerDetailIds); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var baseIdGroup in allComponents.GroupBy(x => x.Component.BaseId)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var richEntries = new List<DetectedComponent>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var bareEntries = new List<DetectedComponent>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Defensive: merge in case different file recorders set different values for the same component. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (component.LicensesConcluded != null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mergedLicenses ??= new HashSet<string>(winningDetectedComponent.LicensesConcluded ?? [], StringComparer.OrdinalIgnoreCase); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mergedLicenses.UnionWith(component.LicensesConcluded); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Sub-group by full Id first: merge duplicates of the same Id (existing behavior). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var idGroup in baseIdGroup.GroupBy(x => x.Component.Id)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var merged = MergeDetectedComponentGroup(idGroup); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (component.Suppliers != null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mergedSuppliers ??= new HashSet<ActorInfo>(winningDetectedComponent.Suppliers ?? []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mergedSuppliers.UnionWith(component.Suppliers); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (merged.Component.Id == merged.Component.BaseId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bareEntries.Add(merged); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (mergedLicenses != null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| winningDetectedComponent.LicensesConcluded = mergedLicenses.Where(x => x != null).OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| richEntries.Add(merged); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (mergedSuppliers != null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (richEntries.Count > 0 && bareEntries.Count > 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Merge each bare entry's metadata into every rich entry, then drop the bare. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var bare in bareEntries) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| winningDetectedComponent.Suppliers = mergedSuppliers.Where(s => s != null).OrderBy(s => s.Name).ThenBy(s => s.Type).ToList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var rich in richEntries) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MergeComponentMetadata(source: bare, target: rich); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return winningDetectedComponent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .ToArray(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reconciledComponents.AddRange(richEntries); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // No conflict: either all rich (different Ids kept separate) or all bare. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reconciledComponents.AddRange(richEntries); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reconciledComponents.AddRange(bareEntries); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return reconciledComponents.ToArray(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Merges component-level metadata from <paramref name="source"/> into <paramref name="target"/>. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static void MergeComponentMetadata(DetectedComponent source, DetectedComponent target) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target.ContainerDetailIds.UnionWith(source.ContainerDetailIds); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var kvp in source.ContainerLayerIds) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (target.ContainerLayerIds.TryGetValue(kvp.Key, out var existingLayers)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target.ContainerLayerIds[kvp.Key] = existingLayers.Union(kvp.Value).Distinct().ToList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target.ContainerLayerIds[kvp.Key] = kvp.Value.ToList(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target.LicensesConcluded = MergeAndNormalizeLicenses(target.LicensesConcluded, source.LicensesConcluded); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target.Suppliers = MergeAndNormalizeSuppliers(target.Suppliers, source.Suppliers); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+110
to
+114
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target.LicensesConcluded = MergeAndNormalizeLicenses(target.LicensesConcluded, source.LicensesConcluded); | |
| target.Suppliers = MergeAndNormalizeSuppliers(target.Suppliers, source.Suppliers); | |
| } | |
| target.FilePaths = MergeAndNormalizeStrings(target.FilePaths, source.FilePaths, StringComparer.Ordinal); | |
| target.TargetFrameworks = MergeAndNormalizeStrings(target.TargetFrameworks, source.TargetFrameworks, StringComparer.OrdinalIgnoreCase); | |
| target.LicensesConcluded = MergeAndNormalizeLicenses(target.LicensesConcluded, source.LicensesConcluded); | |
| target.Suppliers = MergeAndNormalizeSuppliers(target.Suppliers, source.Suppliers); | |
| } | |
| private static IList<string> MergeAndNormalizeStrings(IList<string> target, IList<string> source, StringComparer comparer) | |
| { | |
| if (target == null && source == null) | |
| { | |
| return null; | |
| } | |
| var merged = new HashSet<string>(comparer); | |
| if (target != null) | |
| { | |
| merged.UnionWith(target.Where(x => x != null)); | |
| } | |
| if (source != null) | |
| { | |
| merged.UnionWith(source.Where(x => x != null)); | |
| } | |
| return merged.OrderBy(x => x, comparer).ToList(); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not applicable here. FilePaths and TargetFrameworks are not populated at this stage - they get filled downstream in GatherSetOfDetectedComponentsUnmerged from graph data. The original GetDetectedComponents code also only merged licenses, suppliers, and containers, so this is consistent with existing behavior.
Uh oh!
There was an error while loading. Please reload this page.