1
2
3
4
5 package modget
6
7 import (
8 "fmt"
9 "path/filepath"
10 "regexp"
11 "strings"
12 "sync"
13 "unicode/utf8"
14
15 "cmd/go/internal/base"
16 "cmd/go/internal/gover"
17 "cmd/go/internal/modload"
18 "cmd/go/internal/search"
19 "cmd/go/internal/str"
20 "cmd/internal/pkgpattern"
21
22 "golang.org/x/mod/module"
23 )
24
25
26
27 type query struct {
28
29 raw string
30
31
32 rawVersion string
33
34
35
36
37
38
39
40 pattern string
41
42
43
44
45
46
47 patternIsLocal bool
48
49
50
51
52 version string
53
54
55
56
57 matchWildcard func(path string) bool
58
59
60
61
62 canMatchWildcardInModule func(mPath string) bool
63
64
65
66
67 conflict *query
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 candidates []pathSet
87 candidatesMu sync.Mutex
88
89
90
91 pathSeen sync.Map
92
93
94
95
96
97
98
99 resolved []module.Version
100
101
102
103 matchesPackages bool
104 }
105
106
107
108 type pathSet struct {
109
110
111
112
113
114
115
116 path string
117
118
119
120
121
122 pkgMods []module.Version
123
124
125
126
127
128
129
130
131
132 mod module.Version
133
134 err error
135 }
136
137
138 func errSet(err error) pathSet { return pathSet{err: err} }
139
140
141
142 func newQuery(loaderstate *modload.State, raw string) (*query, error) {
143 pattern, rawVers, found, err := modload.ParsePathVersion(raw)
144 if err != nil {
145 return nil, err
146 }
147 if found && (strings.Contains(rawVers, "@") || rawVers == "") {
148 return nil, fmt.Errorf("invalid module version syntax %q", raw)
149 }
150
151
152
153 version := rawVers
154 if version == "" {
155 if getU.version == "" {
156 version = "upgrade"
157 } else {
158 version = getU.version
159 }
160 }
161
162 q := &query{
163 raw: raw,
164 rawVersion: rawVers,
165 pattern: pattern,
166 patternIsLocal: filepath.IsAbs(pattern) || search.IsRelativePath(pattern),
167 version: version,
168 }
169 if strings.Contains(q.pattern, "...") {
170 q.matchWildcard = pkgpattern.MatchPattern(q.pattern)
171 q.canMatchWildcardInModule = pkgpattern.TreeCanMatchPattern(q.pattern)
172 }
173 if err := q.validate(loaderstate); err != nil {
174 return q, err
175 }
176 return q, nil
177 }
178
179
180 func (q *query) validate(loaderstate *modload.State) error {
181 if q.patternIsLocal {
182 if q.rawVersion != "" {
183 return fmt.Errorf("can't request explicit version %q of path %q in main module", q.rawVersion, q.pattern)
184 }
185 return nil
186 }
187
188 if q.pattern == "all" {
189
190 if !loaderstate.HasModRoot() {
191 return fmt.Errorf(`cannot match "all": %v`, modload.NewNoMainModulesError(loaderstate))
192 }
193 if !versionOkForMainModule(q.version) {
194
195
196
197 return &modload.QueryUpgradesAllError{
198 MainModules: loaderstate.MainModules.Versions(),
199 Query: q.version,
200 }
201 }
202 }
203
204 if search.IsMetaPackage(q.pattern) && q.pattern != "all" {
205 if q.pattern != q.raw {
206 if q.pattern == "tool" {
207 return fmt.Errorf("can't request explicit version of \"tool\" pattern")
208 }
209 return fmt.Errorf("can't request explicit version of standard-library pattern %q", q.pattern)
210 }
211 }
212
213 return nil
214 }
215
216
217 func (q *query) String() string { return q.raw }
218
219
220 func (q *query) ResolvedString(m module.Version) string {
221 if m.Path != q.pattern {
222 if m.Version != q.version {
223 return fmt.Sprintf("%v (matching %s@%s)", m, q.pattern, q.version)
224 }
225 return fmt.Sprintf("%v (matching %v)", m, q)
226 }
227 if m.Version != q.version {
228 return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, m.Version)
229 }
230 return q.String()
231 }
232
233
234 func (q *query) isWildcard() bool {
235 return q.matchWildcard != nil || (q.patternIsLocal && strings.Contains(q.pattern, "..."))
236 }
237
238
239 func (q *query) matchesPath(path string) bool {
240 if q.matchWildcard != nil && !gover.IsToolchain(path) {
241 return q.matchWildcard(path)
242 }
243 return path == q.pattern
244 }
245
246
247
248 func (q *query) canMatchInModule(mPath string) bool {
249 if gover.IsToolchain(mPath) {
250 return false
251 }
252 if q.canMatchWildcardInModule != nil {
253 return q.canMatchWildcardInModule(mPath)
254 }
255 return str.HasPathPrefix(q.pattern, mPath)
256 }
257
258
259
260
261
262
263
264
265
266
267 func (q *query) pathOnce(path string, f func() pathSet) {
268 if _, dup := q.pathSeen.LoadOrStore(path, nil); dup {
269 return
270 }
271
272 cs := f()
273
274 if len(cs.pkgMods) > 0 || cs.mod != (module.Version{}) || cs.err != nil {
275 cs.path = path
276 q.candidatesMu.Lock()
277 q.candidates = append(q.candidates, cs)
278 q.candidatesMu.Unlock()
279 }
280 }
281
282
283 func reportError(q *query, err error) {
284 errStr := err.Error()
285
286
287
288
289
290
291
292 if !utf8.ValidString(q.pattern) || !utf8.ValidString(q.version) {
293 base.Errorf("go: %s", errStr)
294 return
295 }
296
297 patternRE := regexp.MustCompile("(?m)(?:[ \t(\"`]|^)" + regexp.QuoteMeta(q.pattern) + "(?:[ @:;)\"`]|$)")
298 if patternRE.MatchString(errStr) {
299 if q.rawVersion == "" {
300 base.Errorf("go: %s", errStr)
301 return
302 }
303
304 versionRE := regexp.MustCompile("(?m)(?:[ @(\"`]|^)" + regexp.QuoteMeta(q.version) + "(?:[ :;)\"`]|$)")
305 if versionRE.MatchString(errStr) {
306 base.Errorf("go: %s", errStr)
307 return
308 }
309 }
310
311 if qs := q.String(); qs != "" {
312 base.Errorf("go: %s: %s", qs, errStr)
313 } else {
314 base.Errorf("go: %s", errStr)
315 }
316 }
317
318 func reportConflict(pq *query, m module.Version, conflict versionReason) {
319 if pq.conflict != nil {
320
321
322 return
323 }
324 pq.conflict = conflict.reason
325
326 proposed := versionReason{
327 version: m.Version,
328 reason: pq,
329 }
330 if pq.isWildcard() && !conflict.reason.isWildcard() {
331
332 proposed, conflict = conflict, proposed
333 }
334 reportError(pq, &conflictError{
335 mPath: m.Path,
336 proposed: proposed,
337 conflict: conflict,
338 })
339 }
340
341 type conflictError struct {
342 mPath string
343 proposed versionReason
344 conflict versionReason
345 }
346
347 func (e *conflictError) Error() string {
348 argStr := func(q *query, v string) string {
349 if v != q.version {
350 return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, v)
351 }
352 return q.String()
353 }
354
355 pq := e.proposed.reason
356 rq := e.conflict.reason
357 modDetail := ""
358 if e.mPath != pq.pattern {
359 modDetail = fmt.Sprintf("for module %s, ", e.mPath)
360 }
361
362 return fmt.Sprintf("%s%s conflicts with %s",
363 modDetail,
364 argStr(pq, e.proposed.version),
365 argStr(rq, e.conflict.version))
366 }
367
368 func versionOkForMainModule(version string) bool {
369 return version == "upgrade" || version == "patch"
370 }
371
View as plain text