Source file src/pkg/cmd/go/internal/modload/query.go
1
2
3
4
5 package modload
6
7 import (
8 "errors"
9 "fmt"
10 "os"
11 pathpkg "path"
12 "strings"
13 "sync"
14
15 "cmd/go/internal/imports"
16 "cmd/go/internal/modfetch"
17 "cmd/go/internal/module"
18 "cmd/go/internal/search"
19 "cmd/go/internal/semver"
20 "cmd/go/internal/str"
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 func Query(path, query, current string, allowed func(module.Version) bool) (*modfetch.RevInfo, error) {
56 var info *modfetch.RevInfo
57 err := modfetch.TryProxies(func(proxy string) (err error) {
58 info, err = queryProxy(proxy, path, query, current, allowed)
59 return err
60 })
61 return info, err
62 }
63
64 func queryProxy(proxy, path, query, current string, allowed func(module.Version) bool) (*modfetch.RevInfo, error) {
65 if current != "" && !semver.IsValid(current) {
66 return nil, fmt.Errorf("invalid previous version %q", current)
67 }
68 if allowed == nil {
69 allowed = func(module.Version) bool { return true }
70 }
71
72
73
74 badVersion := func(v string) (*modfetch.RevInfo, error) {
75 return nil, fmt.Errorf("invalid semantic version %q in range %q", v, query)
76 }
77 var ok func(module.Version) bool
78 var prefix string
79 var preferOlder bool
80 var mayUseLatest bool
81 switch {
82 case query == "latest":
83 ok = allowed
84 mayUseLatest = true
85
86 case query == "upgrade":
87 ok = allowed
88 mayUseLatest = true
89
90 case query == "patch":
91 if current == "" {
92 ok = allowed
93 mayUseLatest = true
94 } else {
95 prefix = semver.MajorMinor(current)
96 ok = func(m module.Version) bool {
97 return matchSemverPrefix(prefix, m.Version) && allowed(m)
98 }
99 }
100
101 case strings.HasPrefix(query, "<="):
102 v := query[len("<="):]
103 if !semver.IsValid(v) {
104 return badVersion(v)
105 }
106 if isSemverPrefix(v) {
107
108 return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)
109 }
110 ok = func(m module.Version) bool {
111 return semver.Compare(m.Version, v) <= 0 && allowed(m)
112 }
113
114 case strings.HasPrefix(query, "<"):
115 v := query[len("<"):]
116 if !semver.IsValid(v) {
117 return badVersion(v)
118 }
119 ok = func(m module.Version) bool {
120 return semver.Compare(m.Version, v) < 0 && allowed(m)
121 }
122
123 case strings.HasPrefix(query, ">="):
124 v := query[len(">="):]
125 if !semver.IsValid(v) {
126 return badVersion(v)
127 }
128 ok = func(m module.Version) bool {
129 return semver.Compare(m.Version, v) >= 0 && allowed(m)
130 }
131 preferOlder = true
132
133 case strings.HasPrefix(query, ">"):
134 v := query[len(">"):]
135 if !semver.IsValid(v) {
136 return badVersion(v)
137 }
138 if isSemverPrefix(v) {
139
140 return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)
141 }
142 ok = func(m module.Version) bool {
143 return semver.Compare(m.Version, v) > 0 && allowed(m)
144 }
145 preferOlder = true
146
147 case semver.IsValid(query) && isSemverPrefix(query):
148 ok = func(m module.Version) bool {
149 return matchSemverPrefix(query, m.Version) && allowed(m)
150 }
151 prefix = query + "."
152
153 default:
154
155
156
157
158
159 info, err := modfetch.Stat(proxy, path, query)
160 if err != nil {
161 queryErr := err
162
163
164
165 if vers := module.CanonicalVersion(query); vers != "" && vers != query {
166 info, err = modfetch.Stat(proxy, path, vers)
167 if !errors.Is(err, os.ErrNotExist) {
168 return info, err
169 }
170 }
171 if err != nil {
172 return nil, queryErr
173 }
174 }
175 if !allowed(module.Version{Path: path, Version: info.Version}) {
176 return nil, fmt.Errorf("%s@%s excluded", path, info.Version)
177 }
178 return info, nil
179 }
180
181 if path == Target.Path {
182 if query != "latest" {
183 return nil, fmt.Errorf("can't query specific version (%q) for the main module (%s)", query, path)
184 }
185 if !allowed(Target) {
186 return nil, fmt.Errorf("internal error: main module version is not allowed")
187 }
188 return &modfetch.RevInfo{Version: Target.Version}, nil
189 }
190
191 if str.HasPathPrefix(path, "std") || str.HasPathPrefix(path, "cmd") {
192 return nil, fmt.Errorf("explicit requirement on standard-library module %s not allowed", path)
193 }
194
195
196 repo, err := modfetch.Lookup(proxy, path)
197 if err != nil {
198 return nil, err
199 }
200 versions, err := repo.Versions(prefix)
201 if err != nil {
202 return nil, err
203 }
204
205 lookup := func(v string) (*modfetch.RevInfo, error) {
206 rev, err := repo.Stat(v)
207 if err != nil {
208 return nil, err
209 }
210
211
212
213 if current != "" && (query == "upgrade" || query == "patch") {
214 currentTime, err := modfetch.PseudoVersionTime(current)
215 if semver.Compare(rev.Version, current) < 0 || (err == nil && rev.Time.Before(currentTime)) {
216 return repo.Stat(current)
217 }
218 }
219
220 return rev, nil
221 }
222
223 if preferOlder {
224 for _, v := range versions {
225 if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) {
226 return lookup(v)
227 }
228 }
229 for _, v := range versions {
230 if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) {
231 return lookup(v)
232 }
233 }
234 } else {
235 for i := len(versions) - 1; i >= 0; i-- {
236 v := versions[i]
237 if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) {
238 return lookup(v)
239 }
240 }
241 for i := len(versions) - 1; i >= 0; i-- {
242 v := versions[i]
243 if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) {
244 return lookup(v)
245 }
246 }
247 }
248
249 if mayUseLatest {
250
251
252 latest, err := repo.Latest()
253 if err == nil {
254 if allowed(module.Version{Path: path, Version: latest.Version}) {
255 return lookup(latest.Version)
256 }
257 } else if !errors.Is(err, os.ErrNotExist) {
258 return nil, err
259 }
260 }
261
262 return nil, &NoMatchingVersionError{query: query, current: current}
263 }
264
265
266
267 func isSemverPrefix(v string) bool {
268 dots := 0
269 for i := 0; i < len(v); i++ {
270 switch v[i] {
271 case '-', '+':
272 return false
273 case '.':
274 dots++
275 if dots >= 2 {
276 return false
277 }
278 }
279 }
280 return true
281 }
282
283
284
285 func matchSemverPrefix(p, v string) bool {
286 return len(v) > len(p) && v[len(p)] == '.' && v[:len(p)] == p && semver.Prerelease(v) == ""
287 }
288
289 type QueryResult struct {
290 Mod module.Version
291 Rev *modfetch.RevInfo
292 Packages []string
293 }
294
295
296
297
298
299
300
301 func QueryPackage(path, query string, allowed func(module.Version) bool) ([]QueryResult, error) {
302 if search.IsMetaPackage(path) || strings.Contains(path, "...") {
303 return nil, fmt.Errorf("pattern %s is not an importable package", path)
304 }
305 return QueryPattern(path, query, allowed)
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321 func QueryPattern(pattern, query string, allowed func(module.Version) bool) ([]QueryResult, error) {
322 base := pattern
323 var match func(m module.Version, root string, isLocal bool) (pkgs []string)
324
325 if i := strings.Index(pattern, "..."); i >= 0 {
326 base = pathpkg.Dir(pattern[:i+3])
327 match = func(m module.Version, root string, isLocal bool) []string {
328 return matchPackages(pattern, imports.AnyTags(), false, []module.Version{m})
329 }
330 } else {
331 match = func(m module.Version, root string, isLocal bool) []string {
332 prefix := m.Path
333 if m == Target {
334 prefix = targetPrefix
335 }
336 if _, ok := dirInModule(pattern, prefix, root, isLocal); ok {
337 return []string{pattern}
338 } else {
339 return nil
340 }
341 }
342 }
343
344 if HasModRoot() {
345 pkgs := match(Target, modRoot, true)
346 if len(pkgs) > 0 {
347 if query != "latest" {
348 return nil, fmt.Errorf("can't query specific version for package %s in the main module (%s)", pattern, Target.Path)
349 }
350 if !allowed(Target) {
351 return nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed", pattern, Target.Path)
352 }
353 return []QueryResult{{
354 Mod: Target,
355 Rev: &modfetch.RevInfo{Version: Target.Version},
356 Packages: pkgs,
357 }}, nil
358 }
359 }
360
361 var (
362 results []QueryResult
363 candidateModules = modulePrefixesExcludingTarget(base)
364 )
365 if len(candidateModules) == 0 {
366 return nil, fmt.Errorf("package %s is not in the main module (%s)", pattern, Target.Path)
367 }
368
369 err := modfetch.TryProxies(func(proxy string) error {
370 queryModule := func(path string) (r QueryResult, err error) {
371 r.Mod.Path = path
372 r.Rev, err = queryProxy(proxy, path, query, "", allowed)
373 if err != nil {
374 return r, err
375 }
376 r.Mod.Version = r.Rev.Version
377 root, isLocal, err := fetch(r.Mod)
378 if err != nil {
379 return r, err
380 }
381 r.Packages = match(r.Mod, root, isLocal)
382 if len(r.Packages) == 0 {
383 return r, &PackageNotInModuleError{
384 Mod: r.Mod,
385 Replacement: Replacement(r.Mod),
386 Query: query,
387 Pattern: pattern,
388 }
389 }
390 return r, nil
391 }
392
393 var err error
394 results, err = queryPrefixModules(candidateModules, queryModule)
395 return err
396 })
397
398 return results, err
399 }
400
401
402
403
404 func modulePrefixesExcludingTarget(path string) []string {
405 prefixes := make([]string, 0, strings.Count(path, "/")+1)
406
407 for {
408 if path != targetPrefix {
409 if _, _, ok := module.SplitPathVersion(path); ok {
410 prefixes = append(prefixes, path)
411 }
412 }
413
414 j := strings.LastIndexByte(path, '/')
415 if j < 0 {
416 break
417 }
418 path = path[:j]
419 }
420
421 return prefixes
422 }
423
424 type prefixResult struct {
425 QueryResult
426 err error
427 }
428
429 func queryPrefixModules(candidateModules []string, queryModule func(path string) (QueryResult, error)) (found []QueryResult, err error) {
430
431
432
433
434 type result struct {
435 QueryResult
436 err error
437 }
438 results := make([]result, len(candidateModules))
439 var wg sync.WaitGroup
440 wg.Add(len(candidateModules))
441 for i, p := range candidateModules {
442 go func(p string, r *result) {
443 r.QueryResult, r.err = queryModule(p)
444 wg.Done()
445 }(p, &results[i])
446 }
447 wg.Wait()
448
449
450
451
452 var (
453 noPackage *PackageNotInModuleError
454 noVersion *NoMatchingVersionError
455 notExistErr error
456 )
457 for _, r := range results {
458 switch rErr := r.err.(type) {
459 case nil:
460 found = append(found, r.QueryResult)
461 case *PackageNotInModuleError:
462 if noPackage == nil {
463 noPackage = rErr
464 }
465 case *NoMatchingVersionError:
466 if noVersion == nil {
467 noVersion = rErr
468 }
469 default:
470 if errors.Is(rErr, os.ErrNotExist) {
471 if notExistErr == nil {
472 notExistErr = rErr
473 }
474 } else if err == nil {
475 if len(found) > 0 || noPackage != nil {
476
477
478
479
480
481
482
483 } else {
484 err = r.err
485 }
486 }
487 }
488 }
489
490
491
492
493
494 if len(found) == 0 && err == nil {
495 switch {
496 case noPackage != nil:
497 err = noPackage
498 case noVersion != nil:
499 err = noVersion
500 case notExistErr != nil:
501 err = notExistErr
502 default:
503 panic("queryPrefixModules: no modules found, but no error detected")
504 }
505 }
506
507 return found, err
508 }
509
510
511
512
513
514
515
516
517
518 type NoMatchingVersionError struct {
519 query, current string
520 }
521
522 func (e *NoMatchingVersionError) Error() string {
523 currentSuffix := ""
524 if (e.query == "upgrade" || e.query == "patch") && e.current != "" {
525 currentSuffix = fmt.Sprintf(" (current version is %s)", e.current)
526 }
527 return fmt.Sprintf("no matching versions for query %q", e.query) + currentSuffix
528 }
529
530
531
532
533
534
535
536
537
538
539 type PackageNotInModuleError struct {
540 Mod module.Version
541 Replacement module.Version
542 Query string
543 Pattern string
544 }
545
546 func (e *PackageNotInModuleError) Error() string {
547 found := ""
548 if r := e.Replacement; r.Path != "" {
549 replacement := r.Path
550 if r.Version != "" {
551 replacement = fmt.Sprintf("%s@%s", r.Path, r.Version)
552 }
553 if e.Query == e.Mod.Version {
554 found = fmt.Sprintf(" (replaced by %s)", replacement)
555 } else {
556 found = fmt.Sprintf(" (%s, replaced by %s)", e.Mod.Version, replacement)
557 }
558 } else if e.Query != e.Mod.Version {
559 found = fmt.Sprintf(" (%s)", e.Mod.Version)
560 }
561
562 if strings.Contains(e.Pattern, "...") {
563 return fmt.Sprintf("module %s@%s found%s, but does not contain packages matching %s", e.Mod.Path, e.Query, found, e.Pattern)
564 }
565 return fmt.Sprintf("module %s@%s found%s, but does not contain package %s", e.Mod.Path, e.Query, found, e.Pattern)
566 }
567
568
569 func ModuleHasRootPackage(m module.Version) (bool, error) {
570 root, isLocal, err := fetch(m)
571 if err != nil {
572 return false, err
573 }
574 _, ok := dirInModule(m.Path, m.Path, root, isLocal)
575 return ok, nil
576 }
577
View as plain text