github.com/sdboyer/gps@v0.16.3/identifier.go (about) 1 package gps 2 3 import ( 4 "fmt" 5 "math/rand" 6 "strconv" 7 ) 8 9 // ProjectRoot is the topmost import path in a tree of other import paths - the 10 // root of the tree. In gps' current design, ProjectRoots have to correspond to 11 // a repository root (mostly), but their real purpose is to identify the root 12 // import path of a "project", logically encompassing all child packages. 13 // 14 // Projects are a crucial unit of operation in gps. Constraints are declared by 15 // a project's manifest, and apply to all packages in a ProjectRoot's tree. 16 // Solving itself mostly proceeds on a project-by-project basis. 17 // 18 // Aliasing string types is usually a bit of an anti-pattern. gps does it here 19 // as a means of clarifying API intent. This is important because Go's package 20 // management domain has lots of different path-ish strings floating around: 21 // 22 // actual directories: 23 // /home/sdboyer/go/src/github.com/sdboyer/gps/example 24 // URLs: 25 // https://github.com/sdboyer/gps 26 // import paths: 27 // github.com/sdboyer/gps/example 28 // portions of import paths that refer to a package: 29 // example 30 // portions that could not possibly refer to anything sane: 31 // github.com/sdboyer 32 // portions that correspond to a repository root: 33 // github.com/sdboyer/gps 34 // 35 // While not a panacea, having ProjectRoot allows gps to clearly indicate via 36 // the type system when a path-ish string must have particular semantics. 37 type ProjectRoot string 38 39 // A ProjectIdentifier provides the name and source location of a dependency. It 40 // is related to, but differs in two keys ways from, an plain import path. 41 // 42 // First, ProjectIdentifiers do not identify a single package. Rather, they 43 // encompasses the whole tree of packages, including tree's root - the 44 // ProjectRoot. In gps' current design, this ProjectRoot almost always 45 // corresponds to the root of a repository. 46 // 47 // Second, ProjectIdentifiers can optionally carry a Source, which 48 // identifies where the underlying source code can be located on the network. 49 // These can be either a full URL, including protocol, or plain import paths. 50 // So, these are all valid data for Source: 51 // 52 // github.com/sdboyer/gps 53 // github.com/fork/gps 54 // git@github.com:sdboyer/gps 55 // https://github.com/sdboyer/gps 56 // 57 // With plain import paths, network addresses are derived purely through an 58 // algorithm. By having an explicit network name, it becomes possible to, for 59 // example, transparently substitute a fork for the original upstream source 60 // repository. 61 // 62 // Note that gps makes no guarantees about the actual import paths contained in 63 // a repository aligning with ImportRoot. If tools, or their users, specify an 64 // alternate Source that contains a repository with incompatible internal 65 // import paths, gps' solving operations will error. (gps does no import 66 // rewriting.) 67 // 68 // Also note that if different projects' manifests report a different 69 // Source for a given ImportRoot, it is a solve failure. Everyone has to 70 // agree on where a given import path should be sourced from. 71 // 72 // If Source is not explicitly set, gps will derive the network address from 73 // the ImportRoot using a similar algorithm to that utilized by `go get`. 74 type ProjectIdentifier struct { 75 ProjectRoot ProjectRoot 76 Source string 77 } 78 79 func (i ProjectIdentifier) less(j ProjectIdentifier) bool { 80 if i.ProjectRoot < j.ProjectRoot { 81 return true 82 } 83 if j.ProjectRoot < i.ProjectRoot { 84 return false 85 } 86 87 return i.normalizedSource() < j.normalizedSource() 88 } 89 90 func (i ProjectIdentifier) eq(j ProjectIdentifier) bool { 91 if i.ProjectRoot != j.ProjectRoot { 92 return false 93 } 94 if i.Source == j.Source { 95 return true 96 } 97 98 if (i.Source == "" && j.Source == string(j.ProjectRoot)) || 99 (j.Source == "" && i.Source == string(i.ProjectRoot)) { 100 return true 101 } 102 103 return false 104 } 105 106 // equiv will check if the two identifiers are "equivalent," under special 107 // rules. 108 // 109 // Given that the ProjectRoots are equal (==), equivalency occurs if: 110 // 111 // 1. The Sources are equal (==), OR 112 // 2. The LEFT (the receiver) Source is non-empty, and the right 113 // Source is empty. 114 // 115 // *This is asymmetry in this binary relation is intentional.* It facilitates 116 // the case where we allow for a ProjectIdentifier with an explicit Source 117 // to match one without. 118 func (i ProjectIdentifier) equiv(j ProjectIdentifier) bool { 119 if i.ProjectRoot != j.ProjectRoot { 120 return false 121 } 122 if i.Source == j.Source { 123 return true 124 } 125 126 if i.Source != "" && j.Source == "" { 127 return true 128 } 129 130 return false 131 } 132 133 func (i ProjectIdentifier) normalizedSource() string { 134 if i.Source == "" { 135 return string(i.ProjectRoot) 136 } 137 return i.Source 138 } 139 140 func (i ProjectIdentifier) errString() string { 141 if i.Source == "" || i.Source == string(i.ProjectRoot) { 142 return string(i.ProjectRoot) 143 } 144 return fmt.Sprintf("%s (from %s)", i.ProjectRoot, i.Source) 145 } 146 147 func (i ProjectIdentifier) normalize() ProjectIdentifier { 148 if i.Source == "" { 149 i.Source = string(i.ProjectRoot) 150 } 151 152 return i 153 } 154 155 // ProjectProperties comprise the properties that can be attached to a 156 // ProjectRoot. 157 // 158 // In general, these are declared in the context of a map of ProjectRoot to its 159 // ProjectProperties; they make little sense without their corresponding 160 // ProjectRoot. 161 type ProjectProperties struct { 162 Source string 163 Constraint Constraint 164 } 165 166 // bimodalIdentifiers are used to track work to be done in the unselected queue. 167 type bimodalIdentifier struct { 168 id ProjectIdentifier 169 // List of packages required within/under the ProjectIdentifier 170 pl []string 171 // prefv is used to indicate a 'preferred' version. This is expected to be 172 // derived from a dep's lock data, or else is empty. 173 prefv Version 174 // Indicates that the bmi came from the root project originally 175 fromRoot bool 176 } 177 178 type atom struct { 179 id ProjectIdentifier 180 v Version 181 } 182 183 // With a random revision and no name, collisions are...unlikely 184 var nilpa = atom{ 185 v: Revision(strconv.FormatInt(rand.Int63(), 36)), 186 } 187 188 type atomWithPackages struct { 189 a atom 190 pl []string 191 } 192 193 // bmi converts an atomWithPackages into a bimodalIdentifier. 194 // 195 // This is mostly intended for (read-only) trace use, so the package list slice 196 // is not copied. It is the callers responsibility to not modify the pl slice, 197 // lest that backpropagate and cause inconsistencies. 198 func (awp atomWithPackages) bmi() bimodalIdentifier { 199 return bimodalIdentifier{ 200 id: awp.a.id, 201 pl: awp.pl, 202 } 203 } 204 205 // completeDep (name hopefully to change) provides the whole picture of a 206 // dependency - the root (repo and project, since currently we assume the two 207 // are the same) name, a constraint, and the actual packages needed that are 208 // under that root. 209 type completeDep struct { 210 // The base workingConstraint 211 workingConstraint 212 // The specific packages required from the ProjectDep 213 pl []string 214 } 215 216 type dependency struct { 217 depender atom 218 dep completeDep 219 }