diff --git a/src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs b/src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs
index 83363b1a3..12c995077 100644
--- a/src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs
+++ b/src/Microsoft.ComponentDetection.Contracts/DetectorClass.cs
@@ -35,8 +35,11 @@ public enum DetectorClass
/// Indicates a detector applies to Conda packages.
Conda,
-
+
/// Indicates a detector applies to SPDX files.
Spdx,
+
+ /// Indicates a detector applies to Vcpkg packages.
+ Vcpkg,
}
}
diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ComponentType.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ComponentType.cs
index 73c25652b..f02c639b8 100644
--- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ComponentType.cs
+++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ComponentType.cs
@@ -44,8 +44,11 @@ public enum ComponentType : byte
[EnumMember]
Conda = 13,
-
+
[EnumMember]
Spdx = 14,
+
+ [EnumMember]
+ Vcpkg = 15,
}
}
\ No newline at end of file
diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/VcpkgComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/VcpkgComponent.cs
new file mode 100644
index 000000000..41037f922
--- /dev/null
+++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/VcpkgComponent.cs
@@ -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}");
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/Annotation.cs b/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/Annotation.cs
new file mode 100644
index 000000000..0a11ca4cd
--- /dev/null
+++ b/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/Annotation.cs
@@ -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; }
+ }
+}
diff --git a/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/Package.cs b/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/Package.cs
new file mode 100644
index 000000000..fd4b7a6e8
--- /dev/null
+++ b/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/Package.cs
@@ -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; }
+ }
+}
diff --git a/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/VcpkgSBOM.cs b/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/VcpkgSBOM.cs
new file mode 100644
index 000000000..77c53be03
--- /dev/null
+++ b/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/VcpkgSBOM.cs
@@ -0,0 +1,12 @@
+namespace Microsoft.ComponentDetection.Detectors.Vcpkg.Contracts
+{
+ ///
+ /// Matches a subset of https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json.
+ ///
+ public class VcpkgSBOM
+ {
+ public Package[] Packages { get; set; }
+
+ public string Name { get; set; }
+ }
+}
diff --git a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs
new file mode 100644
index 000000000..60a8e99c6
--- /dev/null
+++ b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs
@@ -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 Categories => new[] { Enum.GetName(typeof(DetectorClass), DetectorClass.Vcpkg) };
+
+ public override IList SearchPatterns { get; } = new List { "vcpkg.spdx.json" };
+
+ public override IEnumerable SupportedComponentTypes { get; } = new[] { ComponentType.Vcpkg };
+
+ public override int Version => 1;
+
+ private HashSet projectRoots = new HashSet();
+
+ protected override async Task OnFileFound(ProcessRequest processRequest, IDictionary 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(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}");
+ }
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs
new file mode 100644
index 000000000..9e28ccaa6
--- /dev/null
+++ b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs
@@ -0,0 +1,174 @@
+using Microsoft.ComponentDetection.Common.DependencyGraph;
+using Microsoft.ComponentDetection.Contracts;
+using Microsoft.ComponentDetection.TestsUtilities;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.ComponentDetection.Contracts.TypedComponent;
+using Microsoft.ComponentDetection.Detectors.Vcpkg;
+using System.Reflection;
+
+namespace Microsoft.ComponentDetection.Detectors.Tests
+{
+ [TestClass]
+ [TestCategory("Governance/All")]
+ [TestCategory("Governance/ComponentDetection")]
+ public class VcpkgComponentDetectorTests
+ {
+ private DetectorTestUtility detectorTestUtility;
+
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ var componentRecorder = new ComponentRecorder(enableManualTrackingOfExplicitReferences: false);
+ detectorTestUtility = DetectorTestUtilityCreator.Create()
+ .WithScanRequest(new ScanRequest(new DirectoryInfo(Path.GetTempPath()), null, null, new Dictionary(), null, componentRecorder));
+ }
+
+ [TestMethod]
+ public async Task TestNlohmann()
+ {
+ var spdxFile = @"{
+ ""SPDXID"": ""SPDXRef - DOCUMENT"",
+ ""documentNamespace"":
+ ""https://spdx.org/spdxdocs/nlohmann-json-x64-linux-3.10.4-78c7f190-b402-44d1-a364-b9ac86392b84"",
+ ""name"": ""nlohmann-json:x64-linux@3.10.4 69dcfc6886529ad2d210f71f132d743672a7e65d2c39f53456f17fc5fc08b278"",
+ ""packages"": [
+ {
+ ""name"": ""nlohmann-json"",
+ ""SPDXID"": ""SPDXRef-port"",
+ ""versionInfo"": ""3.10.4"",
+ ""downloadLocation"": ""git+https://github.com/Microsoft/vcpkg#ports/nlohmann-json"",
+ ""homepage"": ""https://github.com/nlohmann/json"",
+ ""licenseConcluded"": ""NOASSERTION"",
+ ""licenseDeclared"": ""NOASSERTION"",
+ ""copyrightText"": ""NOASSERTION"",
+ ""description"": ""JSON for Modern C++"",
+ ""comment"": ""This is the port (recipe) consumed by vcpkg.""
+ }
+ ]
+}";
+ var (scanResult, componentRecorder) = await detectorTestUtility
+ .WithFile("vcpkg.spdx.json", spdxFile)
+ .ExecuteDetector();
+
+ Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode);
+
+ var detectedComponents = componentRecorder.GetDetectedComponents();
+ var components = detectedComponents.ToList();
+ var sbomComponent = (VcpkgComponent)components.FirstOrDefault()?.Component;
+
+ if (sbomComponent is null)
+ {
+ throw new AssertFailedException($"{nameof(sbomComponent)} is null");
+ }
+
+ Assert.AreEqual(1, components.Count());
+ Assert.AreEqual("nlohmann-json", sbomComponent.Name);
+ Assert.AreEqual("3.10.4", sbomComponent.Version);
+ Assert.AreEqual("0", sbomComponent.PortVersion);
+ Assert.AreEqual("SPDXRef-port", sbomComponent.SPDXID);
+ Assert.AreEqual("git+https://github.com/Microsoft/vcpkg#ports/nlohmann-json", sbomComponent.DownloadLocation);
+ Assert.AreEqual("pkg:vcpkg/nlohmann-json@3.10.4?port_version=0", sbomComponent.PackageUrl.ToString());
+ }
+
+ [TestMethod]
+ public async Task TestTinyxmlAndResource()
+ {
+ var spdxFile = @"{
+ ""SPDXID"": ""SPDXRef - DOCUMENT"",
+ ""documentNamespace"":
+ ""https://spdx.org/spdxdocs/tinyxml2-x64-linux-9.0.0-c99e4f03-5275-458b-8a69-b5f8dfa45f18"",
+ ""name"": ""tinyxml2:x64-linux@9.0.0 5c7679507def92c5c71df44aec08a90a5c749f7f805b3f0e8e70f5e8a5b1b8d0"",
+ ""packages"": [
+ {
+ ""name"": ""tinyxml2:x64-linux"",
+ ""SPDXID"": ""SPDXRef-binary"",
+ ""versionInfo"": ""5c7679507def92c5c71df44aec08a90a5c749f7f805b3f0e8e70f5e8a5b1b8d0"",
+ ""downloadLocation"": ""NONE"",
+ ""licenseConcluded"": ""NOASSERTION"",
+ ""licenseDeclared"": ""NOASSERTION"",
+ ""copyrightText"": ""NOASSERTION"",
+ ""comment"": ""This is a binary package built by vcpkg.""
+ },
+ {
+ ""SPDXID"": ""SPDXRef-resource-1"",
+ ""name"": ""leethomason/tinyxml2"",
+ ""downloadLocation"": ""git+https://github.com/leethomason/tinyxml2@9.0.0"",
+ ""licenseConcluded"": ""NOASSERTION"",
+ ""licenseDeclared"": ""NOASSERTION"",
+ ""copyrightText"": ""NOASSERTION"",
+ ""checksums"": [
+ {
+ ""algorithm"": ""SHA512"",
+ ""checksumValue"": ""9c5ce8131984690df302ca3e32314573b137180ed522c92fd631692979c942372a28f697fdb3d5e56bcf2d3dc596262b724d088153f3e1d721c9536f2a883367""
+ }
+ ]
+ }
+ ]
+}";
+ var (scanResult, componentRecorder) = await detectorTestUtility
+ .WithFile("vcpkg.spdx.json", spdxFile)
+ .ExecuteDetector();
+
+ Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode);
+
+ var detectedComponents = componentRecorder.GetDetectedComponents();
+ var components = detectedComponents.ToList();
+
+ Assert.AreEqual(2, components.Count());
+ {
+ var sbomComponent = (VcpkgComponent)components[0].Component;
+ Assert.AreEqual("tinyxml2:x64-linux", sbomComponent.Name);
+ Assert.AreEqual("5c7679507def92c5c71df44aec08a90a5c749f7f805b3f0e8e70f5e8a5b1b8d0", sbomComponent.Version);
+ Assert.AreEqual("SPDXRef-binary", sbomComponent.SPDXID);
+ Assert.AreEqual("NONE", sbomComponent.DownloadLocation);
+ }
+
+ {
+ var sbomComponent = (VcpkgComponent)components[1].Component;
+ Assert.AreEqual("leethomason/tinyxml2", sbomComponent.Name);
+ Assert.AreEqual("9.0.0", sbomComponent.Version);
+ Assert.AreEqual("SPDXRef-resource-1", sbomComponent.SPDXID);
+ Assert.AreEqual("git+https://github.com/leethomason/tinyxml2", sbomComponent.DownloadLocation);
+ }
+ }
+
+ [TestMethod]
+ public async Task TestBlankJson()
+ {
+ var spdxFile = "{}";
+
+ var (scanResult, componentRecorder) = await detectorTestUtility
+ .WithFile("vcpkg.spdx.json", spdxFile)
+ .ExecuteDetector();
+
+ Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode);
+
+ var detectedComponents = componentRecorder.GetDetectedComponents();
+ var components = detectedComponents.ToList();
+ Assert.IsFalse(components.Any());
+ }
+
+ [TestMethod]
+ public async Task TestInvalidFile()
+ {
+ var spdxFile = "invalidspdxfile";
+
+ var (scanResult, componentRecorder) = await detectorTestUtility
+ .WithFile("vcpkg.spdx.json", spdxFile)
+ .ExecuteDetector();
+
+ Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode);
+
+ var detectedComponents = componentRecorder.GetDetectedComponents();
+ var components = detectedComponents.ToList();
+ Assert.IsFalse(components.Any());
+ }
+ }
+}
diff --git a/test/Microsoft.ComponentDetection.VerificationTests/resources/vcpkg/nlohmann-json/vcpkg.spdx.json b/test/Microsoft.ComponentDetection.VerificationTests/resources/vcpkg/nlohmann-json/vcpkg.spdx.json
new file mode 100644
index 000000000..29bae26a3
--- /dev/null
+++ b/test/Microsoft.ComponentDetection.VerificationTests/resources/vcpkg/nlohmann-json/vcpkg.spdx.json
@@ -0,0 +1,115 @@
+{
+ "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json",
+ "spdxVersion": "SPDX-2.2",
+ "dataLicense": "CC0-1.0",
+ "SPDXID": "SPDXRef-DOCUMENT",
+ "documentNamespace": "https://spdx.org/spdxdocs/nlohmann-json-x64-linux-3.10.4-78c7f190-b402-44d1-a364-b9ac86392b84",
+ "name": "nlohmann-json:x64-linux@3.10.4 69dcfc6886529ad2d210f71f132d743672a7e65d2c39f53456f17fc5fc08b278",
+ "creationInfo": {
+ "creators": [
+ "Tool: vcpkg-unknownhash"
+ ],
+ "created": "2022-01-18T21:12:48Z"
+ },
+ "relationships": [
+ {
+ "spdxElementId": "SPDXRef-port",
+ "relationshipType": "GENERATES",
+ "relatedSpdxElement": "SPDXRef-binary"
+ },
+ {
+ "spdxElementId": "SPDXRef-port",
+ "relationshipType": "CONTAINS",
+ "relatedSpdxElement": "SPDXRef-file-0"
+ },
+ {
+ "spdxElementId": "SPDXRef-port",
+ "relationshipType": "CONTAINS",
+ "relatedSpdxElement": "SPDXRef-file-1"
+ },
+ {
+ "spdxElementId": "SPDXRef-binary",
+ "relationshipType": "GENERATED_FROM",
+ "relatedSpdxElement": "SPDXRef-port"
+ },
+ {
+ "spdxElementId": "SPDXRef-file-0",
+ "relationshipType": "CONTAINED_BY",
+ "relatedSpdxElement": "SPDXRef-port"
+ },
+ {
+ "spdxElementId": "SPDXRef-file-1",
+ "relationshipType": "CONTAINED_BY",
+ "relatedSpdxElement": "SPDXRef-port"
+ },
+ {
+ "spdxElementId": "SPDXRef-file-1",
+ "relationshipType": "DEPENDENCY_MANIFEST_OF",
+ "relatedSpdxElement": "SPDXRef-port"
+ }
+ ],
+ "packages": [
+ {
+ "name": "nlohmann-json",
+ "SPDXID": "SPDXRef-port",
+ "versionInfo": "3.10.4",
+ "downloadLocation": "git+https://github.com/Microsoft/vcpkg#ports/nlohmann-json",
+ "homepage": "https://github.com/nlohmann/json",
+ "licenseConcluded": "NOASSERTION",
+ "licenseDeclared": "NOASSERTION",
+ "copyrightText": "NOASSERTION",
+ "description": "JSON for Modern C++",
+ "comment": "This is the port (recipe) consumed by vcpkg."
+ },
+ {
+ "name": "nlohmann-json:x64-linux",
+ "SPDXID": "SPDXRef-binary",
+ "versionInfo": "69dcfc6886529ad2d210f71f132d743672a7e65d2c39f53456f17fc5fc08b278",
+ "downloadLocation": "NONE",
+ "licenseConcluded": "NOASSERTION",
+ "licenseDeclared": "NOASSERTION",
+ "copyrightText": "NOASSERTION",
+ "comment": "This is a binary package built by vcpkg."
+ },
+ {
+ "SPDXID": "SPDXRef-resource-1",
+ "name": "nlohmann/json",
+ "downloadLocation": "git+https://github.com/nlohmann/json@v3.10.4",
+ "licenseConcluded": "NOASSERTION",
+ "licenseDeclared": "NOASSERTION",
+ "copyrightText": "NOASSERTION",
+ "checksums": [
+ {
+ "algorithm": "SHA512",
+ "checksumValue": "f78592db6218165cbc74c10bcba40366f1bfea84405b7ee25fe97a056d5b7a15aeeb956d93296673928dcbd6e26ffcfb152f885b4a44d5d55751396ccf090835"
+ }
+ ]
+ }
+ ],
+ "files": [
+ {
+ "fileName": "./portfile.cmake",
+ "SPDXID": "SPDXRef-file-0",
+ "checksums": [
+ {
+ "algorithm": "SHA256",
+ "checksumValue": "49b4f9e11cdd0ef697a2750187fa5fa42e7e833dbf411d299ca4d1e9f147773a"
+ }
+ ],
+ "licenseConcluded": "NOASSERTION",
+ "copyrightText": "NOASSERTION"
+ },
+ {
+ "fileName": "./vcpkg.json",
+ "SPDXID": "SPDXRef-file-1",
+ "checksums": [
+ {
+ "algorithm": "SHA256",
+ "checksumValue": "b97c75815135eb812a88ee92d50170928d4c69517bfb332e181070cbce2c081b"
+ }
+ ],
+ "licenseConcluded": "NOASSERTION",
+ "copyrightText": "NOASSERTION"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/Microsoft.ComponentDetection.VerificationTests/resources/vcpkg/tinyxml2/vcpkg.spdx.json b/test/Microsoft.ComponentDetection.VerificationTests/resources/vcpkg/tinyxml2/vcpkg.spdx.json
new file mode 100644
index 000000000..fc97fee5c
--- /dev/null
+++ b/test/Microsoft.ComponentDetection.VerificationTests/resources/vcpkg/tinyxml2/vcpkg.spdx.json
@@ -0,0 +1,123 @@
+{
+ "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json",
+ "spdxVersion": "SPDX-2.2",
+ "dataLicense": "CC0-1.0",
+ "SPDXID": "SPDXRef-DOCUMENT",
+ "documentNamespace": "https://spdx.org/spdxdocs/tinyxml2-x64-linux-9.0.0-c99e4f03-5275-458b-8a69-b5f8dfa45f18",
+ "name": "tinyxml2:x64-linux@9.0.0 5c7679507def92c5c71df44aec08a90a5c749f7f805b3f0e8e70f5e8a5b1b8d0",
+ "creationInfo": {
+ "creators": [
+ "Tool: vcpkg-unknownhash"
+ ],
+ "created": "2022-01-14T00:28:41Z"
+ },
+ "relationships": [
+ {
+ "spdxElementId": "SPDXRef-port",
+ "relationshipType": "GENERATES",
+ "relatedSpdxElement": "SPDXRef-binary"
+ },
+ {
+ "spdxElementId": "SPDXRef-port",
+ "relationshipType": "CONTAINS",
+ "relatedSpdxElement": "SPDXRef-file-0"
+ },
+ {
+ "spdxElementId": "SPDXRef-port",
+ "relationshipType": "CONTAINS",
+ "relatedSpdxElement": "SPDXRef-file-1"
+ },
+ {
+ "spdxElementId": "SPDXRef-binary",
+ "relationshipType": "GENERATED_FROM",
+ "relatedSpdxElement": "SPDXRef-port"
+ },
+ {
+ "spdxElementId": "SPDXRef-file-0",
+ "relationshipType": "CONTAINED_BY",
+ "relatedSpdxElement": "SPDXRef-port"
+ },
+ {
+ "spdxElementId": "SPDXRef-file-1",
+ "relationshipType": "CONTAINED_BY",
+ "relatedSpdxElement": "SPDXRef-port"
+ },
+ {
+ "spdxElementId": "SPDXRef-file-1",
+ "relationshipType": "DEPENDENCY_MANIFEST_OF",
+ "relatedSpdxElement": "SPDXRef-port"
+ }
+ ],
+ "packages": [
+ {
+ "name": "tinyxml2",
+ "SPDXID": "SPDXRef-port",
+ "versionInfo": "9.0.0",
+ "downloadLocation": "git+https://github.com/Microsoft/vcpkg#ports/tinyxml2",
+ "homepage": "https://github.com/leethomason/tinyxml2",
+ "licenseConcluded": "NOASSERTION",
+ "licenseDeclared": "NOASSERTION",
+ "copyrightText": "NOASSERTION",
+ "description": "A simple, small, efficient, C++ XML parser",
+ "comment": "This is the port (recipe) consumed by vcpkg.",
+ "annotations": [
+ {
+ "annotationDate": "2010-01-29T18:30:22Z",
+ "annotationType": "OTHER",
+ "annotator": "Tool: vcpkg - 2020-01-01",
+ "comment": "vcpkgPackageType:port"
+ }
+ ]
+ },
+ {
+ "name": "tinyxml2:x64-linux",
+ "SPDXID": "SPDXRef-binary",
+ "versionInfo": "5c7679507def92c5c71df44aec08a90a5c749f7f805b3f0e8e70f5e8a5b1b8d0",
+ "downloadLocation": "NONE",
+ "licenseConcluded": "NOASSERTION",
+ "licenseDeclared": "NOASSERTION",
+ "copyrightText": "NOASSERTION",
+ "comment": "This is a binary package built by vcpkg."
+ },
+ {
+ "SPDXID": "SPDXRef-resource-1",
+ "name": "leethomason/tinyxml2",
+ "downloadLocation": "git+https://github.com/leethomason/tinyxml2@9.0.0",
+ "licenseConcluded": "NOASSERTION",
+ "licenseDeclared": "NOASSERTION",
+ "copyrightText": "NOASSERTION",
+ "checksums": [
+ {
+ "algorithm": "SHA512",
+ "checksumValue": "9c5ce8131984690df302ca3e32314573b137180ed522c92fd631692979c942372a28f697fdb3d5e56bcf2d3dc596262b724d088153f3e1d721c9536f2a883367"
+ }
+ ]
+ }
+ ],
+ "files": [
+ {
+ "fileName": "./portfile.cmake",
+ "SPDXID": "SPDXRef-file-0",
+ "checksums": [
+ {
+ "algorithm": "SHA256",
+ "checksumValue": "dad309c46aea9ccd9a3779723970963b2792d6a7fade26f95437be8ed18ccd60"
+ }
+ ],
+ "licenseConcluded": "NOASSERTION",
+ "copyrightText": "NOASSERTION"
+ },
+ {
+ "fileName": "./vcpkg.json",
+ "SPDXID": "SPDXRef-file-1",
+ "checksums": [
+ {
+ "algorithm": "SHA256",
+ "checksumValue": "4f8ddfeb9d3faa3ecf03f22a07678a060e353cde5e2b46be1cbe519993aab841"
+ }
+ ],
+ "licenseConcluded": "NOASSERTION",
+ "copyrightText": "NOASSERTION"
+ }
+ ]
+}
\ No newline at end of file