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  }