Skip to content

Commit

Permalink
Initial implementation of VcpkgDetector and VcpkgComponent (#52)
Browse files Browse the repository at this point in the history
* Initial implementation of VcpkgDetector and VcpkgComponent

* Fix warnings

* Initial implementation of VcpkgDetector and VcpkgComponent

* Fix warnings

* Update src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs

* Address PR comments. Add parsing for Annotations.

* Use DateTime property for annotation object

* Add tests for VcpkgComponentDetector

* Satisfy format detector

* Update src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs

Co-authored-by: Greg Villicana <[email protected]>
  • Loading branch information
ras0219-msft and grvillic authored Mar 31, 2022
1 parent 9c00871 commit a990db1
Show file tree
Hide file tree
Showing 10 changed files with 659 additions and 2 deletions.
5 changes: 4 additions & 1 deletion src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ public enum DetectorClass

/// <summary>Indicates a detector applies to Conda packages.</summary>
Conda,

/// <summary>Indicates a detector applies to SPDX files.</summary>
Spdx,

/// <summary>Indicates a detector applies to Vcpkg packages.</summary>
Vcpkg,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ public enum ComponentType : byte

[EnumMember]
Conda = 13,

[EnumMember]
Spdx = 14,

[EnumMember]
Vcpkg = 15,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using PackageUrl;

namespace Microsoft.ComponentDetection.Contracts.TypedComponent
{
public class VcpkgComponent : TypedComponent
{
private VcpkgComponent()
{
/* Reserved for deserialization */
}

public VcpkgComponent(string spdxid, string name, string version, string triplet = null, string portVersion = null, string description = null, string downloadLocation = null)
{
SPDXID = ValidateRequiredInput(spdxid, nameof(SPDXID), nameof(ComponentType.Vcpkg));
Name = ValidateRequiredInput(name, nameof(Name), nameof(ComponentType.Vcpkg));
Version = version;
PortVersion = portVersion;
Triplet = triplet;
Description = description;
DownloadLocation = downloadLocation;
}

public string SPDXID { get; set; }

public string Name { get; set; }

public string DownloadLocation { get; set; }

public string Triplet { get; set; }

public string Version { get; set; }

public string Description { get; set; }

public string PortVersion { get; set; }

public override ComponentType Type => ComponentType.Vcpkg;

public override string Id
{
get
{
if (PortVersion != null)
{
return $"{Name} {Version}#{PortVersion} - {Type}";
}
else
{
return $"{Name} {Version} - {Type}";
}
}
}

public override PackageURL PackageUrl
{
get
{
if (PortVersion != null)
{
return new PackageURL($"pkg:vcpkg/{Name}@{Version}?port_version={PortVersion}");
}
else if (Version != null)
{
return new PackageURL($"pkg:vcpkg/{Name}@{Version}");
}
else
{
return new PackageURL($"pkg:vcpkg/{Name}");
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace Microsoft.ComponentDetection.Detectors.Vcpkg.Contracts
{
public class Annotation
{
public DateTime Date { get; set; }

public string Comment { get; set; }

public string Type { get; set; }

public string Annotator { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Microsoft.ComponentDetection.Detectors.Vcpkg.Contracts
{
public class Package
{
public string SPDXID { get; set; }

public string VersionInfo { get; set; }

public string DownloadLocation { get; set; }

public string Filename { get; set; }

public string Homepage { get; set; }

public string Description { get; set; }

public string Name { get; set; }

public Annotation[] Annotations { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Microsoft.ComponentDetection.Detectors.Vcpkg.Contracts
{
/// <summary>
/// Matches a subset of https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json.
/// </summary>
public class VcpkgSBOM
{
public Package[] Packages { get; set; }

public string Name { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Composition;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.ComponentDetection.Common;
using Microsoft.ComponentDetection.Common.Telemetry.Records;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.ComponentDetection.Detectors.Vcpkg.Contracts;
using Newtonsoft.Json;

namespace Microsoft.ComponentDetection.Detectors.Vcpkg
{
[Export(typeof(IComponentDetector))]
public class VcpkgComponentDetector : FileComponentDetector, IDefaultOffComponentDetector
{
[Import]
public ICommandLineInvocationService CommandLineInvocationService { get; set; }

[Import]
public IEnvironmentVariableService EnvVarService { get; set; }

public override string Id { get; } = "Vcpkg";

public override IEnumerable<string> Categories => new[] { Enum.GetName(typeof(DetectorClass), DetectorClass.Vcpkg) };

public override IList<string> SearchPatterns { get; } = new List<string> { "vcpkg.spdx.json" };

public override IEnumerable<ComponentType> SupportedComponentTypes { get; } = new[] { ComponentType.Vcpkg };

public override int Version => 1;

private HashSet<string> projectRoots = new HashSet<string>();

protected override async Task OnFileFound(ProcessRequest processRequest, IDictionary<string, string> detectorArgs)
{
var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder;
var file = processRequest.ComponentStream;

Logger.LogWarning($"vcpkg detector found {file}");

var projectRootDirectory = Directory.GetParent(file.Location);
if (projectRoots.Any(path => projectRootDirectory.FullName.StartsWith(path)))
{
return;
}

await ParseSpdxFile(singleFileComponentRecorder, file);
}

private async Task ParseSpdxFile(
ISingleFileComponentRecorder singleFileComponentRecorder,
IComponentStream file)
{
using var reader = new StreamReader(file.Stream);
VcpkgSBOM sbom;
try
{
sbom = JsonConvert.DeserializeObject<VcpkgSBOM>(await reader.ReadToEndAsync());
}
catch (Exception)
{
return;
}

if (sbom?.Packages == null)
{
return;
}

foreach (var item in sbom.Packages)
{
try
{
if (string.IsNullOrEmpty(item.Name))
{
continue;
}

Logger.LogWarning($"parsed package {item.Name}");
if (item.SPDXID == "SPDXRef-port")
{
var split = item.VersionInfo.Split('#');
var component = new VcpkgComponent(item.SPDXID, item.Name, split[0], portVersion: split.Length >= 2 ? split[1] : "0", downloadLocation: item.DownloadLocation);
singleFileComponentRecorder.RegisterUsage(new DetectedComponent(component));
}
else if (item.SPDXID == "SPDXRef-binary")
{
var split = item.Name.Split(':');
var component = new VcpkgComponent(item.SPDXID, item.Name, item.VersionInfo, triplet: split[1], downloadLocation: item.DownloadLocation);
singleFileComponentRecorder.RegisterUsage(new DetectedComponent(component));
}
else if (item.SPDXID.StartsWith("SPDXRef-resource-"))
{
var dl = item.DownloadLocation;
var split = dl.Split("#");
var subpath = split.Length > 1 ? split[1] : null;
dl = split.Length > 1 ? split[0] : dl;
split = dl.Split("@");
var version = split.Length > 1 ? split[1] : null;
dl = split.Length > 1 ? split[0] : dl;

var component = new VcpkgComponent(item.SPDXID, item.Name, version, downloadLocation: dl);
singleFileComponentRecorder.RegisterUsage(new DetectedComponent(component));
}
}
catch (Exception)
{
Logger.LogWarning($"failed while handling {item.Name}");
}
}
}
}
}
Loading

0 comments on commit a990db1

Please sign in to comment.