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