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

     1  // Copyright 2018 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 verify
     6  
     7  import (
     8  	radix "github.com/armon/go-radix"
     9  	"github.com/golang/dep/gps"
    10  	"github.com/golang/dep/gps/paths"
    11  	"github.com/golang/dep/gps/pkgtree"
    12  )
    13  
    14  // LockSatisfaction holds the compound result of LockSatisfiesInputs, allowing
    15  // the caller to inspect each of several orthogonal possible types of failure.
    16  //
    17  // The zero value assumes that there was no input lock, which necessarily means
    18  // the inputs were not satisfied. This zero value means we err on the side of
    19  // failure.
    20  type LockSatisfaction struct {
    21  	// If LockExisted is false, it indicates that a nil gps.Lock was passed to
    22  	// LockSatisfiesInputs().
    23  	LockExisted bool
    24  	// MissingImports is the set of import paths that were present in the
    25  	// inputs but missing in the Lock.
    26  	MissingImports []string
    27  	// ExcessImports is the set of import paths that were present in the Lock
    28  	// but absent from the inputs.
    29  	ExcessImports []string
    30  	// UnmatchedConstraints reports any normal, non-override constraint rules that
    31  	// were not satisfied by the corresponding LockedProject in the Lock.
    32  	UnmetConstraints map[gps.ProjectRoot]ConstraintMismatch
    33  	// UnmatchedOverrides reports any override rules that were not satisfied by the
    34  	// corresponding LockedProject in the Lock.
    35  	UnmetOverrides map[gps.ProjectRoot]ConstraintMismatch
    36  }
    37  
    38  // ConstraintMismatch is a two-tuple of a gps.Version, and a gps.Constraint that
    39  // does not allow that version.
    40  type ConstraintMismatch struct {
    41  	C gps.Constraint
    42  	V gps.Version
    43  }
    44  
    45  // LockSatisfiesInputs determines whether the provided Lock satisfies all the
    46  // requirements indicated by the inputs (RootManifest and PackageTree).
    47  //
    48  // The second parameter is expected to be the list of imports that were used to
    49  // generate the input Lock. Without this explicit list, it is not possible to
    50  // compute package imports that may have been removed. Figuring out that
    51  // negative space would require exploring the entire graph to ensure there are
    52  // no in-edges for particular imports.
    53  func LockSatisfiesInputs(l gps.Lock, m gps.RootManifest, ptree pkgtree.PackageTree) LockSatisfaction {
    54  	if l == nil {
    55  		return LockSatisfaction{}
    56  	}
    57  
    58  	lsat := LockSatisfaction{
    59  		LockExisted:      true,
    60  		UnmetOverrides:   make(map[gps.ProjectRoot]ConstraintMismatch),
    61  		UnmetConstraints: make(map[gps.ProjectRoot]ConstraintMismatch),
    62  	}
    63  
    64  	var ig *pkgtree.IgnoredRuleset
    65  	var req map[string]bool
    66  	if m != nil {
    67  		ig = m.IgnoredPackages()
    68  		req = m.RequiredPackages()
    69  	}
    70  
    71  	rm, _ := ptree.ToReachMap(true, true, false, ig)
    72  	reach := rm.FlattenFn(paths.IsStandardImportPath)
    73  
    74  	inlock := make(map[string]bool, len(l.InputImports()))
    75  	ininputs := make(map[string]bool, len(reach)+len(req))
    76  
    77  	type lockUnsatisfy uint8
    78  	const (
    79  		missingFromLock lockUnsatisfy = iota
    80  		inAdditionToLock
    81  	)
    82  
    83  	pkgDiff := make(map[string]lockUnsatisfy)
    84  
    85  	for _, imp := range reach {
    86  		ininputs[imp] = true
    87  	}
    88  
    89  	for imp := range req {
    90  		ininputs[imp] = true
    91  	}
    92  
    93  	for _, imp := range l.InputImports() {
    94  		inlock[imp] = true
    95  	}
    96  
    97  	for ip := range ininputs {
    98  		if !inlock[ip] {
    99  			pkgDiff[ip] = missingFromLock
   100  		} else {
   101  			// So we don't have to revisit it below
   102  			delete(inlock, ip)
   103  		}
   104  	}
   105  
   106  	// Something in the missing list might already be in the packages list,
   107  	// because another package in the depgraph imports it. We could make a
   108  	// special case for that, but it would break the simplicity of the model and
   109  	// complicate the notion of LockSatisfaction.Passed(), so let's see if we
   110  	// can get away without it.
   111  
   112  	for ip := range inlock {
   113  		if !ininputs[ip] {
   114  			pkgDiff[ip] = inAdditionToLock
   115  		}
   116  	}
   117  
   118  	for ip, typ := range pkgDiff {
   119  		if typ == missingFromLock {
   120  			lsat.MissingImports = append(lsat.MissingImports, ip)
   121  		} else {
   122  			lsat.ExcessImports = append(lsat.ExcessImports, ip)
   123  		}
   124  	}
   125  
   126  	eff := findEffectualConstraints(m, ininputs)
   127  	ovr, constraints := m.Overrides(), m.DependencyConstraints()
   128  
   129  	for _, lp := range l.Projects() {
   130  		pr := lp.Ident().ProjectRoot
   131  
   132  		if pp, has := ovr[pr]; has {
   133  			if !pp.Constraint.Matches(lp.Version()) {
   134  				lsat.UnmetOverrides[pr] = ConstraintMismatch{
   135  					C: pp.Constraint,
   136  					V: lp.Version(),
   137  				}
   138  			}
   139  			// The constraint isn't considered if we have an override,
   140  			// independent of whether the override is satisfied.
   141  			continue
   142  		}
   143  
   144  		if pp, has := constraints[pr]; has && eff[string(pr)] && !pp.Constraint.Matches(lp.Version()) {
   145  			lsat.UnmetConstraints[pr] = ConstraintMismatch{
   146  				C: pp.Constraint,
   147  				V: lp.Version(),
   148  			}
   149  		}
   150  	}
   151  
   152  	return lsat
   153  }
   154  
   155  // Satisfied is a shortcut method that indicates whether there were any ways in
   156  // which the Lock did not satisfy the inputs. It will return true only if the
   157  // Lock was satisfactory in all respects vis-a-vis the inputs.
   158  func (ls LockSatisfaction) Satisfied() bool {
   159  	if !ls.LockExisted {
   160  		return false
   161  	}
   162  
   163  	if len(ls.MissingImports) > 0 {
   164  		return false
   165  	}
   166  
   167  	if len(ls.ExcessImports) > 0 {
   168  		return false
   169  	}
   170  
   171  	if len(ls.UnmetOverrides) > 0 {
   172  		return false
   173  	}
   174  
   175  	if len(ls.UnmetConstraints) > 0 {
   176  		return false
   177  	}
   178  
   179  	return true
   180  }
   181  
   182  func findEffectualConstraints(m gps.Manifest, imports map[string]bool) map[string]bool {
   183  	eff := make(map[string]bool)
   184  	xt := radix.New()
   185  
   186  	for pr := range m.DependencyConstraints() {
   187  		// FIXME(sdboyer) this has the trailing slash ambiguity problem; adapt
   188  		// code from the solver
   189  		xt.Insert(string(pr), nil)
   190  	}
   191  
   192  	for imp := range imports {
   193  		if root, _, has := xt.LongestPrefix(imp); has {
   194  			eff[root] = true
   195  		}
   196  	}
   197  
   198  	return eff
   199  }