github.com/sdboyer/gps@v0.16.3/solve_failures.go (about) 1 package gps 2 3 import ( 4 "bytes" 5 "fmt" 6 "sort" 7 "strings" 8 ) 9 10 type errorLevel uint8 11 12 // TODO(sdboyer) consistent, sensible way of handling 'type' and 'severity' - or figure 13 // out that they're not orthogonal and collapse into just 'type' 14 15 const ( 16 warning errorLevel = 1 << iota 17 mustResolve 18 cannotResolve 19 ) 20 21 func a2vs(a atom) string { 22 if a.v == rootRev || a.v == nil { 23 return "(root)" 24 } 25 26 return fmt.Sprintf("%s@%s", a.id.errString(), a.v) 27 } 28 29 type traceError interface { 30 traceString() string 31 } 32 33 type noVersionError struct { 34 pn ProjectIdentifier 35 fails []failedVersion 36 } 37 38 func (e *noVersionError) Error() string { 39 if len(e.fails) == 0 { 40 return fmt.Sprintf("No versions found for project %q.", e.pn.ProjectRoot) 41 } 42 43 var buf bytes.Buffer 44 fmt.Fprintf(&buf, "No versions of %s met constraints:", e.pn.ProjectRoot) 45 for _, f := range e.fails { 46 fmt.Fprintf(&buf, "\n\t%s: %s", f.v, f.f.Error()) 47 } 48 49 return buf.String() 50 } 51 52 func (e *noVersionError) traceString() string { 53 if len(e.fails) == 0 { 54 return fmt.Sprintf("No versions found") 55 } 56 57 var buf bytes.Buffer 58 fmt.Fprintf(&buf, "No versions of %s met constraints:", e.pn.ProjectRoot) 59 for _, f := range e.fails { 60 if te, ok := f.f.(traceError); ok { 61 fmt.Fprintf(&buf, "\n %s: %s", f.v, te.traceString()) 62 } else { 63 fmt.Fprintf(&buf, "\n %s: %s", f.v, f.f.Error()) 64 } 65 } 66 67 return buf.String() 68 } 69 70 // disjointConstraintFailure occurs when attempting to introduce an atom that 71 // itself has an acceptable version, but one of its dependency constraints is 72 // disjoint with one or more dependency constraints already active for that 73 // identifier. 74 type disjointConstraintFailure struct { 75 // goal is the dependency with the problematic constraint, forcing us to 76 // reject the atom that introduces it. 77 goal dependency 78 // failsib is the list of active dependencies that are disjoint with the 79 // goal dependency. This will be at least one, but may not be all of the 80 // active dependencies. 81 failsib []dependency 82 // nofailsib is the list of active dependencies that are NOT disjoint with 83 // the goal dependency. The total of nofailsib and failsib will always be 84 // the total number of active dependencies on target identifier. 85 nofailsib []dependency 86 // c is the current constraint on the target identifier. It is intersection 87 // of all the active dependencies' constraints. 88 c Constraint 89 } 90 91 func (e *disjointConstraintFailure) Error() string { 92 if len(e.failsib) == 1 { 93 str := "Could not introduce %s, as it has a dependency on %s with constraint %s, which has no overlap with existing constraint %s from %s" 94 return fmt.Sprintf(str, a2vs(e.goal.depender), e.goal.dep.Ident.errString(), e.goal.dep.Constraint.String(), e.failsib[0].dep.Constraint.String(), a2vs(e.failsib[0].depender)) 95 } 96 97 var buf bytes.Buffer 98 99 var sibs []dependency 100 if len(e.failsib) > 1 { 101 sibs = e.failsib 102 103 str := "Could not introduce %s, as it has a dependency on %s with constraint %s, which has no overlap with the following existing constraints:\n" 104 fmt.Fprintf(&buf, str, a2vs(e.goal.depender), e.goal.dep.Ident.errString(), e.goal.dep.Constraint.String()) 105 } else { 106 sibs = e.nofailsib 107 108 str := "Could not introduce %s, as it has a dependency on %s with constraint %s, which does not overlap with the intersection of existing constraints from other currently selected packages:\n" 109 fmt.Fprintf(&buf, str, a2vs(e.goal.depender), e.goal.dep.Ident.errString(), e.goal.dep.Constraint.String()) 110 } 111 112 for _, c := range sibs { 113 fmt.Fprintf(&buf, "\t%s from %s\n", c.dep.Constraint.String(), a2vs(c.depender)) 114 } 115 116 return buf.String() 117 } 118 119 func (e *disjointConstraintFailure) traceString() string { 120 var buf bytes.Buffer 121 fmt.Fprintf(&buf, "constraint %s on %s disjoint with other dependers:\n", e.goal.dep.Constraint.String(), e.goal.dep.Ident.errString()) 122 for _, f := range e.failsib { 123 fmt.Fprintf( 124 &buf, 125 "%s from %s (no overlap)\n", 126 f.dep.Constraint.String(), 127 a2vs(f.depender), 128 ) 129 } 130 for _, f := range e.nofailsib { 131 fmt.Fprintf( 132 &buf, 133 "%s from %s (some overlap)\n", 134 f.dep.Constraint.String(), 135 a2vs(f.depender), 136 ) 137 } 138 139 return buf.String() 140 } 141 142 // Indicates that an atom could not be introduced because one of its dep 143 // constraints does not admit the currently-selected version of the target 144 // project. 145 type constraintNotAllowedFailure struct { 146 // The dependency with the problematic constraint that could not be 147 // introduced. 148 goal dependency 149 // The (currently selected) version of the target project that was not 150 // admissible by the goal dependency. 151 v Version 152 } 153 154 func (e *constraintNotAllowedFailure) Error() string { 155 return fmt.Sprintf( 156 "Could not introduce %s, as it has a dependency on %s with constraint %s, which does not allow the currently selected version of %s", 157 a2vs(e.goal.depender), 158 e.goal.dep.Ident.errString(), 159 e.goal.dep.Constraint, 160 e.v, 161 ) 162 } 163 164 func (e *constraintNotAllowedFailure) traceString() string { 165 return fmt.Sprintf( 166 "%s depends on %s with %s, but that's already selected at %s", 167 a2vs(e.goal.depender), 168 e.goal.dep.Ident.ProjectRoot, 169 e.goal.dep.Constraint, 170 e.v, 171 ) 172 } 173 174 // versionNotAllowedFailure describes a failure where an atom is rejected 175 // because its version is not allowed by current constraints. 176 // 177 // (This is one of the more straightforward types of failures) 178 type versionNotAllowedFailure struct { 179 // goal is the atom that was rejected by current constraints. 180 goal atom 181 // failparent is the list of active dependencies that caused the atom to be 182 // rejected. Note that this only includes dependencies that actually 183 // rejected the atom, which will be at least one, but may not be all the 184 // active dependencies on the atom's identifier. 185 failparent []dependency 186 // c is the current constraint on the atom's identifier. This is the intersection 187 // of all active dependencies' constraints. 188 c Constraint 189 } 190 191 func (e *versionNotAllowedFailure) Error() string { 192 if len(e.failparent) == 1 { 193 return fmt.Sprintf( 194 "Could not introduce %s, as it is not allowed by constraint %s from project %s.", 195 a2vs(e.goal), 196 e.failparent[0].dep.Constraint.String(), 197 e.failparent[0].depender.id.errString(), 198 ) 199 } 200 201 var buf bytes.Buffer 202 203 fmt.Fprintf(&buf, "Could not introduce %s, as it is not allowed by constraints from the following projects:\n", a2vs(e.goal)) 204 205 for _, f := range e.failparent { 206 fmt.Fprintf(&buf, "\t%s from %s\n", f.dep.Constraint.String(), a2vs(f.depender)) 207 } 208 209 return buf.String() 210 } 211 212 func (e *versionNotAllowedFailure) traceString() string { 213 var buf bytes.Buffer 214 215 fmt.Fprintf(&buf, "%s not allowed by constraint %s:\n", a2vs(e.goal), e.c.String()) 216 for _, f := range e.failparent { 217 fmt.Fprintf(&buf, " %s from %s\n", f.dep.Constraint.String(), a2vs(f.depender)) 218 } 219 220 return buf.String() 221 } 222 223 type missingSourceFailure struct { 224 goal ProjectIdentifier 225 prob string 226 } 227 228 func (e *missingSourceFailure) Error() string { 229 return fmt.Sprintf(e.prob, e.goal) 230 } 231 232 type badOptsFailure string 233 234 func (e badOptsFailure) Error() string { 235 return string(e) 236 } 237 238 type sourceMismatchFailure struct { 239 // The ProjectRoot over which there is disagreement about where it should be 240 // sourced from 241 shared ProjectRoot 242 // The current value for the network source 243 current string 244 // The mismatched value for the network source 245 mismatch string 246 // The currently selected dependencies which have agreed upon/established 247 // the given network source 248 sel []dependency 249 // The atom with the constraint that has the new, incompatible network source 250 prob atom 251 } 252 253 func (e *sourceMismatchFailure) Error() string { 254 var cur []string 255 for _, c := range e.sel { 256 cur = append(cur, string(c.depender.id.ProjectRoot)) 257 } 258 259 str := "Could not introduce %s, as it depends on %s from %s, but %s is already marked as coming from %s by %s" 260 return fmt.Sprintf(str, a2vs(e.prob), e.shared, e.mismatch, e.shared, e.current, strings.Join(cur, ", ")) 261 } 262 263 func (e *sourceMismatchFailure) traceString() string { 264 var buf bytes.Buffer 265 fmt.Fprintf(&buf, "disagreement on network addr for %s:\n", e.shared) 266 267 fmt.Fprintf(&buf, " %s from %s\n", e.mismatch, e.prob.id.errString()) 268 for _, dep := range e.sel { 269 fmt.Fprintf(&buf, " %s from %s\n", e.current, dep.depender.id.errString()) 270 } 271 272 return buf.String() 273 } 274 275 type errDeppers struct { 276 err error 277 deppers []atom 278 } 279 280 // checkeeHasProblemPackagesFailure indicates that the goal atom was rejected 281 // because one or more of the packages required by its deppers had errors. 282 // 283 // "errors" includes package nonexistence, which is indicated by a nil err in 284 // the corresponding errDeppers failpkg map value. 285 // 286 // checkeeHasProblemPackagesFailure complements depHasProblemPackagesFailure; 287 // one or the other could appear to describe the same fundamental issue, 288 // depending on the order in which dependencies were visited. 289 type checkeeHasProblemPackagesFailure struct { 290 // goal is the atom that was rejected due to problematic packages. 291 goal atom 292 // failpkg is a map of package names to the error describing the problem 293 // with them, plus a list of the selected atoms that require that package. 294 failpkg map[string]errDeppers 295 } 296 297 func (e *checkeeHasProblemPackagesFailure) Error() string { 298 var buf bytes.Buffer 299 indent := "" 300 301 if len(e.failpkg) > 1 { 302 indent = "\t" 303 fmt.Fprintf( 304 &buf, "Could not introduce %s due to multiple problematic subpackages:\n", 305 a2vs(e.goal), 306 ) 307 } 308 309 for pkg, errdep := range e.failpkg { 310 var cause string 311 if errdep.err == nil { 312 cause = "is missing" 313 } else { 314 cause = fmt.Sprintf("does not contain usable Go code (%T).", errdep.err) 315 } 316 317 if len(e.failpkg) == 1 { 318 fmt.Fprintf( 319 &buf, "Could not introduce %s, as its subpackage %s %s.", 320 a2vs(e.goal), 321 pkg, 322 cause, 323 ) 324 } else { 325 fmt.Fprintf(&buf, "\tSubpackage %s %s.", pkg, cause) 326 } 327 328 if len(errdep.deppers) == 1 { 329 fmt.Fprintf( 330 &buf, " (Package is required by %s.)", 331 a2vs(errdep.deppers[0]), 332 ) 333 } else { 334 fmt.Fprintf(&buf, " Package is required by:") 335 for _, pa := range errdep.deppers { 336 fmt.Fprintf(&buf, "\n%s\t%s", indent, a2vs(pa)) 337 } 338 } 339 } 340 341 return buf.String() 342 } 343 344 func (e *checkeeHasProblemPackagesFailure) traceString() string { 345 var buf bytes.Buffer 346 347 fmt.Fprintf(&buf, "%s at %s has problem subpkg(s):\n", e.goal.id.ProjectRoot, e.goal.v) 348 for pkg, errdep := range e.failpkg { 349 if errdep.err == nil { 350 fmt.Fprintf(&buf, "\t%s is missing; ", pkg) 351 } else { 352 fmt.Fprintf(&buf, "\t%s has err (%T); ", pkg, errdep.err) 353 } 354 355 if len(errdep.deppers) == 1 { 356 fmt.Fprintf(&buf, "required by %s.", a2vs(errdep.deppers[0])) 357 } else { 358 fmt.Fprintf(&buf, " required by:") 359 for _, pa := range errdep.deppers { 360 fmt.Fprintf(&buf, "\n\t\t%s at %s", pa.id.errString(), pa.v) 361 } 362 } 363 } 364 365 return buf.String() 366 } 367 368 // depHasProblemPackagesFailure indicates that the goal dependency was rejected 369 // because there were problems with one or more of the packages the dependency 370 // requires in the atom currently selected for that dependency. (This failure 371 // can only occur if the target dependency is already selected.) 372 // 373 // "errors" includes package nonexistence, which is indicated by a nil err as 374 // the corresponding prob map value. 375 // 376 // depHasProblemPackagesFailure complements checkeeHasProblemPackagesFailure; 377 // one or the other could appear to describe the same fundamental issue, 378 // depending on the order in which dependencies were visited. 379 type depHasProblemPackagesFailure struct { 380 // goal is the dependency that was rejected due to the atom currently 381 // selected for the dependency's target id having errors (including, and 382 // probably most commonly, 383 // nonexistence) in one or more packages named by the dependency. 384 goal dependency 385 // v is the version of the currently selected atom targeted by the goal 386 // dependency. 387 v Version 388 // prob is a map of problem packages to their specific error. It does not 389 // include missing packages. 390 prob map[string]error 391 } 392 393 func (e *depHasProblemPackagesFailure) Error() string { 394 fcause := func(pkg string) string { 395 if err := e.prob[pkg]; err != nil { 396 return fmt.Sprintf("does not contain usable Go code (%T).", err) 397 } 398 return "is missing." 399 } 400 401 if len(e.prob) == 1 { 402 var pkg string 403 for pkg = range e.prob { 404 } 405 406 return fmt.Sprintf( 407 "Could not introduce %s, as it requires package %s from %s, but in version %s that package %s", 408 a2vs(e.goal.depender), 409 pkg, 410 e.goal.dep.Ident.errString(), 411 e.v, 412 fcause(pkg), 413 ) 414 } 415 416 var buf bytes.Buffer 417 fmt.Fprintf( 418 &buf, "Could not introduce %s, as it requires problematic packages from %s (current version %s):", 419 a2vs(e.goal.depender), 420 e.goal.dep.Ident.errString(), 421 e.v, 422 ) 423 424 pkgs := make([]string, len(e.prob)) 425 k := 0 426 for pkg := range e.prob { 427 pkgs[k] = pkg 428 k++ 429 } 430 sort.Strings(pkgs) 431 for _, pkg := range pkgs { 432 fmt.Fprintf(&buf, "\t%s %s", pkg, fcause(pkg)) 433 } 434 435 return buf.String() 436 } 437 438 func (e *depHasProblemPackagesFailure) traceString() string { 439 var buf bytes.Buffer 440 fcause := func(pkg string) string { 441 if err := e.prob[pkg]; err != nil { 442 return fmt.Sprintf("has parsing err (%T).", err) 443 } 444 return "is missing" 445 } 446 447 fmt.Fprintf( 448 &buf, "%s depping on %s at %s has problem subpkg(s):", 449 a2vs(e.goal.depender), 450 e.goal.dep.Ident.errString(), 451 e.v, 452 ) 453 454 pkgs := make([]string, len(e.prob)) 455 k := 0 456 for pkg := range e.prob { 457 pkgs[k] = pkg 458 k++ 459 } 460 sort.Strings(pkgs) 461 for _, pkg := range pkgs { 462 fmt.Fprintf(&buf, "\t%s %s", pkg, fcause(pkg)) 463 } 464 465 return buf.String() 466 } 467 468 // nonexistentRevisionFailure indicates that a revision constraint was specified 469 // for a given project, but that that revision does not exist in the source 470 // repository. 471 type nonexistentRevisionFailure struct { 472 goal dependency 473 r Revision 474 } 475 476 func (e *nonexistentRevisionFailure) Error() string { 477 return fmt.Sprintf( 478 "Could not introduce %s, as it requires %s at revision %s, but that revision does not exist", 479 a2vs(e.goal.depender), 480 e.goal.dep.Ident.errString(), 481 e.r, 482 ) 483 } 484 485 func (e *nonexistentRevisionFailure) traceString() string { 486 return fmt.Sprintf( 487 "%s wants missing rev %s of %s", 488 a2vs(e.goal.depender), 489 e.r, 490 e.goal.dep.Ident.errString(), 491 ) 492 }