@@ -556,6 +556,86 @@ func generatePythonDeps(ctx context.Context, hdl SCAHandle, generated *config.De
556556 return nil
557557}
558558
559+ // generateRubyDeps generates a ruby-X.Y dependency for packages which ship
560+ // Ruby gems.
561+ func generateRubyDeps (ctx context.Context , hdl SCAHandle , generated * config.Dependencies ) error {
562+ log := clog .FromContext (ctx )
563+ log .Infof ("scanning for ruby gems..." )
564+
565+ fsys , err := hdl .Filesystem ()
566+ if err != nil {
567+ return err
568+ }
569+
570+ var rubyGemVer string
571+ if err := fs .WalkDir (fsys , "." , func (path string , d fs.DirEntry , err error ) error {
572+ if err != nil {
573+ return err
574+ }
575+
576+ // Ruby gems are installed in paths such as /usr/lib/ruby/gems/X.Y.Z/gems/...,
577+ // so if we find a directory named gems, and its parent is a ruby directory,
578+ // then we have a Ruby gem directory.
579+ basename := filepath .Base (path )
580+ if basename != "gems" {
581+ return nil
582+ }
583+
584+ parent := filepath .Dir (path )
585+ basename = filepath .Base (parent )
586+
587+ // The gems path we want is nested in another gems directory, where the parent
588+ // contains the Ruby version. Return if the parent is the Ruby directory
589+ if basename == "ruby" {
590+ return nil
591+ }
592+
593+ // Ruby versions are formatted as major.minor.patch
594+ majorMinorPatch := `^\d+\.\d+\.\d+$`
595+
596+ // Compile expected Ruby version format
597+ re := regexp .MustCompile (majorMinorPatch )
598+
599+ // Match the directory against version format
600+ if ! re .MatchString (basename ) {
601+ return nil
602+ }
603+
604+ // This probably shouldn't ever happen, but lets check to make sure.
605+ if ! d .IsDir () {
606+ return nil
607+ }
608+
609+ // This takes the X.Y part of the ruby/gems/X.Y.Z directory name as the version to pin against.
610+ // If the X.Y part is not present, then rubyModuleVer will remain an empty string and
611+ // no dependency will be generated.
612+ rubyGemVer = basename [:3 ]
613+ return nil
614+ }); err != nil {
615+ return err
616+ }
617+
618+ // Nothing to do...
619+ if rubyGemVer == "" {
620+ return nil
621+ }
622+
623+ // Do not add a Ruby dependency if one already exists.
624+ for _ , dep := range hdl .BaseDependencies ().Runtime {
625+ if strings .HasPrefix (dep , "ruby" ) {
626+ log .Warnf ("%s: Ruby dependency %q already specified, consider removing it in favor of SCA-generated dependency" , hdl .PackageName (), dep )
627+ return nil
628+ }
629+ }
630+
631+ log .Infof (" found ruby gem, generating ruby-%s dependency" , rubyGemVer )
632+ if ! hdl .Options ().NoDepends {
633+ generated .Runtime = append (generated .Runtime , fmt .Sprintf ("ruby-%s" , rubyGemVer ))
634+ }
635+
636+ return nil
637+ }
638+
559639func sonameLibver (soname string ) string {
560640 parts := strings .Split (soname , ".so." )
561641 if len (parts ) < 2 {
@@ -677,6 +757,7 @@ func Analyze(ctx context.Context, hdl SCAHandle, generated *config.Dependencies)
677757 generateCmdProviders ,
678758 generatePkgConfigDeps ,
679759 generatePythonDeps ,
760+ generateRubyDeps ,
680761 generateShbangDeps ,
681762 }
682763
0 commit comments