github.com/sdboyer/gps@v0.16.3/satisfy.go (about)

     1  package gps
     2  
     3  // check performs constraint checks on the provided atom. The set of checks
     4  // differ slightly depending on whether the atom is pkgonly, or if it's the
     5  // entire project being added for the first time.
     6  //
     7  // The goal is to determine whether selecting the atom would result in a state
     8  // where all the solver requirements are still satisfied.
     9  func (s *solver) check(a atomWithPackages, pkgonly bool) error {
    10  	s.mtr.push("satisfy")
    11  	pa := a.a
    12  	if nilpa == pa {
    13  		// This shouldn't be able to happen, but if it does, it unequivocally
    14  		// indicates a logical bug somewhere, so blowing up is preferable
    15  		panic("canary - checking version of empty ProjectAtom")
    16  	}
    17  
    18  	// If we're pkgonly, then base atom was already determined to be allowable,
    19  	// so we can skip the checkAtomAllowable step.
    20  	if !pkgonly {
    21  		if err := s.checkAtomAllowable(pa); err != nil {
    22  			s.traceInfo(err)
    23  			s.mtr.pop()
    24  			return err
    25  		}
    26  	}
    27  
    28  	if err := s.checkRequiredPackagesExist(a); err != nil {
    29  		s.traceInfo(err)
    30  		s.mtr.pop()
    31  		return err
    32  	}
    33  
    34  	_, deps, err := s.getImportsAndConstraintsOf(a)
    35  	if err != nil {
    36  		// An err here would be from the package fetcher; pass it straight back
    37  		// TODO(sdboyer) can we traceInfo this?
    38  		s.mtr.pop()
    39  		return err
    40  	}
    41  
    42  	// TODO(sdboyer) this deps list contains only packages not already selected
    43  	// from the target atom (assuming one is selected at all). It's fine for
    44  	// now, but won't be good enough when we get around to doing static
    45  	// analysis.
    46  	for _, dep := range deps {
    47  		if err := s.checkIdentMatches(a, dep); err != nil {
    48  			s.traceInfo(err)
    49  			s.mtr.pop()
    50  			return err
    51  		}
    52  		if err := s.checkDepsConstraintsAllowable(a, dep); err != nil {
    53  			s.traceInfo(err)
    54  			s.mtr.pop()
    55  			return err
    56  		}
    57  		if err := s.checkDepsDisallowsSelected(a, dep); err != nil {
    58  			s.traceInfo(err)
    59  			s.mtr.pop()
    60  			return err
    61  		}
    62  		if err := s.checkRevisionExists(a, dep); err != nil {
    63  			s.traceInfo(err)
    64  			return err
    65  		}
    66  		if err := s.checkPackageImportsFromDepExist(a, dep); err != nil {
    67  			s.traceInfo(err)
    68  			s.mtr.pop()
    69  			return err
    70  		}
    71  
    72  		// TODO(sdboyer) add check that fails if adding this atom would create a loop
    73  	}
    74  
    75  	s.mtr.pop()
    76  	return nil
    77  }
    78  
    79  // checkAtomAllowable ensures that an atom itself is acceptable with respect to
    80  // the constraints established by the current solution.
    81  func (s *solver) checkAtomAllowable(pa atom) error {
    82  	constraint := s.sel.getConstraint(pa.id)
    83  	if s.vUnify.matches(pa.id, constraint, pa.v) {
    84  		return nil
    85  	}
    86  	// TODO(sdboyer) collect constraint failure reason (wait...aren't we, below?)
    87  
    88  	deps := s.sel.getDependenciesOn(pa.id)
    89  	var failparent []dependency
    90  	for _, dep := range deps {
    91  		if !s.vUnify.matches(pa.id, dep.dep.Constraint, pa.v) {
    92  			s.fail(dep.depender.id)
    93  			failparent = append(failparent, dep)
    94  		}
    95  	}
    96  
    97  	err := &versionNotAllowedFailure{
    98  		goal:       pa,
    99  		failparent: failparent,
   100  		c:          constraint,
   101  	}
   102  
   103  	return err
   104  }
   105  
   106  // checkRequiredPackagesExist ensures that all required packages enumerated by
   107  // existing dependencies on this atom are actually present in the atom.
   108  func (s *solver) checkRequiredPackagesExist(a atomWithPackages) error {
   109  	ptree, err := s.b.ListPackages(a.a.id, a.a.v)
   110  	if err != nil {
   111  		// TODO(sdboyer) handle this more gracefully
   112  		return err
   113  	}
   114  
   115  	deps := s.sel.getDependenciesOn(a.a.id)
   116  	fp := make(map[string]errDeppers)
   117  	// We inspect these in a bit of a roundabout way, in order to incrementally
   118  	// build up the failure we'd return if there is, indeed, a missing package.
   119  	// TODO(sdboyer) rechecking all of these every time is wasteful. Is there a shortcut?
   120  	for _, dep := range deps {
   121  		for _, pkg := range dep.dep.pl {
   122  			if errdep, seen := fp[pkg]; seen {
   123  				errdep.deppers = append(errdep.deppers, dep.depender)
   124  				fp[pkg] = errdep
   125  			} else {
   126  				perr, has := ptree.Packages[pkg]
   127  				if !has || perr.Err != nil {
   128  					fp[pkg] = errDeppers{
   129  						err:     perr.Err,
   130  						deppers: []atom{dep.depender},
   131  					}
   132  				}
   133  			}
   134  		}
   135  	}
   136  
   137  	if len(fp) > 0 {
   138  		return &checkeeHasProblemPackagesFailure{
   139  			goal:    a.a,
   140  			failpkg: fp,
   141  		}
   142  	}
   143  	return nil
   144  }
   145  
   146  // checkDepsConstraintsAllowable checks that the constraints of an atom on a
   147  // given dep are valid with respect to existing constraints.
   148  func (s *solver) checkDepsConstraintsAllowable(a atomWithPackages, cdep completeDep) error {
   149  	dep := cdep.workingConstraint
   150  	constraint := s.sel.getConstraint(dep.Ident)
   151  	// Ensure the constraint expressed by the dep has at least some possible
   152  	// intersection with the intersection of existing constraints.
   153  	if s.vUnify.matchesAny(dep.Ident, constraint, dep.Constraint) {
   154  		return nil
   155  	}
   156  
   157  	siblings := s.sel.getDependenciesOn(dep.Ident)
   158  	// No admissible versions - visit all siblings and identify the disagreement(s)
   159  	var failsib []dependency
   160  	var nofailsib []dependency
   161  	for _, sibling := range siblings {
   162  		if !s.vUnify.matchesAny(dep.Ident, sibling.dep.Constraint, dep.Constraint) {
   163  			s.fail(sibling.depender.id)
   164  			failsib = append(failsib, sibling)
   165  		} else {
   166  			nofailsib = append(nofailsib, sibling)
   167  		}
   168  	}
   169  
   170  	return &disjointConstraintFailure{
   171  		goal:      dependency{depender: a.a, dep: cdep},
   172  		failsib:   failsib,
   173  		nofailsib: nofailsib,
   174  		c:         constraint,
   175  	}
   176  }
   177  
   178  // checkDepsDisallowsSelected ensures that an atom's constraints on a particular
   179  // dep are not incompatible with the version of that dep that's already been
   180  // selected.
   181  func (s *solver) checkDepsDisallowsSelected(a atomWithPackages, cdep completeDep) error {
   182  	dep := cdep.workingConstraint
   183  	selected, exists := s.sel.selected(dep.Ident)
   184  	if exists && !s.vUnify.matches(dep.Ident, dep.Constraint, selected.a.v) {
   185  		s.fail(dep.Ident)
   186  
   187  		return &constraintNotAllowedFailure{
   188  			goal: dependency{depender: a.a, dep: cdep},
   189  			v:    selected.a.v,
   190  		}
   191  	}
   192  	return nil
   193  }
   194  
   195  // checkIdentMatches ensures that the LocalName of a dep introduced by an atom,
   196  // has the same Source as what's already been selected (assuming anything's been
   197  // selected).
   198  //
   199  // In other words, this ensures that the solver never simultaneously selects two
   200  // identifiers with the same local name, but that disagree about where their
   201  // network source is.
   202  func (s *solver) checkIdentMatches(a atomWithPackages, cdep completeDep) error {
   203  	dep := cdep.workingConstraint
   204  	if curid, has := s.sel.getIdentFor(dep.Ident.ProjectRoot); has && !curid.equiv(dep.Ident) {
   205  		deps := s.sel.getDependenciesOn(a.a.id)
   206  		// Fail all the other deps, as there's no way atom can ever be
   207  		// compatible with them
   208  		for _, d := range deps {
   209  			s.fail(d.depender.id)
   210  		}
   211  
   212  		return &sourceMismatchFailure{
   213  			shared:   dep.Ident.ProjectRoot,
   214  			sel:      deps,
   215  			current:  curid.normalizedSource(),
   216  			mismatch: dep.Ident.normalizedSource(),
   217  			prob:     a.a,
   218  		}
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  // checkPackageImportsFromDepExist ensures that, if the dep is already selected,
   225  // the newly-required set of packages being placed on it exist and are valid.
   226  func (s *solver) checkPackageImportsFromDepExist(a atomWithPackages, cdep completeDep) error {
   227  	sel, is := s.sel.selected(cdep.workingConstraint.Ident)
   228  	if !is {
   229  		// dep is not already selected; nothing to do
   230  		return nil
   231  	}
   232  
   233  	ptree, err := s.b.ListPackages(sel.a.id, sel.a.v)
   234  	if err != nil {
   235  		// TODO(sdboyer) handle this more gracefully
   236  		return err
   237  	}
   238  
   239  	e := &depHasProblemPackagesFailure{
   240  		goal: dependency{
   241  			depender: a.a,
   242  			dep:      cdep,
   243  		},
   244  		v:    sel.a.v,
   245  		prob: make(map[string]error),
   246  	}
   247  
   248  	for _, pkg := range cdep.pl {
   249  		perr, has := ptree.Packages[pkg]
   250  		if !has || perr.Err != nil {
   251  			if has {
   252  				e.prob[pkg] = perr.Err
   253  			} else {
   254  				e.prob[pkg] = nil
   255  			}
   256  		}
   257  	}
   258  
   259  	if len(e.prob) > 0 {
   260  		return e
   261  	}
   262  	return nil
   263  }
   264  
   265  // checkRevisionExists ensures that if a dependency is constrained by a
   266  // revision, that that revision actually exists.
   267  func (s *solver) checkRevisionExists(a atomWithPackages, cdep completeDep) error {
   268  	r, isrev := cdep.Constraint.(Revision)
   269  	if !isrev {
   270  		// Constraint is not a revision; nothing to do
   271  		return nil
   272  	}
   273  
   274  	present, _ := s.b.RevisionPresentIn(cdep.Ident, r)
   275  	if present {
   276  		return nil
   277  	}
   278  
   279  	return &nonexistentRevisionFailure{
   280  		goal: dependency{
   281  			depender: a.a,
   282  			dep:      cdep,
   283  		},
   284  		r: r,
   285  	}
   286  }