Skip to content

Commit 98631c0

Browse files
committed
♻️ Refactor: Optimize Ruby gem lookup with caching
- Implement `getAllGemspecs` to cache gemspec paths, reducing I/O overhead during dependency resolution. - Update `findInstalledGemspec` and `fetchInstalledLicense` to utilize the cached gemspec list instead of repeatedly scanning directories. - Introduce `sync.Mutex` to ensure thread-safe access to the gemspec cache. - Key the cache by `GEM_PATH` environment variable to support changes in the environment during runtime (e.g. in tests).
1 parent 89b4db5 commit 98631c0

1 file changed

Lines changed: 75 additions & 51 deletions

File tree

pkg/deps/ruby.go

Lines changed: 75 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"regexp"
2929
"strconv"
3030
"strings"
31+
"sync"
3132
"time"
3233

3334
"github.com/apache/skywalking-eyes/pkg/logger"
@@ -447,44 +448,73 @@ func parseGemspecDependencies(path string) ([]string, error) {
447448
return deps, scanner.Err()
448449
}
449450

450-
func findInstalledGemspec(name, version string) (string, error) {
451+
var (
452+
gemspecsCache map[string][]string
453+
gemspecsCacheLock sync.Mutex
454+
)
455+
456+
func getAllGemspecs() []string {
457+
env := os.Getenv("GEM_PATH")
458+
if env == "" {
459+
env = os.Getenv("GEM_HOME")
460+
}
461+
462+
gemspecsCacheLock.Lock()
463+
defer gemspecsCacheLock.Unlock()
464+
465+
if gemspecsCache == nil {
466+
gemspecsCache = make(map[string][]string)
467+
}
468+
469+
if cached, ok := gemspecsCache[env]; ok {
470+
return cached
471+
}
472+
473+
var allGemspecs []string
451474
gemPaths := getGemPaths()
452475
for _, dir := range gemPaths {
453476
specsDir := filepath.Join(dir, "specifications")
477+
entries, err := os.ReadDir(specsDir)
478+
if err != nil {
479+
continue
480+
}
481+
for _, e := range entries {
482+
if !e.IsDir() && strings.HasSuffix(e.Name(), ".gemspec") {
483+
allGemspecs = append(allGemspecs, filepath.Join(specsDir, e.Name()))
484+
}
485+
}
486+
}
487+
gemspecsCache[env] = allGemspecs
488+
return allGemspecs
489+
}
490+
491+
func findInstalledGemspec(name, version string) (string, error) {
492+
gems := getAllGemspecs()
493+
for _, path := range gems {
494+
filename := filepath.Base(path)
454495
if version != "" && rubyVersionRe.MatchString(version) {
455-
path := filepath.Join(specsDir, name+"-"+version+".gemspec")
456-
if _, err := os.Stat(path); err == nil {
496+
if filename == name+"-"+version+".gemspec" {
457497
return path, nil
458498
}
459499
} else {
460-
entries, err := os.ReadDir(specsDir)
461-
if err != nil {
500+
if !strings.HasPrefix(filename, name+"-") {
462501
continue
463502
}
464-
for _, e := range entries {
465-
if e.IsDir() || !strings.HasPrefix(e.Name(), name+"-") || !strings.HasSuffix(e.Name(), ".gemspec") {
466-
continue
467-
}
468-
stem := strings.TrimSuffix(e.Name(), ".gemspec")
469-
// Ensure that the character immediately after the "name-" prefix
470-
// is a digit, so we only consider filenames where the suffix is
471-
// a version component (e.g., "foo-1.0.0.gemspec") and avoid
472-
// similar names like "foo-bar-1.0.0.gemspec" when searching for "foo".
473-
if len(stem) <= len(name)+1 {
474-
continue
475-
}
476-
versionStart := stem[len(name)+1]
477-
if versionStart < '0' || versionStart > '9' {
478-
continue
479-
}
480-
ver := strings.TrimPrefix(stem, name+"-")
481-
if ver == stem {
482-
continue
483-
}
484-
path := filepath.Join(specsDir, e.Name())
485-
if specName, _, err := parseGemspecInfo(path); err == nil && specName == name {
486-
return path, nil
487-
}
503+
stem := strings.TrimSuffix(filename, ".gemspec")
504+
// Ensure that the character immediately after the "name-" prefix
505+
// is a digit, so we only consider filenames where the suffix is
506+
// a version component (e.g., "foo-1.0.0.gemspec") and avoid
507+
// similar names like "foo-bar-1.0.0.gemspec" when searching for "foo".
508+
if len(stem) <= len(name)+1 {
509+
continue
510+
}
511+
versionStart := stem[len(name)+1]
512+
if versionStart < '0' || versionStart > '9' {
513+
continue
514+
}
515+
516+
if specName, _, err := parseGemspecInfo(path); err == nil && specName == name {
517+
return path, nil
488518
}
489519
}
490520
}
@@ -510,35 +540,29 @@ func fetchLocalLicense(dir, targetName string) (string, error) {
510540
}
511541

512542
func fetchInstalledLicense(name, version string) string {
513-
gemPaths := getGemPaths()
514-
for _, dir := range gemPaths {
515-
specsDir := filepath.Join(dir, "specifications")
543+
gems := getAllGemspecs()
544+
for _, path := range gems {
545+
filename := filepath.Base(path)
516546
// If version is specific
517547
if version != "" && rubyVersionRe.MatchString(version) {
518-
path := filepath.Join(specsDir, name+"-"+version+".gemspec")
519-
if _, license, err := parseGemspecInfo(path); err == nil && license != "" {
520-
return license
548+
if filename == name+"-"+version+".gemspec" {
549+
if _, license, err := parseGemspecInfo(path); err == nil && license != "" {
550+
return license
551+
}
521552
}
522553
} else {
523554
// Scan for any version
524-
entries, err := os.ReadDir(specsDir)
525-
if err != nil {
555+
if !strings.HasPrefix(filename, name+"-") {
526556
continue
527557
}
528-
for _, e := range entries {
529-
if e.IsDir() || !strings.HasPrefix(e.Name(), name+"-") || !strings.HasSuffix(e.Name(), ".gemspec") {
530-
continue
531-
}
532-
stem := strings.TrimSuffix(e.Name(), ".gemspec")
533-
ver := strings.TrimPrefix(stem, name+"-")
534-
// Ensure the character after the gem name corresponds to the start of a version
535-
if ver == "" || ver[0] < '0' || ver[0] > '9' {
536-
continue
537-
}
538-
path := filepath.Join(specsDir, e.Name())
539-
if specName, license, err := parseGemspecInfo(path); err == nil && specName == name && license != "" {
540-
return license
541-
}
558+
stem := strings.TrimSuffix(filename, ".gemspec")
559+
ver := strings.TrimPrefix(stem, name+"-")
560+
// Ensure the character after the gem name corresponds to the start of a version
561+
if ver == "" || ver[0] < '0' || ver[0] > '9' {
562+
continue
563+
}
564+
if specName, license, err := parseGemspecInfo(path); err == nil && specName == name && license != "" {
565+
return license
542566
}
543567
}
544568
}

0 commit comments

Comments
 (0)