From 5a8f09277ed445fb63d295db458dc59449d39e77 Mon Sep 17 00:00:00 2001 From: Chris Gunn Date: Mon, 22 Jun 2026 16:36:41 -0700 Subject: [PATCH] Fix handling of Ruby versions. This change strips off the platform from the Ruby version strings. Ruby version strings are formatted differently from pure semantic versioning. Specifically, everything after the dash is the platform, not the pre-release version. For example: "1.19.4-x86_64-linux-gnu". Ruby represents pre-release versions by placing a letter in the main version string. For example: "2.0.0.rc1". See: https://guides.rubygems.org/patterns/#prerelease-gems --- .../ruby/RubyComponentDetector.cs | 18 +++++++--- .../RubyDetectorTest.cs | 35 +++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.ComponentDetection.Detectors/ruby/RubyComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/ruby/RubyComponentDetector.cs index 93d9b7f82..ac4b886bb 100644 --- a/src/Microsoft.ComponentDetection.Detectors/ruby/RubyComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/ruby/RubyComponentDetector.cs @@ -220,17 +220,27 @@ private void ParseSection(ISingleFileComponentRecorder singleFileComponentRecord wasParentDependencyExcluded = false; var splits = line.Trim().Split(" "); name = splits[0].Trim(); - var version = splits[1][1..^1]; + var fullVersion = splits[1][1..^1]; TypedComponent newComponent; - if (this.IsVersionRelative(version)) + if (this.IsVersionRelative(fullVersion)) { - this.Logger.LogWarning("Found component with invalid version, name = {RubyComponentName} and version = {RubyComponentVersion}", name, version); - singleFileComponentRecorder.RegisterPackageParseFailure($"{name} - {version}"); + this.Logger.LogWarning("Found component with invalid version, name = {RubyComponentName} and version = {RubyComponentVersion}", name, fullVersion); + singleFileComponentRecorder.RegisterPackageParseFailure($"{name} - {fullVersion}"); wasParentDependencyExcluded = true; continue; } + // Ruby version strings are formatted differently from pure semantic versioning. + // Specifically, everything after the dash is the platform, not the pre-release version. For + // example: "1.19.4-x86_64-linux-gnu". Ruby represents pre-release versions by placing a letter + // in the main version string. For example: "2.0.0.rc1". + // See: https://guides.rubygems.org/patterns/#prerelease-gems + + // Remove the platform from the version string (if it exists). + var versionParts = fullVersion.Split("-", 2); + var version = versionParts[0]; + if (sectionType == SectionType.GEM || sectionType == SectionType.PATH) { newComponent = new RubyGemsComponent(name, version, remote); diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/RubyDetectorTest.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/RubyDetectorTest.cs index 44cb9543c..59cde69b3 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/RubyDetectorTest.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/RubyDetectorTest.cs @@ -405,6 +405,41 @@ public async Task TestRubyDetector_DetectorRecognizeLocalDependenciesAsync() this.AssertRubyComponentNameAndVersion(detectedComponents, name: "test2", version: "1.0.0"); } + [TestMethod] + public async Task TestRubyDetector_StripPlatformAsync() + { + var gemFileLockContent = @"GEM + remote: https://rubygems.org/ + specs: + nokogiri (1.19.4-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.4-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.4-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.4-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.4-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.4-x86_64-linux-musl) + racc (~> 1.4)"; + + var (scanResult, componentRecorder) = await this.detectorTestUtility + .WithFile("1Gemfile.lock", gemFileLockContent) + .ExecuteDetectorAsync(); + + scanResult.ResultCode.Should().Be(ProcessingResultCode.Success); + + var detectedComponents = componentRecorder.GetDetectedComponents(); + detectedComponents.Should().HaveCount(1); + + this.AssertRubyComponentNameAndVersion(detectedComponents, "nokogiri", "1.19.4"); + } + private void AssertRubyComponentNameAndVersion(IEnumerable detectedComponents, string name, string version) { detectedComponents.SingleOrDefault(c =>