From 91535582c9c0e8db566d13bbf426078bafead199 Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Wed, 11 Jun 2025 16:27:02 -0700 Subject: [PATCH 01/12] Vcpkg detection enhancement to report vcpkg.json file #1408 --- .../vcpkg/Contracts/ManifestInfo.cs | 9 ++ .../vcpkg/VcpkgComponentDetector.cs | 107 +++++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/ManifestInfo.cs diff --git a/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/ManifestInfo.cs b/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/ManifestInfo.cs new file mode 100644 index 000000000..b5512c1b0 --- /dev/null +++ b/src/Microsoft.ComponentDetection.Detectors/vcpkg/Contracts/ManifestInfo.cs @@ -0,0 +1,9 @@ +namespace Microsoft.ComponentDetection.Detectors.Vcpkg.Contracts; + +using Newtonsoft.Json; + +public class ManifestInfo +{ + [JsonProperty("manifest-path")] + public string ManifestPath { get; set; } +} diff --git a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs index 393e10879..898448e2e 100644 --- a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs @@ -1,9 +1,11 @@ namespace Microsoft.ComponentDetection.Detectors.Vcpkg; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.ComponentDetection.Contracts; @@ -16,6 +18,7 @@ namespace Microsoft.ComponentDetection.Detectors.Vcpkg; public class VcpkgComponentDetector : FileComponentDetector { private readonly HashSet projectRoots = []; + private readonly ConcurrentDictionary manifestMappings = new(StringComparer.OrdinalIgnoreCase); private readonly ICommandLineInvocationService commandLineInvocationService; private readonly IEnvironmentVariableService envVarService; @@ -38,7 +41,7 @@ public VcpkgComponentDetector( public override IEnumerable Categories => [Enum.GetName(typeof(DetectorClass), DetectorClass.Vcpkg)]; - public override IList SearchPatterns { get; } = ["vcpkg.spdx.json"]; + public override IList SearchPatterns { get; } = ["vcpkg.spdx.json", "manifest-info.json"]; public override IEnumerable SupportedComponentTypes { get; } = [ComponentType.Vcpkg]; @@ -57,7 +60,44 @@ protected override async Task OnFileFoundAsync(ProcessRequest processRequest, ID return; } - await this.ParseSpdxFileAsync(singleFileComponentRecorder, file); + await this.ParseSpdxFileAsync(this.GetManifestComponentRecorder(singleFileComponentRecorder), file); + } + + protected override async Task> OnPrepareDetectionAsync(IObservable processRequests, IDictionary detectorArgs, CancellationToken cancellationToken = default) + { + var filteredProcessRequests = new List(); + + await processRequests.ForEachAsync(async pr => + { + var fileLocation = pr.ComponentStream.Location; + var fileName = Path.GetFileName(fileLocation); + + if (fileName.Equals("manifest-info.json", StringComparison.OrdinalIgnoreCase)) + { + this.Logger.LogDebug("Discovered VCPKG package manifest file at: {Location}", pr.ComponentStream.Location); + + using (var reader = new StreamReader(pr.ComponentStream.Stream)) + { + var contents = await reader.ReadToEndAsync().ConfigureAwait(false); + var manifestData = JsonConvert.DeserializeObject(contents); + + if (manifestData == null || string.IsNullOrWhiteSpace(manifestData.ManifestPath)) + { + this.Logger.LogWarning("Failed to deserialize manifest-info.json or missing ManifestPath at {Path}", pr.ComponentStream.Location); + } + else + { + this.manifestMappings.TryAdd(fileLocation, manifestData); + } + } + } + else + { + filteredProcessRequests.Add(pr); + } + }).ConfigureAwait(false); + + return filteredProcessRequests.ToObservable(); } private async Task ParseSpdxFileAsync( @@ -123,4 +163,67 @@ private async Task ParseSpdxFileAsync( } } } + + /// + /// Attempts to resolve and return a manifest component recorder for the given recorder. + /// Returns the matching manifest component recorder if found; otherwise, returns the original recorder. + /// + private ISingleFileComponentRecorder GetManifestComponentRecorder(ISingleFileComponentRecorder singleFileComponentRecorder) + { + try + { + const string vcpkgInstalled = "vcpkg_installed"; + var manifestFileLocation = singleFileComponentRecorder.ManifestFileLocation; + + var vcpkgInstalledIndex = manifestFileLocation.IndexOf(vcpkgInstalled, StringComparison.OrdinalIgnoreCase); + if (vcpkgInstalledIndex < 0) + { + this.Logger.LogWarning( + "Could not find '{VcpkgInstalled}' in ManifestFileLocation: '{ManifestFileLocation}'. Returning original recorder.", + vcpkgInstalled, + manifestFileLocation); + + return singleFileComponentRecorder; + } + + var vcpkgInstalledDir = manifestFileLocation[..(vcpkgInstalledIndex + vcpkgInstalled.Length)]; + + var preferredManifest = Path.Combine(vcpkgInstalledDir, "vcpkg", "manifest-info.json"); + var fallbackManifest = Path.Combine(vcpkgInstalledDir, "manifest-info.json"); + + // Try preferred location first + if (this.manifestMappings.TryGetValue(preferredManifest, out var manifestData) && manifestData != null) + { + return this.ComponentRecorder.CreateSingleFileComponentRecorder(manifestData.ManifestPath); + } + else if (this.manifestMappings.TryGetValue(fallbackManifest, out manifestData) && manifestData != null) + { + // Use the fallback location. + this.Logger.LogWarning( + "Preferred manifest at '{PreferredManifest}' was not found or invalid. Using fallback manifest at '{FallbackManifest}'.", + preferredManifest, + fallbackManifest); + + return this.ComponentRecorder.CreateSingleFileComponentRecorder(manifestData.ManifestPath); + } + else + { + this.Logger.LogWarning( + "No valid manifest-info.json found at either '{PreferredManifest}' or '{FallbackManifest}' for base location '{VcpkgInstalledDir}'. Returning original recorder.", + preferredManifest, + fallbackManifest, + vcpkgInstalledDir); + } + } + catch (Exception ex) + { + this.Logger.LogWarning( + ex, + "An exception occurred while resolving manifest component recorder for '{ManifestFileLocation}'. Returning original recorder.", + singleFileComponentRecorder.ManifestFileLocation); + } + + // Always return the original recorder if no manifest is found or on error + return singleFileComponentRecorder; + } } From 1c2ab00e357dd9ab837768373bb2bba778cf9ce8 Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Thu, 12 Jun 2025 07:22:27 -0700 Subject: [PATCH 02/12] Logging as Debug vs Warnings to follow the pattern of the other detectors --- .../vcpkg/VcpkgComponentDetector.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs index 898448e2e..0bbb893de 100644 --- a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs @@ -83,7 +83,7 @@ await processRequests.ForEachAsync(async pr => if (manifestData == null || string.IsNullOrWhiteSpace(manifestData.ManifestPath)) { - this.Logger.LogWarning("Failed to deserialize manifest-info.json or missing ManifestPath at {Path}", pr.ComponentStream.Location); + this.Logger.LogDebug("Failed to deserialize manifest-info.json or missing ManifestPath at {Path}", pr.ComponentStream.Location); } else { @@ -178,7 +178,7 @@ private ISingleFileComponentRecorder GetManifestComponentRecorder(ISingleFileCom var vcpkgInstalledIndex = manifestFileLocation.IndexOf(vcpkgInstalled, StringComparison.OrdinalIgnoreCase); if (vcpkgInstalledIndex < 0) { - this.Logger.LogWarning( + this.Logger.LogDebug( "Could not find '{VcpkgInstalled}' in ManifestFileLocation: '{ManifestFileLocation}'. Returning original recorder.", vcpkgInstalled, manifestFileLocation); @@ -199,7 +199,7 @@ private ISingleFileComponentRecorder GetManifestComponentRecorder(ISingleFileCom else if (this.manifestMappings.TryGetValue(fallbackManifest, out manifestData) && manifestData != null) { // Use the fallback location. - this.Logger.LogWarning( + this.Logger.LogDebug( "Preferred manifest at '{PreferredManifest}' was not found or invalid. Using fallback manifest at '{FallbackManifest}'.", preferredManifest, fallbackManifest); @@ -208,7 +208,7 @@ private ISingleFileComponentRecorder GetManifestComponentRecorder(ISingleFileCom } else { - this.Logger.LogWarning( + this.Logger.LogDebug( "No valid manifest-info.json found at either '{PreferredManifest}' or '{FallbackManifest}' for base location '{VcpkgInstalledDir}'. Returning original recorder.", preferredManifest, fallbackManifest, From bb0f6d2ae790ae0516ca1f65057890b7f3eee42a Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Fri, 13 Jun 2025 06:49:07 -0700 Subject: [PATCH 03/12] Removed unnecessary else blocks after return statements --- .../vcpkg/VcpkgComponentDetector.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs index 0bbb893de..e7497a0a8 100644 --- a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs @@ -206,14 +206,12 @@ private ISingleFileComponentRecorder GetManifestComponentRecorder(ISingleFileCom return this.ComponentRecorder.CreateSingleFileComponentRecorder(manifestData.ManifestPath); } - else - { - this.Logger.LogDebug( - "No valid manifest-info.json found at either '{PreferredManifest}' or '{FallbackManifest}' for base location '{VcpkgInstalledDir}'. Returning original recorder.", - preferredManifest, - fallbackManifest, - vcpkgInstalledDir); - } + + this.Logger.LogDebug( + "No valid manifest-info.json found at either '{PreferredManifest}' or '{FallbackManifest}' for base location '{VcpkgInstalledDir}'. Returning original recorder.", + preferredManifest, + fallbackManifest, + vcpkgInstalledDir); } catch (Exception ex) { From 9ecb1859d912ad99b19df5e1a7f6f1fe1c7d8941 Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Fri, 13 Jun 2025 12:10:20 -0700 Subject: [PATCH 04/12] Adding tests for manifest resolution validation --- .../VcpkgComponentDetectorTests.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs index f149aec1b..ddc4a6b5d 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs @@ -177,4 +177,49 @@ public async Task TestInvalidFileAsync() var components = detectedComponents.ToList(); components.Should().BeEmpty(); } + + [TestMethod] + [DataTestMethod] + [DataRow("\\vcpkg_installed\\manifest-info.json", "path\\to\\vcpkg.json")] + [DataRow("\\vcpkg_installed\\vcpkg\\manifest-info.json", "path\\to\\vcpkg.json")] + [DataRow("\\bad_location\\manifest-info.json", "\\vcpkg_installed\\packageLocation\\vcpkg.spdx.json")] + public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToVcpkg) + { + 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#5"", + ""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 manifestFile = @"{ + ""manifest-path"": ""path\\to\\vcpkg.json"" +}"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("\\vcpkg_installed\\packageLocation\\vcpkg.spdx.json", spdxFile) + .WithFile(manifestPath, manifestFile) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + + var detectedComponents = componentRecorder.GetDependencyGraphsByLocation(); + + var singleFileComponent = detectedComponents.FirstOrDefault(); + singleFileComponent.Should().NotBeNull(); + singleFileComponent.Key.Should().Be(pathToVcpkg); + } } From 05e2ba879da3a42faffb8842f95de304f71bb30f Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Fri, 13 Jun 2025 12:43:59 -0700 Subject: [PATCH 05/12] Trimming prefixed /tmp/ from the path when running in github --- .../VcpkgComponentDetectorTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs index ddc4a6b5d..d65e10cfa 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs @@ -220,6 +220,10 @@ public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToV var singleFileComponent = detectedComponents.FirstOrDefault(); singleFileComponent.Should().NotBeNull(); - singleFileComponent.Key.Should().Be(pathToVcpkg); + + var sanitizedPathToVcpkg = pathToVcpkg.StartsWith("/tmp/") + ? pathToVcpkg[5..] : pathToVcpkg; + + singleFileComponent.Key.Should().Be(sanitizedPathToVcpkg); } } From 3e8a01b04f1f96d6af677104ef2573703fc8f0c1 Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Fri, 13 Jun 2025 13:25:53 -0700 Subject: [PATCH 06/12] Fixing tests --- .../VcpkgComponentDetectorTests.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs index d65e10cfa..decfd70e6 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs @@ -221,9 +221,13 @@ public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToV var singleFileComponent = detectedComponents.FirstOrDefault(); singleFileComponent.Should().NotBeNull(); - var sanitizedPathToVcpkg = pathToVcpkg.StartsWith("/tmp/") - ? pathToVcpkg[5..] : pathToVcpkg; + // When run in github the actual path may include a "/tmp/" prefix. + // To account for this, we dynamically add the prefix to the expected path if the actual path starts with "/tmp/". + if (singleFileComponent.Key.StartsWith("/tmp/")) + { + pathToVcpkg = "/tmp/" + pathToVcpkg; + } - singleFileComponent.Key.Should().Be(sanitizedPathToVcpkg); + singleFileComponent.Key.Should().Be(pathToVcpkg); } } From 3e243902f31dd8d3f6eaaf19d3f9f85b85cdbab6 Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Fri, 13 Jun 2025 14:57:47 -0700 Subject: [PATCH 07/12] Updating tests --- .../VcpkgComponentDetectorTests.cs | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs index decfd70e6..d5d942d42 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs @@ -3,6 +3,7 @@ namespace Microsoft.ComponentDetection.Detectors.Tests; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; using Microsoft.ComponentDetection.Common.DependencyGraph; @@ -180,11 +181,14 @@ public async Task TestInvalidFileAsync() [TestMethod] [DataTestMethod] - [DataRow("\\vcpkg_installed\\manifest-info.json", "path\\to\\vcpkg.json")] - [DataRow("\\vcpkg_installed\\vcpkg\\manifest-info.json", "path\\to\\vcpkg.json")] - [DataRow("\\bad_location\\manifest-info.json", "\\vcpkg_installed\\packageLocation\\vcpkg.spdx.json")] + [DataRow("vcpkg_installed\\manifest-info.json", "path\\to\\vcpkg.json")] + [DataRow("vcpkg_installed\\vcpkg\\manifest-info.json", "path\\to\\vcpkg.json")] + [DataRow("bad_location\\manifest-info.json", "vcpkg_installed\\packageLocation\\vcpkg.spdx.json")] public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToVcpkg) { + var t_pathToVcpkg = Path.GetFullPath(pathToVcpkg); + var t_manifestPath = Path.GetFullPath(manifestPath); + var spdxFile = @"{ ""SPDXID"": ""SPDXRef - DOCUMENT"", ""documentNamespace"": @@ -205,13 +209,13 @@ public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToV } ] }"; - var manifestFile = @"{ - ""manifest-path"": ""path\\to\\vcpkg.json"" -}"; + var manifestFile = $@"{{ + ""manifest-path"": {JsonSerializer.Serialize(t_pathToVcpkg)} +}}"; var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile("\\vcpkg_installed\\packageLocation\\vcpkg.spdx.json", spdxFile) - .WithFile(manifestPath, manifestFile) + .WithFile(Path.GetFullPath("vcpkg_installed\\packageLocation\\vcpkg.spdx.json"), spdxFile) + .WithFile(t_manifestPath, manifestFile) .ExecuteDetectorAsync(); scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); @@ -221,13 +225,6 @@ public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToV var singleFileComponent = detectedComponents.FirstOrDefault(); singleFileComponent.Should().NotBeNull(); - // When run in github the actual path may include a "/tmp/" prefix. - // To account for this, we dynamically add the prefix to the expected path if the actual path starts with "/tmp/". - if (singleFileComponent.Key.StartsWith("/tmp/")) - { - pathToVcpkg = "/tmp/" + pathToVcpkg; - } - - singleFileComponent.Key.Should().Be(pathToVcpkg); + singleFileComponent.Key.Should().Be(t_pathToVcpkg); } } From ee60bb2684268635a1acb9fed076510271b6ad59 Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Fri, 13 Jun 2025 15:21:58 -0700 Subject: [PATCH 08/12] Updating tests --- .../VcpkgComponentDetectorTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs index d5d942d42..1ffc84b55 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs @@ -3,7 +3,6 @@ namespace Microsoft.ComponentDetection.Detectors.Tests; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; using Microsoft.ComponentDetection.Common.DependencyGraph; @@ -210,7 +209,7 @@ public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToV ] }"; var manifestFile = $@"{{ - ""manifest-path"": {JsonSerializer.Serialize(t_pathToVcpkg)} + ""manifest-path"": ""{t_pathToVcpkg.Replace("\\", "\\\\")}"" }}"; var (scanResult, componentRecorder) = await this.DetectorTestUtility From ab94f4bd54ec86a305b230d3538a65b635460dd3 Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Fri, 13 Jun 2025 16:10:51 -0700 Subject: [PATCH 09/12] Updating tests --- .../VcpkgComponentDetectorTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs index 1ffc84b55..a40b55392 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs @@ -180,8 +180,8 @@ public async Task TestInvalidFileAsync() [TestMethod] [DataTestMethod] - [DataRow("vcpkg_installed\\manifest-info.json", "path\\to\\vcpkg.json")] - [DataRow("vcpkg_installed\\vcpkg\\manifest-info.json", "path\\to\\vcpkg.json")] + [DataRow("vcpkg_installed\\manifest-info.json", "vcpkg.json")] + [DataRow("vcpkg_installed\\vcpkg\\manifest-info.json", "vcpkg.json")] [DataRow("bad_location\\manifest-info.json", "vcpkg_installed\\packageLocation\\vcpkg.spdx.json")] public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToVcpkg) { From 6c381cd95a015cda8649f489dca40da11c7ba915 Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Fri, 13 Jun 2025 18:09:56 -0700 Subject: [PATCH 10/12] Updated tests now pass in WSL and Windows should work here --- .../VcpkgComponentDetectorTests.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs index a40b55392..246a56132 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/VcpkgComponentDetectorTests.cs @@ -1,5 +1,6 @@ namespace Microsoft.ComponentDetection.Detectors.Tests; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -185,8 +186,8 @@ public async Task TestInvalidFileAsync() [DataRow("bad_location\\manifest-info.json", "vcpkg_installed\\packageLocation\\vcpkg.spdx.json")] public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToVcpkg) { - var t_pathToVcpkg = Path.GetFullPath(pathToVcpkg); - var t_manifestPath = Path.GetFullPath(manifestPath); + var t_pathToVcpkg = CrossPlatformPath(Path.GetFullPath(pathToVcpkg)); + var t_manifestPath = CrossPlatformPath(Path.GetFullPath(manifestPath)); var spdxFile = @"{ ""SPDXID"": ""SPDXRef - DOCUMENT"", @@ -213,7 +214,7 @@ public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToV }}"; var (scanResult, componentRecorder) = await this.DetectorTestUtility - .WithFile(Path.GetFullPath("vcpkg_installed\\packageLocation\\vcpkg.spdx.json"), spdxFile) + .WithFile(CrossPlatformPath(Path.GetFullPath("vcpkg_installed\\packageLocation\\vcpkg.spdx.json")), spdxFile) .WithFile(t_manifestPath, manifestFile) .ExecuteDetectorAsync(); @@ -224,6 +225,13 @@ public async Task TestVcpkgManifestFileAsync(string manifestPath, string pathToV var singleFileComponent = detectedComponents.FirstOrDefault(); singleFileComponent.Should().NotBeNull(); - singleFileComponent.Key.Should().Be(t_pathToVcpkg); + var expectedResult = singleFileComponent.Key.Replace("/tmp/", string.Empty); + expectedResult.Should().Be(t_pathToVcpkg); + } + + private static string CrossPlatformPath(string relPath) + { + var segments = relPath.Split(['\\', '/'], StringSplitOptions.RemoveEmptyEntries); + return Path.Combine(segments); } } From 060a4432c00121103d287b8be8dc64d73f886bce Mon Sep 17 00:00:00 2001 From: Jacob Dotson Date: Wed, 18 Jun 2025 11:26:10 -0700 Subject: [PATCH 11/12] Fixing up PR feedback --- .../vcpkg/VcpkgComponentDetector.cs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs index e7497a0a8..924be966f 100644 --- a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs @@ -17,8 +17,11 @@ namespace Microsoft.ComponentDetection.Detectors.Vcpkg; public class VcpkgComponentDetector : FileComponentDetector { + private const string VcpkgInstalledFolder = "vcpkg_installed"; + private const string ManifestInfoFile = "manifest-info.json"; + private readonly HashSet projectRoots = []; - private readonly ConcurrentDictionary manifestMappings = new(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary manifestMappings = new(StringComparer.OrdinalIgnoreCase); private readonly ICommandLineInvocationService commandLineInvocationService; private readonly IEnvironmentVariableService envVarService; @@ -41,7 +44,7 @@ public VcpkgComponentDetector( public override IEnumerable Categories => [Enum.GetName(typeof(DetectorClass), DetectorClass.Vcpkg)]; - public override IList SearchPatterns { get; } = ["vcpkg.spdx.json", "manifest-info.json"]; + public override IList SearchPatterns { get; } = ["vcpkg.spdx.json", ManifestInfoFile]; public override IEnumerable SupportedComponentTypes { get; } = [ComponentType.Vcpkg]; @@ -72,7 +75,7 @@ await processRequests.ForEachAsync(async pr => var fileLocation = pr.ComponentStream.Location; var fileName = Path.GetFileName(fileLocation); - if (fileName.Equals("manifest-info.json", StringComparison.OrdinalIgnoreCase)) + if (fileName.Equals(ManifestInfoFile, StringComparison.OrdinalIgnoreCase)) { this.Logger.LogDebug("Discovered VCPKG package manifest file at: {Location}", pr.ComponentStream.Location); @@ -87,7 +90,7 @@ await processRequests.ForEachAsync(async pr => } else { - this.manifestMappings.TryAdd(fileLocation, manifestData); + this.manifestMappings.TryAdd(fileLocation, manifestData.ManifestPath); } } } @@ -172,31 +175,30 @@ private ISingleFileComponentRecorder GetManifestComponentRecorder(ISingleFileCom { try { - const string vcpkgInstalled = "vcpkg_installed"; var manifestFileLocation = singleFileComponentRecorder.ManifestFileLocation; - var vcpkgInstalledIndex = manifestFileLocation.IndexOf(vcpkgInstalled, StringComparison.OrdinalIgnoreCase); + var vcpkgInstalledIndex = manifestFileLocation.IndexOf(VcpkgInstalledFolder, StringComparison.OrdinalIgnoreCase); if (vcpkgInstalledIndex < 0) { this.Logger.LogDebug( "Could not find '{VcpkgInstalled}' in ManifestFileLocation: '{ManifestFileLocation}'. Returning original recorder.", - vcpkgInstalled, + VcpkgInstalledFolder, manifestFileLocation); return singleFileComponentRecorder; } - var vcpkgInstalledDir = manifestFileLocation[..(vcpkgInstalledIndex + vcpkgInstalled.Length)]; + var vcpkgInstalledDir = manifestFileLocation[..(vcpkgInstalledIndex + VcpkgInstalledFolder.Length)]; - var preferredManifest = Path.Combine(vcpkgInstalledDir, "vcpkg", "manifest-info.json"); - var fallbackManifest = Path.Combine(vcpkgInstalledDir, "manifest-info.json"); + var preferredManifest = Path.Combine(vcpkgInstalledDir, "vcpkg", ManifestInfoFile); + var fallbackManifest = Path.Combine(vcpkgInstalledDir, ManifestInfoFile); // Try preferred location first - if (this.manifestMappings.TryGetValue(preferredManifest, out var manifestData) && manifestData != null) + if (this.manifestMappings.TryGetValue(preferredManifest, out var manifestPath) && manifestPath != null) { - return this.ComponentRecorder.CreateSingleFileComponentRecorder(manifestData.ManifestPath); + return this.ComponentRecorder.CreateSingleFileComponentRecorder(manifestPath); } - else if (this.manifestMappings.TryGetValue(fallbackManifest, out manifestData) && manifestData != null) + else if (this.manifestMappings.TryGetValue(fallbackManifest, out manifestPath) && manifestPath != null) { // Use the fallback location. this.Logger.LogDebug( @@ -204,7 +206,7 @@ private ISingleFileComponentRecorder GetManifestComponentRecorder(ISingleFileCom preferredManifest, fallbackManifest); - return this.ComponentRecorder.CreateSingleFileComponentRecorder(manifestData.ManifestPath); + return this.ComponentRecorder.CreateSingleFileComponentRecorder(manifestPath); } this.Logger.LogDebug( From 711e4d9a78c4e4039fb2fcde6cfb5d7dfaa318da Mon Sep 17 00:00:00 2001 From: Greg Villicana <58237075+grvillic@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:52:26 -0700 Subject: [PATCH 12/12] Update VcpkgComponentDetector.cs Bump Detector version --- .../vcpkg/VcpkgComponentDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs index 924be966f..96988f925 100644 --- a/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/vcpkg/VcpkgComponentDetector.cs @@ -48,7 +48,7 @@ public VcpkgComponentDetector( public override IEnumerable SupportedComponentTypes { get; } = [ComponentType.Vcpkg]; - public override int Version => 2; + public override int Version => 3; protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary detectorArgs, CancellationToken cancellationToken = default) {