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