Skip to content

Commit 4bfb55d

Browse files
committed
Merge branch 'main' of https://github.com/microsoft/sbom-tool into copilot/fix-1093
2 parents bb2706b + c56cfe3 commit 4bfb55d

16 files changed

Lines changed: 358 additions & 69 deletions

File tree

src/Microsoft.Sbom.Api/Executors/PackageInfoJsonWriter.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ namespace Microsoft.Sbom.Api.Executors;
1919
/// </summary>
2020
public class PackageInfoJsonWriter
2121
{
22-
private readonly ManifestGeneratorProvider manifestGeneratorProvider;
22+
private readonly IManifestGeneratorProvider manifestGeneratorProvider;
2323
private readonly ILogger log;
2424

2525
public PackageInfoJsonWriter(
26-
ManifestGeneratorProvider manifestGeneratorProvider,
26+
IManifestGeneratorProvider manifestGeneratorProvider,
2727
ILogger log)
2828
{
2929
if (manifestGeneratorProvider is null)
@@ -54,7 +54,7 @@ public PackageInfoJsonWriter(
5454
return (result, errors);
5555
}
5656

57-
private async Task GenerateJson(
57+
internal async Task GenerateJson(
5858
IList<ISbomConfig> packagesArraySupportingConfigs,
5959
SbomPackage packageInfo,
6060
Channel<JsonDocWithSerializer> result,
@@ -67,14 +67,22 @@ private async Task GenerateJson(
6767
var generationResult =
6868
manifestGeneratorProvider.Get(sbomConfig.ManifestInfo).GenerateJsonDocument(packageInfo);
6969

70+
var recordedAnyDependencies = false;
71+
7072
if (generationResult?.ResultMetadata?.DependOn != null)
7173
{
7274
foreach (var dependency in generationResult?.ResultMetadata?.DependOn)
7375
{
7476
sbomConfig.Recorder.RecordPackageId(generationResult?.ResultMetadata?.EntityId, dependency);
77+
recordedAnyDependencies = true;
7578
}
7679
}
7780

81+
if (!recordedAnyDependencies)
82+
{
83+
sbomConfig.Recorder.RecordPackageId(generationResult?.ResultMetadata?.EntityId, null);
84+
}
85+
7886
await result.Writer.WriteAsync((generationResult?.Document, sbomConfig.JsonSerializer));
7987
}
8088
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.Sbom.Extensions;
6+
using Microsoft.Sbom.Extensions.Entities;
7+
8+
namespace Microsoft.Sbom.Api.Manifest;
9+
10+
public interface IManifestGeneratorProvider
11+
{
12+
public IManifestGenerator Get(ManifestInfo manifestInfo);
13+
14+
public IEnumerable<ManifestInfo> GetSupportedManifestInfos();
15+
16+
public ManifestGeneratorProvider Init();
17+
}

src/Microsoft.Sbom.Api/Manifest/ManifestGeneratorProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Microsoft.Sbom.Api.Manifest;
1313
/// Factory class that returns the correct implementation of the <see cref="IManifestGenerator"/>
1414
/// at runtime based on the 'ManifestInfo' parameter.
1515
/// </summary>
16-
public class ManifestGeneratorProvider
16+
public class ManifestGeneratorProvider : IManifestGeneratorProvider
1717
{
1818
private readonly IEnumerable<IManifestGenerator> manifestGenerators;
1919
private readonly IDictionary<string, IManifestGenerator> manifestMap = new Dictionary<string, IManifestGenerator>(StringComparer.OrdinalIgnoreCase);

src/Microsoft.Sbom.Api/Providers/FilesProviders/CGScannedExternalDocumentReferenceFileProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public CGScannedExternalDocumentReferenceFileProvider(
5151

5252
public override bool IsSupported(ProviderType providerType)
5353
{
54-
if (providerType == ProviderType.Files)
54+
if (providerType == ProviderType.Files && Configuration.ManifestToolAction == ManifestToolActions.Generate)
5555
{
5656
Log.Debug($"Using the {nameof(CGScannedExternalDocumentReferenceFileProvider)} provider for the files workflow.");
5757
return true;

src/Microsoft.Sbom.Api/Workflows/SbomAggregationWorkflow.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ private IEnumerable<AggregationSource> GetSbomsToAggregate(string artifactPath,
127127
var isValidSpdxFormat = spdxFormatDetector.TryGetSbomsWithVersion(manifestDirPath, out var detectedSboms);
128128
if (!isValidSpdxFormat)
129129
{
130-
logger.Information($"No SBOMs located in {manifestDirPath} of a recognized SPDX format.");
130+
logger.Warning($"No SBOMs located in {manifestDirPath} of a recognized SPDX format.");
131131
return null;
132132
}
133133

@@ -148,6 +148,7 @@ private async Task<bool> ValidateSourceSbomsAsync(IEnumerable<AggregationSource>
148148
configuration.IgnoreMissing = new ConfigurationSetting<bool>(source.ArtifactInfo.IgnoreMissingFiles ?? false);
149149
configuration.BuildDropPath = new ConfigurationSetting<string>(source.BuildDropPath);
150150
configuration.OutputPath = new ConfigurationSetting<string>(fileSystemUtils.JoinPaths(workingDir, $"validation-results-{identifier}.json"));
151+
configuration.ManifestInfo = new ConfigurationSetting<IList<ManifestInfo>>(new List<ManifestInfo>() { source.SbomConfig.ManifestInfo });
151152
configuration.ManifestDirPath = BuildManifestDirPathForSource(source);
152153

153154
Console.WriteLine($"Running validation for {source.SbomConfig.ManifestJsonFilePath} with identifier {identifier}. Writing output results to {configuration.OutputPath.Value}.");

src/Microsoft.Sbom.Api/Workflows/SbomGenerationWorkflow.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,8 @@ private async Task<IEnumerable<FileValidationResult>> CallGeneratorsAync(IEnumer
186186
// Write all the JSON documents from the generationResults to the manifest based on the manifestInfo.
187187
// When aggregating, only call the packages generator. A helper method might make this more compact,
188188
// but it would be less readable.
189-
if (configuration.ManifestToolAction == ManifestToolActions.Generate)
190-
{
191-
var fileGeneratorResult = await fileArrayGenerator.GenerateAsync(targetConfigs, elementsSpdxIdList);
192-
validErrors.AddRange(fileGeneratorResult.Errors);
193-
}
189+
var fileGeneratorResult = await fileArrayGenerator.GenerateAsync(targetConfigs, elementsSpdxIdList);
190+
validErrors.AddRange(fileGeneratorResult.Errors);
194191

195192
var packageGeneratorResult = await packageArrayGenerator.GenerateAsync(targetConfigs, elementsSpdxIdList);
196193
validErrors.AddRange(packageGeneratorResult.Errors);
@@ -201,11 +198,8 @@ private async Task<IEnumerable<FileValidationResult>> CallGeneratorsAync(IEnumer
201198
validErrors.AddRange(externalDocumentReferenceGeneratorResult.Errors);
202199
}
203200

204-
if (configuration.ManifestToolAction == ManifestToolActions.Generate)
205-
{
206-
var relationshipGeneratorResult = await relationshipsArrayGenerator.GenerateAsync(targetConfigs, elementsSpdxIdList);
207-
validErrors.AddRange(relationshipGeneratorResult.Errors);
208-
}
201+
var relationshipGeneratorResult = await relationshipsArrayGenerator.GenerateAsync(targetConfigs, elementsSpdxIdList);
202+
validErrors.AddRange(relationshipGeneratorResult.Errors);
209203

210204
return validErrors;
211205
}

src/Microsoft.Sbom.Common/Config/IConfiguration.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ public interface IConfiguration
120120

121121
/// <summary>
122122
/// Gets or sets a list of <see cref="SbomFile"/> files provided to us from the API.
123-
/// We won't traverse the build root path to get a list of files if this is set, and
124-
/// use the list provided here instead.
123+
/// We will use the list provided here to populate the files section in addition to
124+
/// select file providers.
125125
/// </summary>
126126
public ConfigurationSetting<IEnumerable<SbomFile>> FilesList { get; set; }
127127

src/Microsoft.Sbom.Common/Utils/CommonSPDXUtils.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Security.Cryptography;
66
using System.Text;
77
using System.Text.RegularExpressions;
8+
using Microsoft.Sbom.Contracts;
89

910
namespace Microsoft.Sbom.Common.Utils;
1011

@@ -23,6 +24,32 @@ public static class CommonSPDXUtils
2324
/// </summary>
2425
public static string GenerateSpdxPackageId(string id) => $"{Constants.SPDXRefPackage}-{GetStringHash(id)}";
2526

27+
/// <summary>
28+
/// Returns the SPDX-compliant package ID from an SbomPackage object.
29+
/// </summary>
30+
public static string GenerateSpdxPackageId(SbomPackage packageInfo)
31+
{
32+
if (packageInfo is null)
33+
{
34+
throw new ArgumentNullException(nameof(packageInfo));
35+
}
36+
37+
// Special case to preserve incoming SPDX ID's during aggregation
38+
if (packageInfo.Id is not null && packageInfo.Id.StartsWith(Constants.SPDXRefPackage, StringComparison.OrdinalIgnoreCase))
39+
{
40+
return packageInfo.Id;
41+
}
42+
43+
// Get package identity as package name and package version. If version is empty, just use package name
44+
var packageIdentity = $"{packageInfo.Type}-{packageInfo.PackageName}";
45+
if (!string.IsNullOrWhiteSpace(packageInfo.PackageVersion))
46+
{
47+
packageIdentity = string.Join("-", packageInfo.Type, packageInfo.PackageName, packageInfo.PackageVersion);
48+
}
49+
50+
return GenerateSpdxPackageId(packageInfo.Id ?? packageIdentity);
51+
}
52+
2653
/// <summary>
2754
/// Returns the SPDX-compliant file ID.
2855
/// </summary>

src/Microsoft.Sbom.Extensions.DependencyInjection/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public static IServiceCollection AddSbomTool(this IServiceCollection services, L
8787
.AddTransient<IHashCodeGenerator, HashCodeGenerator>()
8888
.AddTransient<IManifestPathConverter, SbomToolManifestPathConverter>()
8989
.AddTransient<ManifestGeneratorProvider>()
90+
.AddTransient<IManifestGeneratorProvider, ManifestGeneratorProvider>()
9091
.AddTransient<HashValidator>()
9192
.AddTransient<ValidationResultGenerator>()
9293
.AddTransient<IOutputWriter, FileOutputWriter>()

src/Microsoft.Sbom.Parsers.Spdx22SbomParser/MergeableContentProvider.cs

Lines changed: 105 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
using System.IO;
77
using System.Linq;
88
using Microsoft.Sbom.Common;
9+
using Microsoft.Sbom.Common.Utils;
910
using Microsoft.Sbom.Contracts;
1011
using Microsoft.Sbom.Extensions;
1112
using Microsoft.Sbom.Extensions.Entities;
1213
using Microsoft.Sbom.JsonAsynchronousNodeKit;
1314
using Microsoft.Sbom.Parser;
1415
using Microsoft.Sbom.Parsers.Spdx22SbomParser.Entities;
16+
using Microsoft.Sbom.Utils;
17+
using Serilog;
1518

1619
namespace Microsoft.Sbom.Parsers.Spdx22SbomParser;
1720

@@ -20,11 +23,15 @@ namespace Microsoft.Sbom.Parsers.Spdx22SbomParser;
2023
/// </summary>
2124
public class MergeableContentProvider : IMergeableContentProvider
2225
{
26+
private const string DependsOn = "DEPENDS_ON";
27+
2328
private readonly IFileSystemUtils fileSystemUtils;
29+
private readonly ILogger logger;
2430

25-
public MergeableContentProvider(IFileSystemUtils fileSystemUtils)
31+
public MergeableContentProvider(IFileSystemUtils fileSystemUtils, ILogger logger)
2632
{
2733
this.fileSystemUtils = fileSystemUtils ?? throw new ArgumentNullException(nameof(fileSystemUtils));
34+
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
2835
}
2936

3037
/// <summary>
@@ -44,6 +51,7 @@ public bool TryGetContent(string filePath, out MergeableContent mergeableContent
4451

4552
if (!fileSystemUtils.FileExists(filePath))
4653
{
54+
logger.Debug($"File '{filePath}' does not exist.");
4755
mergeableContent = null;
4856
return false;
4957
}
@@ -52,16 +60,19 @@ public bool TryGetContent(string filePath, out MergeableContent mergeableContent
5260

5361
if (stream == null)
5462
{
63+
logger.Debug($"Unable to open stream for file '{filePath}'.");
5564
mergeableContent = null;
5665
return false;
5766
}
5867

5968
try
6069
{
70+
logger.Debug($"Attempting to parse SPDX file at '{filePath}'.");
6171
return GetMergeableContent(stream, out mergeableContent);
6272
}
6373
catch (Exception)
6474
{
75+
logger.Debug($"Failed to parse SPDX file at '{filePath}'. It may not be a valid SPDX 2.2 file.");
6576
mergeableContent = null;
6677
return false;
6778
}
@@ -73,9 +84,9 @@ public bool TryGetContent(string filePath, out MergeableContent mergeableContent
7384
private bool GetMergeableContent(Stream stream, out MergeableContent mergeableContent)
7485
{
7586
// Skip sections that are expensive to parse and would require additional logic to properly ignore.
76-
var parser = new SPDXParser(stream, ["files", "externalDocumentRefs", "relationships"]);
77-
var packages = Enumerable.Empty<SbomPackage>();
78-
var relationships = Enumerable.Empty<SbomRelationship>();
87+
var parser = new SPDXParser(stream, new[] { "files", "externalDocumentRefs" });
88+
IList<SbomPackage> packages = new List<SbomPackage>();
89+
IList<SbomRelationship> relationships = new List<SbomRelationship>();
7990

8091
ParserStateResult result = null;
8192
do
@@ -86,7 +97,10 @@ private bool GetMergeableContent(Stream stream, out MergeableContent mergeableCo
8697
switch (result)
8798
{
8899
case PackagesResult packagesResult:
89-
packages = ProcessPackages(packagesResult.Packages);
100+
packages = ProcessPackages(packagesResult.Packages.ToList()); // ToList() ensures correct parsing of the data
101+
break;
102+
case RelationshipsResult relationshipsResult:
103+
relationships = ProcessRelationships(relationshipsResult.Relationships.ToList()); // ToList() ensures correct parsing of the data
90104
break;
91105
default:
92106
break;
@@ -95,33 +109,105 @@ private bool GetMergeableContent(Stream stream, out MergeableContent mergeableCo
95109
}
96110
while (result is not null);
97111

98-
mergeableContent = new MergeableContent(packages, relationships);
112+
mergeableContent = CreateRemappedMergeableContent(packages, relationships);
99113
return true;
100114
}
101115

102116
/// <summary>
103117
/// Process packages and return the collection of <see cref="SbomPackage"/> objects.
104118
/// </summary>
105-
private IEnumerable<SbomPackage> ProcessPackages(IEnumerable<SPDXPackage> spdxPackages)
119+
private IList<SbomPackage> ProcessPackages(IReadOnlyList<SPDXPackage> spdxPackages)
106120
{
107121
var packages = new List<SbomPackage>();
108122
foreach (var spdxPackage in spdxPackages)
109123
{
110-
var sbomPackage = new SbomPackage
111-
{
112-
PackageName = spdxPackage.Name,
113-
PackageVersion = spdxPackage.VersionInfo,
114-
Id = spdxPackage.SpdxId,
115-
FilesAnalyzed = spdxPackage.FilesAnalyzed,
116-
LicenseInfo = new LicenseInfo
117-
{
118-
Concluded = spdxPackage.LicenseConcluded,
119-
Declared = spdxPackage.LicenseDeclared
120-
},
121-
};
124+
var sbomPackage = spdxPackage.ToSbomPackage();
122125
packages.Add(sbomPackage);
123126
}
124127

125128
return packages;
126129
}
130+
131+
private IList<SbomRelationship> ProcessRelationships(IReadOnlyList<SPDXRelationship> spdxRelationships)
132+
{
133+
var relationships = new List<SbomRelationship>();
134+
135+
foreach (var spdxRelationship in spdxRelationships)
136+
{
137+
if (IsDependsOnRelationship(spdxRelationship))
138+
{
139+
var relationship = new SbomRelationship
140+
{
141+
SourceElementId = spdxRelationship.SourceElementId,
142+
TargetElementId = spdxRelationship.TargetElementId,
143+
RelationshipType = DependsOn, // Force output consistency
144+
};
145+
relationships.Add(relationship);
146+
}
147+
}
148+
149+
return relationships;
150+
}
151+
152+
private static bool IsDependsOnRelationship(SPDXRelationship spdxRelationship)
153+
{
154+
// Include both "DEPENDS_ON" and "DEPENDSON" to cover variations in SPDX files, and ignore case.
155+
return spdxRelationship.RelationshipType.Equals("DEPENDS_ON", StringComparison.InvariantCultureIgnoreCase) ||
156+
spdxRelationship.RelationshipType.Equals("DEPENDSON", StringComparison.InvariantCultureIgnoreCase);
157+
}
158+
159+
/// <summary>
160+
/// Remaps the root package ID, updates relationships accordingly, then creates a new <see cref="MergeableContent"/> object.
161+
/// </summary>
162+
private MergeableContent CreateRemappedMergeableContent(IList<SbomPackage> packages, IList<SbomRelationship> relationships)
163+
{
164+
var mappedRootPackageId = GetAdjustedRootPackageId(packages);
165+
166+
AdjustRootPackageRelationships(relationships, mappedRootPackageId);
167+
168+
logger.Debug($"MergeableContent includes {packages.Count} package(s) and {relationships.Count} relationship(s).");
169+
170+
return new MergeableContent(packages, relationships);
171+
}
172+
173+
private string GetAdjustedRootPackageId(IList<SbomPackage> packages)
174+
{
175+
foreach (var package in packages)
176+
{
177+
if (package.Id == Constants.RootPackageIdValue)
178+
{
179+
package.Id = null;
180+
var newSpdxId = CommonSPDXUtils.GenerateSpdxPackageId(package);
181+
package.Id = newSpdxId;
182+
logger.Debug($"Remapped root package ID from '{Constants.RootPackageIdValue}' to '{newSpdxId}'");
183+
return newSpdxId;
184+
}
185+
}
186+
187+
throw new InvalidDataException("No root package found in the SPDX document.");
188+
}
189+
190+
private void AdjustRootPackageRelationships(IList<SbomRelationship> relationships, string mappedRootPackageId)
191+
{
192+
// Update relationships where the source is the root package.
193+
foreach (var relationship in relationships)
194+
{
195+
if (relationship.SourceElementId == Constants.RootPackageIdValue)
196+
{
197+
// Update the source element ID to the remapped root package ID.
198+
logger.Verbose($"Remapped root package dependency on '{relationship.TargetElementId}'");
199+
relationship.SourceElementId = mappedRootPackageId;
200+
}
201+
}
202+
203+
// Make the output root depend on the remapped root.
204+
var newRootRelationship = new SbomRelationship
205+
{
206+
SourceElementId = Constants.RootPackageIdValue,
207+
TargetElementId = mappedRootPackageId,
208+
RelationshipType = DependsOn, // Force output consistency
209+
};
210+
relationships.Add(newRootRelationship);
211+
logger.Debug($"Added new root relationship from '{Constants.RootPackageIdValue}' to '{mappedRootPackageId}'");
212+
}
127213
}

0 commit comments

Comments
 (0)