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 }