github.com/octohelm/cuemod@v0.9.4/internal/cmd/go/internals/modload/query.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package modload 6 7 import ( 8 "bytes" 9 "context" 10 "errors" 11 "fmt" 12 "io/fs" 13 "os" 14 pathpkg "path" 15 "slices" 16 "sort" 17 "strings" 18 "sync" 19 "time" 20 21 "github.com/octohelm/cuemod/internal/cmd/go/internals/cfg" 22 "github.com/octohelm/cuemod/internal/cmd/go/internals/gover" 23 "github.com/octohelm/cuemod/internal/cmd/go/internals/imports" 24 "github.com/octohelm/cuemod/internal/cmd/go/internals/modfetch" 25 "github.com/octohelm/cuemod/internal/cmd/go/internals/modfetch/codehost" 26 "github.com/octohelm/cuemod/internal/cmd/go/internals/modinfo" 27 "github.com/octohelm/cuemod/internal/cmd/go/internals/search" 28 "github.com/octohelm/cuemod/internal/cmd/go/internals/str" 29 "github.com/octohelm/cuemod/internal/cmd/go/internals/trace" 30 "github.com/octohelm/cuemod/internal/cmd/internals/pkgpattern" 31 32 "golang.org/x/mod/module" 33 "golang.org/x/mod/semver" 34 ) 35 36 // Query looks up a revision of a given module given a version query string. 37 // The module must be a complete module path. 38 // The version must take one of the following forms: 39 // 40 // - the literal string "latest", denoting the latest available, allowed 41 // tagged version, with non-prereleases preferred over prereleases. 42 // If there are no tagged versions in the repo, latest returns the most 43 // recent commit. 44 // 45 // - the literal string "upgrade", equivalent to "latest" except that if 46 // current is a newer version, current will be returned (see below). 47 // 48 // - the literal string "patch", denoting the latest available tagged version 49 // with the same major and minor number as current (see below). 50 // 51 // - v1, denoting the latest available tagged version v1.x.x. 52 // 53 // - v1.2, denoting the latest available tagged version v1.2.x. 54 // 55 // - v1.2.3, a semantic version string denoting that tagged version. 56 // 57 // - <v1.2.3, <=v1.2.3, >v1.2.3, >=v1.2.3, 58 // denoting the version closest to the target and satisfying the given operator, 59 // with non-prereleases preferred over prereleases. 60 // 61 // - a repository commit identifier or tag, denoting that commit. 62 // 63 // current denotes the currently-selected version of the module; it may be 64 // "none" if no version is currently selected, or "" if the currently-selected 65 // version is unknown or should not be considered. If query is 66 // "upgrade" or "patch", current will be returned if it is a newer 67 // semantic version or a chronologically later pseudo-version than the 68 // version that would otherwise be chosen. This prevents accidental downgrades 69 // from newer pre-release or development versions. 70 // 71 // The allowed function (which may be nil) is used to filter out unsuitable 72 // versions (see AllowedFunc documentation for details). If the query refers to 73 // a specific revision (for example, "master"; see IsRevisionQuery), and the 74 // revision is disallowed by allowed, Query returns the error. If the query 75 // does not refer to a specific revision (for example, "latest"), Query 76 // acts as if versions disallowed by allowed do not exist. 77 // 78 // If path is the path of the main module and the query is "latest", 79 // Query returns Target.Version as the version. 80 // 81 // Query often returns a non-nil *RevInfo with a non-nil error, 82 // to provide an info.Origin that can allow the error to be cached. 83 func Query(ctx context.Context, path, query, current string, allowed AllowedFunc) (*modfetch.RevInfo, error) { 84 ctx, span := trace.StartSpan(ctx, "modload.Query "+path) 85 defer span.Done() 86 87 return queryReuse(ctx, path, query, current, allowed, nil) 88 } 89 90 // queryReuse is like Query but also takes a map of module info that can be reused 91 // if the validation criteria in Origin are met. 92 func queryReuse(ctx context.Context, path, query, current string, allowed AllowedFunc, reuse map[module.Version]*modinfo.ModulePublic) (*modfetch.RevInfo, error) { 93 var info *modfetch.RevInfo 94 err := modfetch.TryProxies(func(proxy string) (err error) { 95 info, err = queryProxy(ctx, proxy, path, query, current, allowed, reuse) 96 return err 97 }) 98 return info, err 99 } 100 101 // checkReuse checks whether a revision of a given module 102 // for a given module may be reused, according to the information in origin. 103 func checkReuse(ctx context.Context, m module.Version, old *codehost.Origin) error { 104 return modfetch.TryProxies(func(proxy string) error { 105 repo, err := lookupRepo(ctx, proxy, m.Path) 106 if err != nil { 107 return err 108 } 109 return checkReuseRepo(ctx, repo, m.Path, m.Version, old) 110 }) 111 } 112 113 func checkReuseRepo(ctx context.Context, repo versionRepo, path, query string, origin *codehost.Origin) error { 114 if origin == nil { 115 return errors.New("nil Origin") 116 } 117 118 // Ensure that the Origin actually includes enough fields to resolve the query. 119 // If we got the previous Origin data from a proxy, it may be missing something 120 // that we would have needed to resolve the query directly from the repo. 121 switch { 122 case origin.RepoSum != "": 123 // A RepoSum is always acceptable, since it incorporates everything 124 // (and is often associated with an error result). 125 126 case query == module.CanonicalVersion(query): 127 // This query refers to a specific version, and Go module versions 128 // are supposed to be cacheable and immutable (confirmed with checksums). 129 // If the version exists at all, we shouldn't need any extra information 130 // to identify which commit it resolves to. 131 // 132 // It may be associated with a Ref for a semantic-version tag, but if so 133 // we don't expect that tag to change in the future. We also don't need a 134 // TagSum: if a tag is removed from some ancestor commit, the version may 135 // change from valid to invalid, but we're ok with keeping stale versions 136 // as long as they were valid at some point in the past. 137 // 138 // If the version did not successfully resolve, the origin may indicate 139 // a TagSum and/or RepoSum instead of a Hash, in which case we still need 140 // to check those to ensure that the error is still applicable. 141 if origin.Hash == "" && origin.Ref == "" && origin.TagSum == "" { 142 return errors.New("no Origin information to check") 143 } 144 145 case IsRevisionQuery(path, query): 146 // This query may refer to a branch, non-version tag, or commit ID. 147 // 148 // If it is a commit ID, we expect to see a Hash in the Origin data. On 149 // the other hand, if it is not a commit ID, we expect to see either a Ref 150 // (for a positive result) or a RepoSum (for a negative result), since 151 // we don't expect refs in general to remain stable over time. 152 if origin.Hash == "" && origin.Ref == "" { 153 return fmt.Errorf("query %q requires a Hash or Ref", query) 154 } 155 // Once we resolve the query to a particular commit, we will need to 156 // also identify the most appropriate version to assign to that commit. 157 // (It may correspond to more than one valid version.) 158 // 159 // The most appropriate version depends on the tags associated with 160 // both the commit itself (if the commit is a tagged version) 161 // and its ancestors (if we need to produce a pseudo-version for it). 162 if origin.TagSum == "" { 163 return fmt.Errorf("query %q requires a TagSum", query) 164 } 165 166 default: 167 // The query may be "latest" or a version inequality or prefix. 168 // Its result depends on the absence of higher tags matching the query, 169 // not just the state of an individual ref or tag. 170 if origin.TagSum == "" { 171 return fmt.Errorf("query %q requires a TagSum", query) 172 } 173 } 174 175 return repo.CheckReuse(ctx, origin) 176 } 177 178 // AllowedFunc is used by Query and other functions to filter out unsuitable 179 // versions, for example, those listed in exclude directives in the main 180 // module's go.mod file. 181 // 182 // An AllowedFunc returns an error equivalent to ErrDisallowed for an unsuitable 183 // version. Any other error indicates the function was unable to determine 184 // whether the version should be allowed, for example, the function was unable 185 // to fetch or parse a go.mod file containing retractions. Typically, errors 186 // other than ErrDisallowed may be ignored. 187 type AllowedFunc func(context.Context, module.Version) error 188 189 var errQueryDisabled error = queryDisabledError{} 190 191 type queryDisabledError struct{} 192 193 func (queryDisabledError) Error() string { 194 if cfg.BuildModReason == "" { 195 return fmt.Sprintf("cannot query module due to -mod=%s", cfg.BuildMod) 196 } 197 return fmt.Sprintf("cannot query module due to -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason) 198 } 199 200 func queryProxy(ctx context.Context, proxy, path, query, current string, allowed AllowedFunc, reuse map[module.Version]*modinfo.ModulePublic) (*modfetch.RevInfo, error) { 201 ctx, span := trace.StartSpan(ctx, "modload.queryProxy "+path+" "+query) 202 defer span.Done() 203 204 if current != "" && current != "none" && !gover.ModIsValid(path, current) { 205 return nil, fmt.Errorf("invalid previous version %v@%v", path, current) 206 } 207 if cfg.BuildMod == "vendor" { 208 return nil, errQueryDisabled 209 } 210 if allowed == nil { 211 allowed = func(context.Context, module.Version) error { return nil } 212 } 213 214 if MainModules.Contains(path) && (query == "upgrade" || query == "patch") { 215 m := module.Version{Path: path} 216 if err := allowed(ctx, m); err != nil { 217 return nil, fmt.Errorf("internal error: main module version is not allowed: %w", err) 218 } 219 return &modfetch.RevInfo{Version: m.Version}, nil 220 } 221 222 if path == "std" || path == "cmd" { 223 return nil, fmt.Errorf("can't query specific version (%q) of standard-library module %q", query, path) 224 } 225 226 repo, err := lookupRepo(ctx, proxy, path) 227 if err != nil { 228 return nil, err 229 } 230 231 if old := reuse[module.Version{Path: path, Version: query}]; old != nil { 232 if err := checkReuseRepo(ctx, repo, path, query, old.Origin); err == nil { 233 info := &modfetch.RevInfo{ 234 Version: old.Version, 235 Origin: old.Origin, 236 } 237 if old.Time != nil { 238 info.Time = *old.Time 239 } 240 return info, nil 241 } 242 } 243 244 // Parse query to detect parse errors (and possibly handle query) 245 // before any network I/O. 246 qm, err := newQueryMatcher(path, query, current, allowed) 247 if (err == nil && qm.canStat) || err == errRevQuery { 248 // Direct lookup of a commit identifier or complete (non-prefix) semantic 249 // version. 250 251 // If the identifier is not a canonical semver tag — including if it's a 252 // semver tag with a +metadata suffix — then modfetch.Stat will populate 253 // info.Version with a suitable pseudo-version. 254 info, err := repo.Stat(ctx, query) 255 if err != nil { 256 queryErr := err 257 // The full query doesn't correspond to a tag. If it is a semantic version 258 // with a +metadata suffix, see if there is a tag without that suffix: 259 // semantic versioning defines them to be equivalent. 260 canonicalQuery := module.CanonicalVersion(query) 261 if canonicalQuery != "" && query != canonicalQuery { 262 info, err = repo.Stat(ctx, canonicalQuery) 263 if err != nil && !errors.Is(err, fs.ErrNotExist) { 264 return info, err 265 } 266 } 267 if err != nil { 268 return info, queryErr 269 } 270 } 271 if err := allowed(ctx, module.Version{Path: path, Version: info.Version}); errors.Is(err, ErrDisallowed) { 272 return nil, err 273 } 274 return info, nil 275 } else if err != nil { 276 return nil, err 277 } 278 279 // Load versions and execute query. 280 versions, err := repo.Versions(ctx, qm.prefix) 281 if err != nil { 282 return nil, err 283 } 284 origin := versions.Origin 285 286 revWithOrigin := func(rev *modfetch.RevInfo) *modfetch.RevInfo { 287 if rev == nil { 288 if origin == nil { 289 return nil 290 } 291 return &modfetch.RevInfo{Origin: origin} 292 } 293 294 clone := *rev 295 clone.Origin = origin 296 return &clone 297 } 298 299 releases, prereleases, err := qm.filterVersions(ctx, versions.List) 300 if err != nil { 301 return revWithOrigin(nil), err 302 } 303 304 lookup := func(v string) (*modfetch.RevInfo, error) { 305 rev, err := repo.Stat(ctx, v) 306 if rev != nil { 307 // Note that Stat can return a non-nil rev and a non-nil err, 308 // in order to provide origin information to make the error cacheable. 309 origin = mergeOrigin(origin, rev.Origin) 310 } 311 if err != nil { 312 return revWithOrigin(nil), err 313 } 314 315 if (query == "upgrade" || query == "patch") && module.IsPseudoVersion(current) && !rev.Time.IsZero() { 316 // Don't allow "upgrade" or "patch" to move from a pseudo-version 317 // to a chronologically older version or pseudo-version. 318 // 319 // If the current version is a pseudo-version from an untagged branch, it 320 // may be semantically lower than the "latest" release or the latest 321 // pseudo-version on the main branch. A user on such a version is unlikely 322 // to intend to “upgrade” to a version that already existed at that point 323 // in time. 324 // 325 // We do this only if the current version is a pseudo-version: if the 326 // version is tagged, the author of the dependency module has given us 327 // explicit information about their intended precedence of this version 328 // relative to other versions, and we shouldn't contradict that 329 // information. (For example, v1.0.1 might be a backport of a fix already 330 // incorporated into v1.1.0, in which case v1.0.1 would be chronologically 331 // newer but v1.1.0 is still an “upgrade”; or v1.0.2 might be a revert of 332 // an unsuccessful fix in v1.0.1, in which case the v1.0.2 commit may be 333 // older than the v1.0.1 commit despite the tag itself being newer.) 334 currentTime, err := module.PseudoVersionTime(current) 335 if err == nil && rev.Time.Before(currentTime) { 336 if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) { 337 return revWithOrigin(nil), err 338 } 339 rev, err = repo.Stat(ctx, current) 340 if rev != nil { 341 origin = mergeOrigin(origin, rev.Origin) 342 } 343 if err != nil { 344 return revWithOrigin(nil), err 345 } 346 return revWithOrigin(rev), nil 347 } 348 } 349 350 return revWithOrigin(rev), nil 351 } 352 353 if qm.preferLower { 354 if len(releases) > 0 { 355 return lookup(releases[0]) 356 } 357 if len(prereleases) > 0 { 358 return lookup(prereleases[0]) 359 } 360 } else { 361 if len(releases) > 0 { 362 return lookup(releases[len(releases)-1]) 363 } 364 if len(prereleases) > 0 { 365 return lookup(prereleases[len(prereleases)-1]) 366 } 367 } 368 369 if qm.mayUseLatest { 370 latest, err := repo.Latest(ctx) 371 if latest != nil { 372 origin = mergeOrigin(origin, latest.Origin) 373 } 374 if err == nil { 375 if qm.allowsVersion(ctx, latest.Version) { 376 return lookup(latest.Version) 377 } 378 } else if !errors.Is(err, fs.ErrNotExist) { 379 return revWithOrigin(nil), err 380 } 381 } 382 383 if (query == "upgrade" || query == "patch") && current != "" && current != "none" { 384 // "upgrade" and "patch" may stay on the current version if allowed. 385 if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) { 386 return revWithOrigin(nil), err 387 } 388 return lookup(current) 389 } 390 391 return revWithOrigin(nil), &NoMatchingVersionError{query: query, current: current} 392 } 393 394 // IsRevisionQuery returns true if vers is a version query that may refer to 395 // a particular version or revision in a repository like "v1.0.0", "master", 396 // or "0123abcd". IsRevisionQuery returns false if vers is a query that 397 // chooses from among available versions like "latest" or ">v1.0.0". 398 func IsRevisionQuery(path, vers string) bool { 399 if vers == "latest" || 400 vers == "upgrade" || 401 vers == "patch" || 402 strings.HasPrefix(vers, "<") || 403 strings.HasPrefix(vers, ">") || 404 (gover.ModIsValid(path, vers) && gover.ModIsPrefix(path, vers)) { 405 return false 406 } 407 return true 408 } 409 410 type queryMatcher struct { 411 path string 412 prefix string 413 filter func(version string) bool 414 allowed AllowedFunc 415 canStat bool // if true, the query can be resolved by repo.Stat 416 preferLower bool // if true, choose the lowest matching version 417 mayUseLatest bool 418 preferIncompatible bool 419 } 420 421 var errRevQuery = errors.New("query refers to a non-semver revision") 422 423 // newQueryMatcher returns a new queryMatcher that matches the versions 424 // specified by the given query on the module with the given path. 425 // 426 // If the query can only be resolved by statting a non-SemVer revision, 427 // newQueryMatcher returns errRevQuery. 428 func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (*queryMatcher, error) { 429 badVersion := func(v string) (*queryMatcher, error) { 430 return nil, fmt.Errorf("invalid semantic version %q in range %q", v, query) 431 } 432 433 matchesMajor := func(v string) bool { 434 _, pathMajor, ok := module.SplitPathVersion(path) 435 if !ok { 436 return false 437 } 438 return module.CheckPathMajor(v, pathMajor) == nil 439 } 440 441 qm := &queryMatcher{ 442 path: path, 443 allowed: allowed, 444 preferIncompatible: strings.HasSuffix(current, "+incompatible"), 445 } 446 447 switch { 448 case query == "latest": 449 qm.mayUseLatest = true 450 451 case query == "upgrade": 452 if current == "" || current == "none" { 453 qm.mayUseLatest = true 454 } else { 455 qm.mayUseLatest = module.IsPseudoVersion(current) 456 qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, current) >= 0 } 457 } 458 459 case query == "patch": 460 if current == "" || current == "none" { 461 return nil, &NoPatchBaseError{path} 462 } 463 if current == "" { 464 qm.mayUseLatest = true 465 } else { 466 qm.mayUseLatest = module.IsPseudoVersion(current) 467 qm.prefix = gover.ModMajorMinor(qm.path, current) + "." 468 qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, current) >= 0 } 469 } 470 471 case strings.HasPrefix(query, "<="): 472 v := query[len("<="):] 473 if !gover.ModIsValid(path, v) { 474 return badVersion(v) 475 } 476 if gover.ModIsPrefix(path, v) { 477 // Refuse to say whether <=v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3). 478 return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query) 479 } 480 qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, v) <= 0 } 481 if !matchesMajor(v) { 482 qm.preferIncompatible = true 483 } 484 485 case strings.HasPrefix(query, "<"): 486 v := query[len("<"):] 487 if !gover.ModIsValid(path, v) { 488 return badVersion(v) 489 } 490 qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, v) < 0 } 491 if !matchesMajor(v) { 492 qm.preferIncompatible = true 493 } 494 495 case strings.HasPrefix(query, ">="): 496 v := query[len(">="):] 497 if !gover.ModIsValid(path, v) { 498 return badVersion(v) 499 } 500 qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, v) >= 0 } 501 qm.preferLower = true 502 if !matchesMajor(v) { 503 qm.preferIncompatible = true 504 } 505 506 case strings.HasPrefix(query, ">"): 507 v := query[len(">"):] 508 if !gover.ModIsValid(path, v) { 509 return badVersion(v) 510 } 511 if gover.ModIsPrefix(path, v) { 512 // Refuse to say whether >v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3). 513 return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query) 514 } 515 qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, v) > 0 } 516 qm.preferLower = true 517 if !matchesMajor(v) { 518 qm.preferIncompatible = true 519 } 520 521 case gover.ModIsValid(path, query): 522 if gover.ModIsPrefix(path, query) { 523 qm.prefix = query + "." 524 // Do not allow the query "v1.2" to match versions lower than "v1.2.0", 525 // such as prereleases for that version. (https://golang.org/issue/31972) 526 qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, query) >= 0 } 527 } else { 528 qm.canStat = true 529 qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, query) == 0 } 530 qm.prefix = semver.Canonical(query) 531 } 532 if !matchesMajor(query) { 533 qm.preferIncompatible = true 534 } 535 536 default: 537 return nil, errRevQuery 538 } 539 540 return qm, nil 541 } 542 543 // allowsVersion reports whether version v is allowed by the prefix, filter, and 544 // AllowedFunc of qm. 545 func (qm *queryMatcher) allowsVersion(ctx context.Context, v string) bool { 546 if qm.prefix != "" && !strings.HasPrefix(v, qm.prefix) { 547 if gover.IsToolchain(qm.path) && strings.TrimSuffix(qm.prefix, ".") == v { 548 // Allow 1.21 to match "1.21." prefix. 549 } else { 550 return false 551 } 552 } 553 if qm.filter != nil && !qm.filter(v) { 554 return false 555 } 556 if qm.allowed != nil { 557 if err := qm.allowed(ctx, module.Version{Path: qm.path, Version: v}); errors.Is(err, ErrDisallowed) { 558 return false 559 } 560 } 561 return true 562 } 563 564 // filterVersions classifies versions into releases and pre-releases, filtering 565 // out: 566 // 1. versions that do not satisfy the 'allowed' predicate, and 567 // 2. "+incompatible" versions, if a compatible one satisfies the predicate 568 // and the incompatible version is not preferred. 569 // 570 // If the allowed predicate returns an error not equivalent to ErrDisallowed, 571 // filterVersions returns that error. 572 func (qm *queryMatcher) filterVersions(ctx context.Context, versions []string) (releases, prereleases []string, err error) { 573 needIncompatible := qm.preferIncompatible 574 575 var lastCompatible string 576 for _, v := range versions { 577 if !qm.allowsVersion(ctx, v) { 578 continue 579 } 580 581 if !needIncompatible { 582 // We're not yet sure whether we need to include +incompatible versions. 583 // Keep track of the last compatible version we've seen, and use the 584 // presence (or absence) of a go.mod file in that version to decide: a 585 // go.mod file implies that the module author is supporting modules at a 586 // compatible version (and we should ignore +incompatible versions unless 587 // requested explicitly), while a lack of go.mod file implies the 588 // potential for legacy (pre-modules) versioning without semantic import 589 // paths (and thus *with* +incompatible versions). 590 // 591 // This isn't strictly accurate if the latest compatible version has been 592 // replaced by a local file path, because we do not allow file-path 593 // replacements without a go.mod file: the user would have needed to add 594 // one. However, replacing the last compatible version while 595 // simultaneously expecting to upgrade implicitly to a +incompatible 596 // version seems like an extreme enough corner case to ignore for now. 597 598 if !strings.HasSuffix(v, "+incompatible") { 599 lastCompatible = v 600 } else if lastCompatible != "" { 601 // If the latest compatible version is allowed and has a go.mod file, 602 // ignore any version with a higher (+incompatible) major version. (See 603 // https://golang.org/issue/34165.) Note that we even prefer a 604 // compatible pre-release over an incompatible release. 605 ok, err := versionHasGoMod(ctx, module.Version{Path: qm.path, Version: lastCompatible}) 606 if err != nil { 607 return nil, nil, err 608 } 609 if ok { 610 // The last compatible version has a go.mod file, so that's the 611 // highest version we're willing to consider. Don't bother even 612 // looking at higher versions, because they're all +incompatible from 613 // here onward. 614 break 615 } 616 617 // No acceptable compatible release has a go.mod file, so the versioning 618 // for the module might not be module-aware, and we should respect 619 // legacy major-version tags. 620 needIncompatible = true 621 } 622 } 623 624 if gover.ModIsPrerelease(qm.path, v) { 625 prereleases = append(prereleases, v) 626 } else { 627 releases = append(releases, v) 628 } 629 } 630 631 return releases, prereleases, nil 632 } 633 634 type QueryResult struct { 635 Mod module.Version 636 Rev *modfetch.RevInfo 637 Packages []string 638 } 639 640 // QueryPackages is like QueryPattern, but requires that the pattern match at 641 // least one package and omits the non-package result (if any). 642 func QueryPackages(ctx context.Context, pattern, query string, current func(string) string, allowed AllowedFunc) ([]QueryResult, error) { 643 pkgMods, modOnly, err := QueryPattern(ctx, pattern, query, current, allowed) 644 645 if len(pkgMods) == 0 && err == nil { 646 replacement := Replacement(modOnly.Mod) 647 return nil, &PackageNotInModuleError{ 648 Mod: modOnly.Mod, 649 Replacement: replacement, 650 Query: query, 651 Pattern: pattern, 652 } 653 } 654 655 return pkgMods, err 656 } 657 658 // QueryPattern looks up the module(s) containing at least one package matching 659 // the given pattern at the given version. The results are sorted by module path 660 // length in descending order. If any proxy provides a non-empty set of candidate 661 // modules, no further proxies are tried. 662 // 663 // For wildcard patterns, QueryPattern looks in modules with package paths up to 664 // the first "..." in the pattern. For the pattern "example.com/a/b.../c", 665 // QueryPattern would consider prefixes of "example.com/a". 666 // 667 // If any matching package is in the main module, QueryPattern considers only 668 // the main module and only the version "latest", without checking for other 669 // possible modules. 670 // 671 // QueryPattern always returns at least one QueryResult (which may be only 672 // modOnly) or a non-nil error. 673 func QueryPattern(ctx context.Context, pattern, query string, current func(string) string, allowed AllowedFunc) (pkgMods []QueryResult, modOnly *QueryResult, err error) { 674 ctx, span := trace.StartSpan(ctx, "modload.QueryPattern "+pattern+" "+query) 675 defer span.Done() 676 677 base := pattern 678 679 firstError := func(m *search.Match) error { 680 if len(m.Errs) == 0 { 681 return nil 682 } 683 return m.Errs[0] 684 } 685 686 var match func(mod module.Version, roots []string, isLocal bool) *search.Match 687 matchPattern := pkgpattern.MatchPattern(pattern) 688 689 if i := strings.Index(pattern, "..."); i >= 0 { 690 base = pathpkg.Dir(pattern[:i+3]) 691 if base == "." { 692 return nil, nil, &WildcardInFirstElementError{Pattern: pattern, Query: query} 693 } 694 match = func(mod module.Version, roots []string, isLocal bool) *search.Match { 695 m := search.NewMatch(pattern) 696 matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{mod}) 697 return m 698 } 699 } else { 700 match = func(mod module.Version, roots []string, isLocal bool) *search.Match { 701 m := search.NewMatch(pattern) 702 prefix := mod.Path 703 if MainModules.Contains(mod.Path) { 704 prefix = MainModules.PathPrefix(module.Version{Path: mod.Path}) 705 } 706 for _, root := range roots { 707 if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil { 708 m.AddError(err) 709 } else if ok { 710 m.Pkgs = []string{pattern} 711 } 712 } 713 return m 714 } 715 } 716 717 var mainModuleMatches []module.Version 718 for _, mainModule := range MainModules.Versions() { 719 m := match(mainModule, modRoots, true) 720 if len(m.Pkgs) > 0 { 721 if query != "upgrade" && query != "patch" { 722 return nil, nil, &QueryMatchesPackagesInMainModuleError{ 723 Pattern: pattern, 724 Query: query, 725 Packages: m.Pkgs, 726 } 727 } 728 if err := allowed(ctx, mainModule); err != nil { 729 return nil, nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed: %w", pattern, mainModule.Path, err) 730 } 731 return []QueryResult{{ 732 Mod: mainModule, 733 Rev: &modfetch.RevInfo{Version: mainModule.Version}, 734 Packages: m.Pkgs, 735 }}, nil, nil 736 } 737 if err := firstError(m); err != nil { 738 return nil, nil, err 739 } 740 741 var matchesMainModule bool 742 if matchPattern(mainModule.Path) { 743 mainModuleMatches = append(mainModuleMatches, mainModule) 744 matchesMainModule = true 745 } 746 747 if (query == "upgrade" || query == "patch") && matchesMainModule { 748 if err := allowed(ctx, mainModule); err == nil { 749 modOnly = &QueryResult{ 750 Mod: mainModule, 751 Rev: &modfetch.RevInfo{Version: mainModule.Version}, 752 } 753 } 754 } 755 } 756 757 var ( 758 results []QueryResult 759 candidateModules = modulePrefixesExcludingTarget(base) 760 ) 761 if len(candidateModules) == 0 { 762 if modOnly != nil { 763 return nil, modOnly, nil 764 } else if len(mainModuleMatches) != 0 { 765 return nil, nil, &QueryMatchesMainModulesError{ 766 MainModules: mainModuleMatches, 767 Pattern: pattern, 768 Query: query, 769 } 770 } else { 771 return nil, nil, &PackageNotInModuleError{ 772 MainModules: mainModuleMatches, 773 Query: query, 774 Pattern: pattern, 775 } 776 } 777 } 778 779 err = modfetch.TryProxies(func(proxy string) error { 780 queryModule := func(ctx context.Context, path string) (r QueryResult, err error) { 781 ctx, span := trace.StartSpan(ctx, "modload.QueryPattern.queryModule ["+proxy+"] "+path) 782 defer span.Done() 783 784 pathCurrent := current(path) 785 r.Mod.Path = path 786 r.Rev, err = queryProxy(ctx, proxy, path, query, pathCurrent, allowed, nil) 787 if err != nil { 788 return r, err 789 } 790 r.Mod.Version = r.Rev.Version 791 if gover.IsToolchain(r.Mod.Path) { 792 return r, nil 793 } 794 root, isLocal, err := fetch(ctx, r.Mod) 795 if err != nil { 796 return r, err 797 } 798 m := match(r.Mod, []string{root}, isLocal) 799 r.Packages = m.Pkgs 800 if len(r.Packages) == 0 && !matchPattern(path) { 801 if err := firstError(m); err != nil { 802 return r, err 803 } 804 replacement := Replacement(r.Mod) 805 return r, &PackageNotInModuleError{ 806 Mod: r.Mod, 807 Replacement: replacement, 808 Query: query, 809 Pattern: pattern, 810 } 811 } 812 return r, nil 813 } 814 815 allResults, err := queryPrefixModules(ctx, candidateModules, queryModule) 816 results = allResults[:0] 817 for _, r := range allResults { 818 if len(r.Packages) == 0 { 819 modOnly = &r 820 } else { 821 results = append(results, r) 822 } 823 } 824 return err 825 }) 826 827 if len(mainModuleMatches) > 0 && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) { 828 return nil, nil, &QueryMatchesMainModulesError{ 829 Pattern: pattern, 830 Query: query, 831 } 832 } 833 return slices.Clip(results), modOnly, err 834 } 835 836 // modulePrefixesExcludingTarget returns all prefixes of path that may plausibly 837 // exist as a module, excluding targetPrefix but otherwise including path 838 // itself, sorted by descending length. Prefixes that are not valid module paths 839 // but are valid package paths (like "m" or "example.com/.gen") are included, 840 // since they might be replaced. 841 func modulePrefixesExcludingTarget(path string) []string { 842 prefixes := make([]string, 0, strings.Count(path, "/")+1) 843 844 mainModulePrefixes := make(map[string]bool) 845 for _, m := range MainModules.Versions() { 846 mainModulePrefixes[m.Path] = true 847 } 848 849 for { 850 if !mainModulePrefixes[path] { 851 if _, _, ok := module.SplitPathVersion(path); ok { 852 prefixes = append(prefixes, path) 853 } 854 } 855 856 j := strings.LastIndexByte(path, '/') 857 if j < 0 { 858 break 859 } 860 path = path[:j] 861 } 862 863 return prefixes 864 } 865 866 func queryPrefixModules(ctx context.Context, candidateModules []string, queryModule func(ctx context.Context, path string) (QueryResult, error)) (found []QueryResult, err error) { 867 ctx, span := trace.StartSpan(ctx, "modload.queryPrefixModules") 868 defer span.Done() 869 870 // If the path we're attempting is not in the module cache and we don't have a 871 // fetch result cached either, we'll end up making a (potentially slow) 872 // request to the proxy or (often even slower) the origin server. 873 // To minimize latency, execute all of those requests in parallel. 874 type result struct { 875 QueryResult 876 err error 877 } 878 results := make([]result, len(candidateModules)) 879 var wg sync.WaitGroup 880 wg.Add(len(candidateModules)) 881 for i, p := range candidateModules { 882 ctx := trace.StartGoroutine(ctx) 883 go func(p string, r *result) { 884 r.QueryResult, r.err = queryModule(ctx, p) 885 wg.Done() 886 }(p, &results[i]) 887 } 888 wg.Wait() 889 890 // Classify the results. In case of failure, identify the error that the user 891 // is most likely to find helpful: the most useful class of error at the 892 // longest matching path. 893 var ( 894 noPackage *PackageNotInModuleError 895 noVersion *NoMatchingVersionError 896 noPatchBase *NoPatchBaseError 897 invalidPath *module.InvalidPathError // see comment in case below 898 invalidVersion error 899 notExistErr error 900 ) 901 for _, r := range results { 902 switch rErr := r.err.(type) { 903 case nil: 904 found = append(found, r.QueryResult) 905 case *PackageNotInModuleError: 906 // Given the option, prefer to attribute “package not in module” 907 // to modules other than the main one. 908 if noPackage == nil || MainModules.Contains(noPackage.Mod.Path) { 909 noPackage = rErr 910 } 911 case *NoMatchingVersionError: 912 if noVersion == nil { 913 noVersion = rErr 914 } 915 case *NoPatchBaseError: 916 if noPatchBase == nil { 917 noPatchBase = rErr 918 } 919 case *module.InvalidPathError: 920 // The prefix was not a valid module path, and there was no replacement. 921 // Prefixes like this may appear in candidateModules, since we handle 922 // replaced modules that weren't required in the repo lookup process 923 // (see lookupRepo). 924 // 925 // A shorter prefix may be a valid module path and may contain a valid 926 // import path, so this is a low-priority error. 927 if invalidPath == nil { 928 invalidPath = rErr 929 } 930 default: 931 if errors.Is(rErr, fs.ErrNotExist) { 932 if notExistErr == nil { 933 notExistErr = rErr 934 } 935 } else if iv := (*module.InvalidVersionError)(nil); errors.As(rErr, &iv) { 936 if invalidVersion == nil { 937 invalidVersion = rErr 938 } 939 } else if err == nil { 940 if len(found) > 0 || noPackage != nil { 941 // golang.org/issue/34094: If we have already found a module that 942 // could potentially contain the target package, ignore unclassified 943 // errors for modules with shorter paths. 944 945 // golang.org/issue/34383 is a special case of this: if we have 946 // already found example.com/foo/v2@v2.0.0 with a matching go.mod 947 // file, ignore the error from example.com/foo@v2.0.0. 948 } else { 949 err = r.err 950 } 951 } 952 } 953 } 954 955 // TODO(#26232): If len(found) == 0 and some of the errors are 4xx HTTP 956 // codes, have the auth package recheck the failed paths. 957 // If we obtain new credentials for any of them, re-run the above loop. 958 959 if len(found) == 0 && err == nil { 960 switch { 961 case noPackage != nil: 962 err = noPackage 963 case noVersion != nil: 964 err = noVersion 965 case noPatchBase != nil: 966 err = noPatchBase 967 case invalidPath != nil: 968 err = invalidPath 969 case invalidVersion != nil: 970 err = invalidVersion 971 case notExistErr != nil: 972 err = notExistErr 973 default: 974 panic("queryPrefixModules: no modules found, but no error detected") 975 } 976 } 977 978 return found, err 979 } 980 981 // A NoMatchingVersionError indicates that Query found a module at the requested 982 // path, but not at any versions satisfying the query string and allow-function. 983 // 984 // NOTE: NoMatchingVersionError MUST NOT implement Is(fs.ErrNotExist). 985 // 986 // If the module came from a proxy, that proxy had to return a successful status 987 // code for the versions it knows about, and thus did not have the opportunity 988 // to return a non-400 status code to suppress fallback. 989 type NoMatchingVersionError struct { 990 query, current string 991 } 992 993 func (e *NoMatchingVersionError) Error() string { 994 currentSuffix := "" 995 if (e.query == "upgrade" || e.query == "patch") && e.current != "" && e.current != "none" { 996 currentSuffix = fmt.Sprintf(" (current version is %s)", e.current) 997 } 998 return fmt.Sprintf("no matching versions for query %q", e.query) + currentSuffix 999 } 1000 1001 // A NoPatchBaseError indicates that Query was called with the query "patch" 1002 // but with a current version of "" or "none". 1003 type NoPatchBaseError struct { 1004 path string 1005 } 1006 1007 func (e *NoPatchBaseError) Error() string { 1008 return fmt.Sprintf(`can't query version "patch" of module %s: no existing version is required`, e.path) 1009 } 1010 1011 // A WildcardInFirstElementError indicates that a pattern passed to QueryPattern 1012 // had a wildcard in its first path element, and therefore had no pattern-prefix 1013 // modules to search in. 1014 type WildcardInFirstElementError struct { 1015 Pattern string 1016 Query string 1017 } 1018 1019 func (e *WildcardInFirstElementError) Error() string { 1020 return fmt.Sprintf("no modules to query for %s@%s because first path element contains a wildcard", e.Pattern, e.Query) 1021 } 1022 1023 // A PackageNotInModuleError indicates that QueryPattern found a candidate 1024 // module at the requested version, but that module did not contain any packages 1025 // matching the requested pattern. 1026 // 1027 // NOTE: PackageNotInModuleError MUST NOT implement Is(fs.ErrNotExist). 1028 // 1029 // If the module came from a proxy, that proxy had to return a successful status 1030 // code for the versions it knows about, and thus did not have the opportunity 1031 // to return a non-400 status code to suppress fallback. 1032 type PackageNotInModuleError struct { 1033 MainModules []module.Version 1034 Mod module.Version 1035 Replacement module.Version 1036 Query string 1037 Pattern string 1038 } 1039 1040 func (e *PackageNotInModuleError) Error() string { 1041 if len(e.MainModules) > 0 { 1042 prefix := "workspace modules do" 1043 if len(e.MainModules) == 1 { 1044 prefix = fmt.Sprintf("main module (%s) does", e.MainModules[0]) 1045 } 1046 if strings.Contains(e.Pattern, "...") { 1047 return fmt.Sprintf("%s not contain packages matching %s", prefix, e.Pattern) 1048 } 1049 return fmt.Sprintf("%s not contain package %s", prefix, e.Pattern) 1050 } 1051 1052 found := "" 1053 if r := e.Replacement; r.Path != "" { 1054 replacement := r.Path 1055 if r.Version != "" { 1056 replacement = fmt.Sprintf("%s@%s", r.Path, r.Version) 1057 } 1058 if e.Query == e.Mod.Version { 1059 found = fmt.Sprintf(" (replaced by %s)", replacement) 1060 } else { 1061 found = fmt.Sprintf(" (%s, replaced by %s)", e.Mod.Version, replacement) 1062 } 1063 } else if e.Query != e.Mod.Version { 1064 found = fmt.Sprintf(" (%s)", e.Mod.Version) 1065 } 1066 1067 if strings.Contains(e.Pattern, "...") { 1068 return fmt.Sprintf("module %s@%s found%s, but does not contain packages matching %s", e.Mod.Path, e.Query, found, e.Pattern) 1069 } 1070 return fmt.Sprintf("module %s@%s found%s, but does not contain package %s", e.Mod.Path, e.Query, found, e.Pattern) 1071 } 1072 1073 func (e *PackageNotInModuleError) ImportPath() string { 1074 if !strings.Contains(e.Pattern, "...") { 1075 return e.Pattern 1076 } 1077 return "" 1078 } 1079 1080 // versionHasGoMod returns whether a version has a go.mod file. 1081 // 1082 // versionHasGoMod fetches the go.mod file (possibly a fake) and true if it 1083 // contains anything other than a module directive with the same path. When a 1084 // module does not have a real go.mod file, the go command acts as if it had one 1085 // that only contained a module directive. Normal go.mod files created after 1086 // 1.12 at least have a go directive. 1087 // 1088 // This function is a heuristic, since it's possible to commit a file that would 1089 // pass this test. However, we only need a heuristic for determining whether 1090 // +incompatible versions may be "latest", which is what this function is used 1091 // for. 1092 // 1093 // This heuristic is useful for two reasons: first, when using a proxy, 1094 // this lets us fetch from the .mod endpoint which is much faster than the .zip 1095 // endpoint. The .mod file is used anyway, even if the .zip file contains a 1096 // go.mod with different content. Second, if we don't fetch the .zip, then 1097 // we don't need to verify it in go.sum. This makes 'go list -m -u' faster 1098 // and simpler. 1099 func versionHasGoMod(_ context.Context, m module.Version) (bool, error) { 1100 _, data, err := rawGoModData(m) 1101 if err != nil { 1102 return false, err 1103 } 1104 isFake := bytes.Equal(data, modfetch.LegacyGoMod(m.Path)) 1105 return !isFake, nil 1106 } 1107 1108 // A versionRepo is a subset of modfetch.Repo that can report information about 1109 // available versions, but cannot fetch specific source files. 1110 type versionRepo interface { 1111 ModulePath() string 1112 CheckReuse(context.Context, *codehost.Origin) error 1113 Versions(ctx context.Context, prefix string) (*modfetch.Versions, error) 1114 Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error) 1115 Latest(context.Context) (*modfetch.RevInfo, error) 1116 } 1117 1118 var _ versionRepo = modfetch.Repo(nil) 1119 1120 func lookupRepo(ctx context.Context, proxy, path string) (repo versionRepo, err error) { 1121 if path != "go" && path != "toolchain" { 1122 err = module.CheckPath(path) 1123 } 1124 if err == nil { 1125 repo = modfetch.Lookup(ctx, proxy, path) 1126 } else { 1127 repo = emptyRepo{path: path, err: err} 1128 } 1129 1130 if MainModules == nil { 1131 return repo, err 1132 } else if _, ok := MainModules.HighestReplaced()[path]; ok { 1133 return &replacementRepo{repo: repo}, nil 1134 } 1135 1136 return repo, err 1137 } 1138 1139 // An emptyRepo is a versionRepo that contains no versions. 1140 type emptyRepo struct { 1141 path string 1142 err error 1143 } 1144 1145 var _ versionRepo = emptyRepo{} 1146 1147 func (er emptyRepo) ModulePath() string { return er.path } 1148 func (er emptyRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error { 1149 return fmt.Errorf("empty repo") 1150 } 1151 func (er emptyRepo) Versions(ctx context.Context, prefix string) (*modfetch.Versions, error) { 1152 return &modfetch.Versions{}, nil 1153 } 1154 func (er emptyRepo) Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error) { 1155 return nil, er.err 1156 } 1157 func (er emptyRepo) Latest(ctx context.Context) (*modfetch.RevInfo, error) { return nil, er.err } 1158 1159 // A replacementRepo augments a versionRepo to include the replacement versions 1160 // (if any) found in the main module's go.mod file. 1161 // 1162 // A replacementRepo suppresses "not found" errors for otherwise-nonexistent 1163 // modules, so a replacementRepo should only be constructed for a module that 1164 // actually has one or more valid replacements. 1165 type replacementRepo struct { 1166 repo versionRepo 1167 } 1168 1169 var _ versionRepo = (*replacementRepo)(nil) 1170 1171 func (rr *replacementRepo) ModulePath() string { return rr.repo.ModulePath() } 1172 1173 func (rr *replacementRepo) CheckReuse(ctx context.Context, old *codehost.Origin) error { 1174 return fmt.Errorf("replacement repo") 1175 } 1176 1177 // Versions returns the versions from rr.repo augmented with any matching 1178 // replacement versions. 1179 func (rr *replacementRepo) Versions(ctx context.Context, prefix string) (*modfetch.Versions, error) { 1180 repoVersions, err := rr.repo.Versions(ctx, prefix) 1181 if err != nil { 1182 if !errors.Is(err, os.ErrNotExist) { 1183 return nil, err 1184 } 1185 repoVersions = new(modfetch.Versions) 1186 } 1187 1188 versions := repoVersions.List 1189 for _, mm := range MainModules.Versions() { 1190 if index := MainModules.Index(mm); index != nil && len(index.replace) > 0 { 1191 path := rr.ModulePath() 1192 for m := range index.replace { 1193 if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) { 1194 versions = append(versions, m.Version) 1195 } 1196 } 1197 } 1198 } 1199 1200 if len(versions) == len(repoVersions.List) { // replacement versions added 1201 return repoVersions, nil 1202 } 1203 1204 path := rr.ModulePath() 1205 sort.Slice(versions, func(i, j int) bool { 1206 return gover.ModCompare(path, versions[i], versions[j]) < 0 1207 }) 1208 str.Uniq(&versions) 1209 return &modfetch.Versions{List: versions}, nil 1210 } 1211 1212 func (rr *replacementRepo) Stat(ctx context.Context, rev string) (*modfetch.RevInfo, error) { 1213 info, err := rr.repo.Stat(ctx, rev) 1214 if err == nil { 1215 return info, err 1216 } 1217 var hasReplacements bool 1218 for _, v := range MainModules.Versions() { 1219 if index := MainModules.Index(v); index != nil && len(index.replace) > 0 { 1220 hasReplacements = true 1221 } 1222 } 1223 if !hasReplacements { 1224 return info, err 1225 } 1226 1227 v := module.CanonicalVersion(rev) 1228 if v != rev { 1229 // The replacements in the go.mod file list only canonical semantic versions, 1230 // so a non-canonical version can't possibly have a replacement. 1231 return info, err 1232 } 1233 1234 path := rr.ModulePath() 1235 _, pathMajor, ok := module.SplitPathVersion(path) 1236 if ok && pathMajor == "" { 1237 if err := module.CheckPathMajor(v, pathMajor); err != nil && semver.Build(v) == "" { 1238 v += "+incompatible" 1239 } 1240 } 1241 1242 if r := Replacement(module.Version{Path: path, Version: v}); r.Path == "" { 1243 return info, err 1244 } 1245 return rr.replacementStat(v) 1246 } 1247 1248 func (rr *replacementRepo) Latest(ctx context.Context) (*modfetch.RevInfo, error) { 1249 info, err := rr.repo.Latest(ctx) 1250 path := rr.ModulePath() 1251 1252 if v, ok := MainModules.HighestReplaced()[path]; ok { 1253 if v == "" { 1254 // The only replacement is a wildcard that doesn't specify a version, so 1255 // synthesize a pseudo-version with an appropriate major version and a 1256 // timestamp below any real timestamp. That way, if the main module is 1257 // used from within some other module, the user will be able to upgrade 1258 // the requirement to any real version they choose. 1259 if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 { 1260 v = module.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000") 1261 } else { 1262 v = module.PseudoVersion("v0", "", time.Time{}, "000000000000") 1263 } 1264 } 1265 1266 if err != nil || gover.ModCompare(path, v, info.Version) > 0 { 1267 return rr.replacementStat(v) 1268 } 1269 } 1270 1271 return info, err 1272 } 1273 1274 func (rr *replacementRepo) replacementStat(v string) (*modfetch.RevInfo, error) { 1275 rev := &modfetch.RevInfo{Version: v} 1276 if module.IsPseudoVersion(v) { 1277 rev.Time, _ = module.PseudoVersionTime(v) 1278 rev.Short, _ = module.PseudoVersionRev(v) 1279 } 1280 return rev, nil 1281 } 1282 1283 // A QueryMatchesMainModulesError indicates that a query requests 1284 // a version of the main module that cannot be satisfied. 1285 // (The main module's version cannot be changed.) 1286 type QueryMatchesMainModulesError struct { 1287 MainModules []module.Version 1288 Pattern string 1289 Query string 1290 } 1291 1292 func (e *QueryMatchesMainModulesError) Error() string { 1293 if MainModules.Contains(e.Pattern) { 1294 return fmt.Sprintf("can't request version %q of the main module (%s)", e.Query, e.Pattern) 1295 } 1296 1297 plural := "" 1298 mainModulePaths := make([]string, len(e.MainModules)) 1299 for i := range e.MainModules { 1300 mainModulePaths[i] = e.MainModules[i].Path 1301 } 1302 if len(e.MainModules) > 1 { 1303 plural = "s" 1304 } 1305 return fmt.Sprintf("can't request version %q of pattern %q that includes the main module%s (%s)", e.Query, e.Pattern, plural, strings.Join(mainModulePaths, ", ")) 1306 } 1307 1308 // A QueryUpgradesAllError indicates that a query requests 1309 // an upgrade on the all pattern. 1310 // (The main module's version cannot be changed.) 1311 type QueryUpgradesAllError struct { 1312 MainModules []module.Version 1313 Query string 1314 } 1315 1316 func (e *QueryUpgradesAllError) Error() string { 1317 var plural string = "" 1318 if len(e.MainModules) != 1 { 1319 plural = "s" 1320 } 1321 1322 return fmt.Sprintf("can't request version %q of pattern \"all\" that includes the main module%s", e.Query, plural) 1323 } 1324 1325 // A QueryMatchesPackagesInMainModuleError indicates that a query cannot be 1326 // satisfied because it matches one or more packages found in the main module. 1327 type QueryMatchesPackagesInMainModuleError struct { 1328 Pattern string 1329 Query string 1330 Packages []string 1331 } 1332 1333 func (e *QueryMatchesPackagesInMainModuleError) Error() string { 1334 if len(e.Packages) > 1 { 1335 return fmt.Sprintf("pattern %s matches %d packages in the main module, so can't request version %s", e.Pattern, len(e.Packages), e.Query) 1336 } 1337 1338 if search.IsMetaPackage(e.Pattern) || strings.Contains(e.Pattern, "...") { 1339 return fmt.Sprintf("pattern %s matches package %s in the main module, so can't request version %s", e.Pattern, e.Packages[0], e.Query) 1340 } 1341 1342 return fmt.Sprintf("package %s is in the main module, so can't request version %s", e.Packages[0], e.Query) 1343 }