github.com/golang/dep@v0.5.4/gps/satisfy.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gps
     6  
     7  // check performs constraint checks on the provided atom. The set of checks
     8  // differ slightly depending on whether the atom is pkgonly, or if it's the
     9  // entire project being added for the first time.
    10  //
    11  // The goal is to determine whether selecting the atom would result in a state
    12  // where all the solver requirements are still satisfied.
    13  func (s *solver) check(a atomWithPackages, pkgonly bool) error {
    14  	pa := a.a
    15  	if nilpa == pa {
    16  		// This shouldn't be able to happen, but if it does, it unequivocally
    17  		// indicates a logical bug somewhere, so blowing up is preferable
    18  		panic("canary - checking version of empty ProjectAtom")
    19  	}
    20  
    21  	s.mtr.push("satisfy")
    22  	var err error
    23  	defer func() {
    24  		if err != nil {
    25  			s.traceInfo(err)
    26  		}
    27  		s.mtr.pop()
    28  	}()
    29  
    30  	// If we're pkgonly, then base atom was already determined to be allowable,
    31  	// so we can skip the checkAtomAllowable step.
    32  	if !pkgonly {
    33  		if err = s.checkAtomAllowable(pa); err != nil {
    34  			return err
    35  		}
    36  	}
    37  
    38  	if err = s.checkRequiredPackagesExist(a); err != nil {
    39  		return err
    40  	}
    41  
    42  	var deps []completeDep
    43  	_, deps, err = s.getImportsAndConstraintsOf(a)
    44  	if err != nil {
    45  		// An err here would be from the package fetcher; pass it straight back
    46  		return err
    47  	}
    48  
    49  	// TODO(sdboyer) this deps list contains only packages not already selected
    50  	// from the target atom (assuming one is selected at all). It's fine for
    51  	// now, but won't be good enough when we get around to doing static
    52  	// analysis.
    53  	for _, dep := range deps {
    54  		if err = s.checkIdentMatches(a, dep); err != nil {
    55  			return err
    56  		}
    57  		if err = s.checkRootCaseConflicts(a, dep); err != nil {
    58  			return err
    59  		}
    60  		if err = s.checkDepsConstraintsAllowable(a, dep); err != nil {
    61  			return err
    62  		}
    63  		if err = s.checkDepsDisallowsSelected(a, dep); err != nil {
    64  			return err
    65  		}
    66  		if err = s.checkRevisionExists(a, dep); err != nil {
    67  			return err
    68  		}
    69  		if err = s.checkPackageImportsFromDepExist(a, dep); err != nil {
    70  			return err
    71  		}
    72  
    73  		// TODO(sdboyer) add check that fails if adding this atom would create a loop
    74  	}
    75  
    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 constraint.Matches(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 !dep.dep.Constraint.Matches(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 constraint.MatchesAny(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 !sibling.dep.Constraint.MatchesAny(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 && !dep.Constraint.Matches(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  // checkRootCaseConflicts ensures that the ProjectRoot specified in the completeDep
   225  // does not have case conflicts with any existing dependencies.
   226  //
   227  // We only need to check the ProjectRoot, rather than any packages therein, as
   228  // the later check for package existence is case-sensitive.
   229  func (s *solver) checkRootCaseConflicts(a atomWithPackages, cdep completeDep) error {
   230  	pr := cdep.workingConstraint.Ident.ProjectRoot
   231  	hasConflict, current := s.sel.findCaseConflicts(pr)
   232  	if !hasConflict {
   233  		return nil
   234  	}
   235  
   236  	curid, _ := s.sel.getIdentFor(current)
   237  	deps := s.sel.getDependenciesOn(curid)
   238  	for _, d := range deps {
   239  		s.fail(d.depender.id)
   240  	}
   241  
   242  	// If a project has multiple packages that import each other, we treat that
   243  	// as establishing a canonical case variant for the ProjectRoot. It's possible,
   244  	// however, that that canonical variant is not the same one that others
   245  	// imported it under. If that's the situation, then we'll have arrived here
   246  	// when visiting the project, not its dependers, having misclassified its
   247  	// internal imports as external. That means the atomWithPackages will
   248  	// be the wrong case variant induced by the importers, and the cdep will be
   249  	// a link pointing back at the canonical case variant.
   250  	//
   251  	// If this is the case, use a special failure, wrongCaseFailure, that
   252  	// makes a stronger statement as to the correctness of case variants.
   253  	//
   254  	// TODO(sdboyer) This approach to marking failure is less than great, as
   255  	// this will mark the current atom as failed, as well, causing the
   256  	// backtracker to work through it. While that could prove fruitful, it's
   257  	// quite likely just to be wasted effort. Addressing this - if that's a good
   258  	// idea - would entail creating another path back out of checking to enable
   259  	// backjumping directly to the incorrect importers.
   260  	if current == a.a.id.ProjectRoot {
   261  		return &wrongCaseFailure{
   262  			correct: pr,
   263  			goal:    dependency{depender: a.a, dep: cdep},
   264  			badcase: deps,
   265  		}
   266  	}
   267  
   268  	return &caseMismatchFailure{
   269  		goal:    dependency{depender: a.a, dep: cdep},
   270  		current: current,
   271  		failsib: deps,
   272  	}
   273  }
   274  
   275  // checkPackageImportsFromDepExist ensures that, if the dep is already selected,
   276  // the newly-required set of packages being placed on it exist and are valid.
   277  func (s *solver) checkPackageImportsFromDepExist(a atomWithPackages, cdep completeDep) error {
   278  	sel, is := s.sel.selected(cdep.workingConstraint.Ident)
   279  	if !is {
   280  		// dep is not already selected; nothing to do
   281  		return nil
   282  	}
   283  
   284  	ptree, err := s.b.ListPackages(sel.a.id, sel.a.v)
   285  	if err != nil {
   286  		// TODO(sdboyer) handle this more gracefully
   287  		return err
   288  	}
   289  
   290  	e := &depHasProblemPackagesFailure{
   291  		goal: dependency{
   292  			depender: a.a,
   293  			dep:      cdep,
   294  		},
   295  		v:    sel.a.v,
   296  		prob: make(map[string]error),
   297  	}
   298  
   299  	for _, pkg := range cdep.pl {
   300  		perr, has := ptree.Packages[pkg]
   301  		if !has || perr.Err != nil {
   302  			if has {
   303  				e.prob[pkg] = perr.Err
   304  			} else {
   305  				e.prob[pkg] = nil
   306  			}
   307  		}
   308  	}
   309  
   310  	if len(e.prob) > 0 {
   311  		return e
   312  	}
   313  	return nil
   314  }
   315  
   316  // checkRevisionExists ensures that if a dependency is constrained by a
   317  // revision, that that revision actually exists.
   318  func (s *solver) checkRevisionExists(a atomWithPackages, cdep completeDep) error {
   319  	r, isrev := cdep.Constraint.(Revision)
   320  	if !isrev {
   321  		// Constraint is not a revision; nothing to do
   322  		return nil
   323  	}
   324  
   325  	present, _ := s.b.RevisionPresentIn(cdep.Ident, r)
   326  	if present {
   327  		return nil
   328  	}
   329  
   330  	return &nonexistentRevisionFailure{
   331  		goal: dependency{
   332  			depender: a.a,
   333  			dep:      cdep,
   334  		},
   335  		r: r,
   336  	}
   337  }