|
6 | 6 | "os" |
7 | 7 | "os/exec" |
8 | 8 | "path/filepath" |
| 9 | + "regexp" |
9 | 10 | "runtime" |
10 | 11 | "strings" |
11 | 12 | ) |
@@ -104,12 +105,16 @@ func (b *ExtConfBuilder) runExtConf(ctx context.Context, config *BuildConfig, ex |
104 | 105 | } |
105 | 106 |
|
106 | 107 | if err != nil { |
| 108 | + // Parse output for missing dependencies |
| 109 | + result.MissingDependencies = b.parseLoadErrors(result.Output) |
107 | 110 | return BuildError("ExtConf", result.Output, err) |
108 | 111 | } |
109 | 112 |
|
110 | 113 | // Verify Makefile was created |
111 | 114 | makefilePath := filepath.Join(extensionDir, "Makefile") |
112 | 115 | if _, err := os.Stat(makefilePath); os.IsNotExist(err) { |
| 116 | + // Parse output for missing dependencies even when Makefile wasn't generated |
| 117 | + result.MissingDependencies = b.parseLoadErrors(result.Output) |
113 | 118 | return BuildError("ExtConf", result.Output, fmt.Errorf("makefile not generated")) |
114 | 119 | } |
115 | 120 |
|
@@ -229,3 +234,58 @@ func (b *ExtConfBuilder) getMakeProgram() string { |
229 | 234 | return "make" |
230 | 235 | } |
231 | 236 | } |
| 237 | + |
| 238 | +// parseLoadErrors parses build output for missing dependencies. |
| 239 | +// It recognizes common Ruby error patterns: |
| 240 | +// - "cannot load such file -- gem_name" |
| 241 | +// - "Could not find 'gem_name' (~> 1.0)" |
| 242 | +// - "Gem::MissingSpecVersionError: gem_name requires version ~> 1.0" |
| 243 | +// - "LoadError: cannot load such file -- gem_name/subpath" |
| 244 | +func (b *ExtConfBuilder) parseLoadErrors(output []string) []MissingDependency { |
| 245 | + var deps []MissingDependency |
| 246 | + seen := make(map[string]bool) |
| 247 | + |
| 248 | + // Regex patterns for different error formats |
| 249 | + patterns := []*regexp.Regexp{ |
| 250 | + // "cannot load such file -- mini_portile2" or "cannot load such file -- mini_portile2/version" |
| 251 | + regexp.MustCompile(`cannot load such file -- ([a-zA-Z0-9_-]+)`), |
| 252 | + // "Could not find 'mini_portile2' (~> 2.8.2)" |
| 253 | + regexp.MustCompile(`Could not find '([^']+)'(?: \(([^)]+)\))?`), |
| 254 | + // "Gem::MissingSpecVersionError: mini_portile2 requires ~> 2.8.2" |
| 255 | + regexp.MustCompile(`Gem::MissingSpec(?:Version)?Error:?\s*([a-zA-Z0-9_-]+)(?:\s+requires?\s+(.+))?`), |
| 256 | + // "Bundler could not find compatible versions for gem \"mini_portile2\":" |
| 257 | + regexp.MustCompile(`compatible versions for gem "([^"]+)"`), |
| 258 | + // "mini_portile2 (~> 2.8.2) was resolved to 2.8.2" |
| 259 | + regexp.MustCompile(`^([a-zA-Z0-9_-]+) \(([^)]+)\) was resolved`), |
| 260 | + } |
| 261 | + |
| 262 | + for _, line := range output { |
| 263 | + for i, re := range patterns { |
| 264 | + matches := re.FindStringSubmatch(line) |
| 265 | + if matches == nil { |
| 266 | + continue |
| 267 | + } |
| 268 | + |
| 269 | + name := matches[1] |
| 270 | + if seen[name] { |
| 271 | + continue |
| 272 | + } |
| 273 | + seen[name] = true |
| 274 | + |
| 275 | + dep := MissingDependency{Name: name} |
| 276 | + |
| 277 | + // Extract version constraint if present |
| 278 | + if i == 1 && len(matches) > 2 && matches[2] != "" { |
| 279 | + dep.Constraint = matches[2] |
| 280 | + } else if i == 2 && len(matches) > 2 && matches[2] != "" { |
| 281 | + dep.Constraint = strings.TrimSpace(matches[2]) |
| 282 | + } else if i == 4 && len(matches) > 2 { |
| 283 | + dep.Constraint = matches[2] |
| 284 | + } |
| 285 | + |
| 286 | + deps = append(deps, dep) |
| 287 | + } |
| 288 | + } |
| 289 | + |
| 290 | + return deps |
| 291 | +} |
0 commit comments