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 return fmt.Errorf("can't request explicit version of standard-library pattern %q", q.pattern)
203 }
204 }
205
206 return nil
207 }
208
209
210 func (q *query) String() string { return q.raw }
211
212
213 func (q *query) ResolvedString(m module.Version) string {
214 if m.Path != q.pattern {
215 if m.Version != q.version {
216 return fmt.Sprintf("%v (matching %s@%s)", m, q.pattern, q.version)
217 }
218 return fmt.Sprintf("%v (matching %v)", m, q)
219 }
220 if m.Version != q.version {
221 return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, m.Version)
222 }
223 return q.String()
224 }
225
226
227 func (q *query) isWildcard() bool {
228 return q.matchWildcard != nil || (q.patternIsLocal && strings.Contains(q.pattern, "..."))
229 }
230
231
232 func (q *query) matchesPath(path string) bool {
233 if q.matchWildcard != nil && !gover.IsToolchain(path) {
234 return q.matchWildcard(path)
235 }
236 return path == q.pattern
237 }
238
239
240
241 func (q *query) canMatchInModule(mPath string) bool {
242 if gover.IsToolchain(mPath) {
243 return false
244 }
245 if q.canMatchWildcardInModule != nil {
246 return q.canMatchWildcardInModule(mPath)
247 }
248 return str.HasPathPrefix(q.pattern, mPath)
249 }
250
251
252
253
254
255
256
257
258
259
260 func (q *query) pathOnce(path string, f func() pathSet) {
261 if _, dup := q.pathSeen.LoadOrStore(path, nil); dup {
262 return
263 }
264
265 cs := f()
266
267 if len(cs.pkgMods) > 0 || cs.mod != (module.Version{}) || cs.err != nil {
268 cs.path = path
269 q.candidatesMu.Lock()
270 q.candidates = append(q.candidates, cs)
271 q.candidatesMu.Unlock()
272 }
273 }
274
275
276 func reportError(q *query, err error) {
277 errStr := err.Error()
278
279
280
281
282
283
284
285 patternRE := regexp.MustCompile("(?m)(?:[ \t(\"`]|^)" + regexp.QuoteMeta(q.pattern) + "(?:[ @:;)\"`]|$)")
286 if patternRE.MatchString(errStr) {
287 if q.rawVersion == "" {
288 base.Errorf("go: %s", errStr)
289 return
290 }
291
292 versionRE := regexp.MustCompile("(?m)(?:[ @(\"`]|^)" + regexp.QuoteMeta(q.version) + "(?:[ :;)\"`]|$)")
293 if versionRE.MatchString(errStr) {
294 base.Errorf("go: %s", errStr)
295 return
296 }
297 }
298
299 if qs := q.String(); qs != "" {
300 base.Errorf("go: %s: %s", qs, errStr)
301 } else {
302 base.Errorf("go: %s", errStr)
303 }
304 }
305
306 func reportConflict(pq *query, m module.Version, conflict versionReason) {
307 if pq.conflict != nil {
308
309
310 return
311 }
312 pq.conflict = conflict.reason
313
314 proposed := versionReason{
315 version: m.Version,
316 reason: pq,
317 }
318 if pq.isWildcard() && !conflict.reason.isWildcard() {
319
320 proposed, conflict = conflict, proposed
321 }
322 reportError(pq, &conflictError{
323 mPath: m.Path,
324 proposed: proposed,
325 conflict: conflict,
326 })
327 }
328
329 type conflictError struct {
330 mPath string
331 proposed versionReason
332 conflict versionReason
333 }
334
335 func (e *conflictError) Error() string {
336 argStr := func(q *query, v string) string {
337 if v != q.version {
338 return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, v)
339 }
340 return q.String()
341 }
342
343 pq := e.proposed.reason
344 rq := e.conflict.reason
345 modDetail := ""
346 if e.mPath != pq.pattern {
347 modDetail = fmt.Sprintf("for module %s, ", e.mPath)
348 }
349
350 return fmt.Sprintf("%s%s conflicts with %s",
351 modDetail,
352 argStr(pq, e.proposed.version),
353 argStr(rq, e.conflict.version))
354 }
355
356 func versionOkForMainModule(version string) bool {
357 return version == "upgrade" || version == "patch"
358 }
359
View as plain text