github.com/golang/dep@v0.5.4/gps/lock.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 import ( 8 "fmt" 9 "sort" 10 ) 11 12 // Lock represents data from a lock file (or however the implementing tool 13 // chooses to store it) at a particular version that is relevant to the 14 // satisfiability solving process. 15 // 16 // In general, the information produced by gps on finding a successful 17 // solution is all that would be necessary to constitute a lock file, though 18 // tools can include whatever other information they want in their storage. 19 type Lock interface { 20 // Projects returns the list of LockedProjects contained in the lock data. 21 Projects() []LockedProject 22 23 // The set of imports (and required statements) that were the inputs that 24 // generated this Lock. It is acceptable to return a nil slice from this 25 // method if the information cannot reasonably be made available. 26 InputImports() []string 27 } 28 29 // sortLockedProjects returns a sorted copy of lps, or itself if already sorted. 30 func sortLockedProjects(lps []LockedProject) []LockedProject { 31 if len(lps) <= 1 || sort.SliceIsSorted(lps, func(i, j int) bool { 32 return lps[i].Ident().Less(lps[j].Ident()) 33 }) { 34 return lps 35 } 36 cp := make([]LockedProject, len(lps)) 37 copy(cp, lps) 38 sort.Slice(cp, func(i, j int) bool { 39 return cp[i].Ident().Less(cp[j].Ident()) 40 }) 41 return cp 42 } 43 44 // LockedProject is a single project entry from a lock file. It expresses the 45 // project's name, one or both of version and underlying revision, the network 46 // URI for accessing it, the path at which it should be placed within a vendor 47 // directory, and the packages that are used in it. 48 type LockedProject interface { 49 Ident() ProjectIdentifier 50 Version() Version 51 Packages() []string 52 Eq(LockedProject) bool 53 String() string 54 } 55 56 // lockedProject is the default implementation of LockedProject. 57 type lockedProject struct { 58 pi ProjectIdentifier 59 v UnpairedVersion 60 r Revision 61 pkgs []string 62 } 63 64 // SimpleLock is a helper for tools to easily describe lock data when they know 65 // that input imports are unavailable. 66 type SimpleLock []LockedProject 67 68 var _ Lock = SimpleLock{} 69 70 // Projects returns the entire contents of the SimpleLock. 71 func (l SimpleLock) Projects() []LockedProject { 72 return l 73 } 74 75 // InputImports returns a nil string slice, as SimpleLock does not provide a way 76 // of capturing string slices. 77 func (l SimpleLock) InputImports() []string { 78 return nil 79 } 80 81 // NewLockedProject creates a new LockedProject struct with a given 82 // ProjectIdentifier (name and optional upstream source URL), version. and list 83 // of packages required from the project. 84 // 85 // Note that passing a nil version will cause a panic. This is a correctness 86 // measure to ensure that the solver is never exposed to a version-less lock 87 // entry. Such a case would be meaningless - the solver would have no choice but 88 // to simply dismiss that project. By creating a hard failure case via panic 89 // instead, we are trying to avoid inflicting the resulting pain on the user by 90 // instead forcing a decision on the Analyzer implementation. 91 func NewLockedProject(id ProjectIdentifier, v Version, pkgs []string) LockedProject { 92 if v == nil { 93 panic("must provide a non-nil version to create a LockedProject") 94 } 95 96 lp := lockedProject{ 97 pi: id, 98 pkgs: pkgs, 99 } 100 101 switch tv := v.(type) { 102 case Revision: 103 lp.r = tv 104 case branchVersion: 105 lp.v = tv 106 case semVersion: 107 lp.v = tv 108 case plainVersion: 109 lp.v = tv 110 case versionPair: 111 lp.r = tv.r 112 lp.v = tv.v 113 } 114 115 return lp 116 } 117 118 // Ident returns the identifier describing the project. This includes both the 119 // local name (the root name by which the project is referenced in import paths) 120 // and the network name, where the upstream source lives. 121 func (lp lockedProject) Ident() ProjectIdentifier { 122 return lp.pi 123 } 124 125 // Version assembles together whatever version and/or revision data is 126 // available into a single Version. 127 func (lp lockedProject) Version() Version { 128 if lp.r == "" { 129 return lp.v 130 } 131 132 if lp.v == nil { 133 return lp.r 134 } 135 136 return lp.v.Pair(lp.r) 137 } 138 139 // Eq checks if two LockedProject instances are equal. The implementation 140 // assumes both Packages lists are already sorted lexicographically. 141 func (lp lockedProject) Eq(lp2 LockedProject) bool { 142 if lp.pi != lp2.Ident() { 143 return false 144 } 145 146 var uv UnpairedVersion 147 switch tv := lp2.Version().(type) { 148 case Revision: 149 if lp.r != tv { 150 return false 151 } 152 case versionPair: 153 if lp.r != tv.r { 154 return false 155 } 156 uv = tv.v 157 case branchVersion, semVersion, plainVersion: 158 // For now, we're going to say that revisions must be present in order 159 // to indicate equality. We may need to change this later, as it may be 160 // more appropriate to enforce elsewhere. 161 return false 162 } 163 164 v1n := lp.v == nil 165 v2n := uv == nil 166 167 if v1n != v2n { 168 return false 169 } 170 171 if !v1n && !lp.v.Matches(uv) { 172 return false 173 } 174 175 opkgs := lp2.Packages() 176 if len(lp.pkgs) != len(opkgs) { 177 return false 178 } 179 180 for k, v := range lp.pkgs { 181 if opkgs[k] != v { 182 return false 183 } 184 } 185 186 return true 187 } 188 189 // Packages returns the list of packages from within the LockedProject that are 190 // actually used in the import graph. Some caveats: 191 // 192 // * The names given are relative to the root import path for the project. If 193 // the root package itself is imported, it's represented as ".". 194 // * Just because a package path isn't included in this list doesn't mean it's 195 // safe to remove - it could contain C files, or other assets, that can't be 196 // safely removed. 197 // * The slice is not a copy. If you need to modify it, copy it first. 198 func (lp lockedProject) Packages() []string { 199 return lp.pkgs 200 } 201 202 func (lp lockedProject) String() string { 203 return fmt.Sprintf("%s@%s with packages: %v", 204 lp.Ident(), lp.Version(), lp.pkgs) 205 } 206 207 type safeLock struct { 208 p []LockedProject 209 i []string 210 } 211 212 func (sl safeLock) InputImports() []string { 213 return sl.i 214 } 215 216 func (sl safeLock) Projects() []LockedProject { 217 return sl.p 218 } 219 220 // prepLock ensures a lock is prepared and safe for use by the solver. This is 221 // mostly about defensively ensuring that no outside routine can modify the lock 222 // while the solver is in-flight. 223 // 224 // This is achieved by copying the lock's data into a new safeLock. 225 func prepLock(l Lock) safeLock { 226 pl := l.Projects() 227 228 rl := safeLock{ 229 p: make([]LockedProject, len(pl)), 230 } 231 copy(rl.p, pl) 232 233 rl.i = make([]string, len(l.InputImports())) 234 copy(rl.i, l.InputImports()) 235 236 return rl 237 }