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