Skip to content
Open
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
22 changes: 18 additions & 4 deletions nuke/SbomGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Xml.Linq;
using Nuke.Common.IO;
using Nuke.Common.Tooling;
using NuGet.Versioning;
using static Serilog.Log;

namespace NukeExtensions;
Expand Down Expand Up @@ -177,14 +178,19 @@ readonly record struct ConstituentScan(
scannedProjectDirs.Add(project.Parent);

var tempBom = outputDirectory / $"_{projectId}.tmp.json";
// Tool quotes each interpolated value. A combined optional fragment becomes one invalid argument.
// No quotes around the interpolated values: Tool takes an ArgumentStringHandler,
// which already quotes whatever is interpolated into it. Quoting here as well
// produces ""C:\Some Dir\proj.csproj"", which the OS reads as an empty quoted
// string followed by a bare path, so the argument splits at the first space and
// the tool rejects the remainder. Only bites where a path contains a space.
// The optional fragment stays a separate argument for the same reason.
if (baseIntermediateOutputPath is null)
cycloneDx(
$"\"{project}\" -o \"{outputDirectory}\" -fn \"{tempBom.Name}\" -F Json -dpr -ed -sn \"{packageId}\" -sv \"{version}\"",
$"{project} -o {outputDirectory} -fn {tempBom.Name} -F Json -dpr -ed -sn {packageId} -sv {version}",
workingDirectory: rootDirectory);
else
cycloneDx(
$"\"{project}\" -o \"{outputDirectory}\" -fn \"{tempBom.Name}\" -F Json -dpr -ed -sn \"{packageId}\" -sv \"{version}\" -biop \"{baseIntermediateOutputPath}\"",
$"{project} -o {outputDirectory} -fn {tempBom.Name} -F Json -dpr -ed -sn {packageId} -sv {version} -biop {baseIntermediateOutputPath}",
workingDirectory: rootDirectory);

var doc = JsonNode.Parse(File.ReadAllText(tempBom))!.AsObject();
Expand Down Expand Up @@ -594,7 +600,7 @@ static void EnrichRootComponent(JsonObject merged, PackageMetadata meta)
component["type"] = meta.ComponentType;
// A version mismatch means that the restored dependencies can differ from the shipped package.
var scannedVersion = component["version"]?.GetValue<string>();
if (scannedVersion is not null && scannedVersion != meta.Version)
if (scannedVersion is not null && !IsSameVersion(scannedVersion, meta.Version))
throw new InvalidOperationException(
$"SBOM: '{meta.Id}' was scanned as version '{scannedVersion}' but the shipped package is '{meta.Version}' - are the packages stale?");
component["version"] = meta.Version;
Expand Down Expand Up @@ -635,6 +641,14 @@ static void EnrichRootComponent(JsonObject merged, PackageMetadata meta)
metadata["supplier"] = supplier.DeepClone();
}

// NuGet pack normalizes whatever version it is handed ("12.2" ships as "12.2.0"), so the
// build-supplied and nuspec strings can spell the same version differently; only a semantic
// difference means the packages are stale. Unparseable versions keep the exact-string check.
static bool IsSameVersion(string scanned, string shipped) =>
NuGetVersion.TryParse(scanned, out var scannedVersion) && NuGetVersion.TryParse(shipped, out var shippedVersion)
? scannedVersion == shippedVersion
: string.Equals(scanned, shipped, StringComparison.Ordinal);

// The -ed option can exclude a PrivateAssets package that the .nuspec still declares.
// Add all missing .nuspec dependencies because consumers restore them.
static void AddDeclaredDependencyComponents(JsonObject merged, HashSet<string> seenComponentKeys,
Expand Down