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