diff options
Diffstat (limited to 'libgo/go/cmd/go/internal/modload/query.go')
-rw-r--r-- | libgo/go/cmd/go/internal/modload/query.go | 181 |
1 files changed, 147 insertions, 34 deletions
diff --git a/libgo/go/cmd/go/internal/modload/query.go b/libgo/go/cmd/go/internal/modload/query.go index 602bf47275d..53278b91002 100644 --- a/libgo/go/cmd/go/internal/modload/query.go +++ b/libgo/go/cmd/go/internal/modload/query.go @@ -9,15 +9,18 @@ import ( "fmt" "os" pathpkg "path" + "path/filepath" "strings" "sync" + "cmd/go/internal/cfg" "cmd/go/internal/imports" "cmd/go/internal/modfetch" - "cmd/go/internal/module" "cmd/go/internal/search" - "cmd/go/internal/semver" "cmd/go/internal/str" + + "golang.org/x/mod/module" + "golang.org/x/mod/semver" ) // Query looks up a revision of a given module given a version query string. @@ -61,10 +64,24 @@ func Query(path, query, current string, allowed func(module.Version) bool) (*mod return info, err } +var errQueryDisabled error = queryDisabledError{} + +type queryDisabledError struct{} + +func (queryDisabledError) Error() string { + if cfg.BuildModReason == "" { + return fmt.Sprintf("cannot query module due to -mod=%s", cfg.BuildMod) + } + return fmt.Sprintf("cannot query module due to -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason) +} + func queryProxy(proxy, path, query, current string, allowed func(module.Version) bool) (*modfetch.RevInfo, error) { if current != "" && !semver.IsValid(current) { return nil, fmt.Errorf("invalid previous version %q", current) } + if cfg.BuildMod != "" && cfg.BuildMod != "mod" { + return nil, errQueryDisabled + } if allowed == nil { allowed = func(module.Version) bool { return true } } @@ -74,10 +91,20 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version) badVersion := func(v string) (*modfetch.RevInfo, error) { return nil, fmt.Errorf("invalid semantic version %q in range %q", v, query) } - var ok func(module.Version) bool - var prefix string - var preferOlder bool - var mayUseLatest bool + matchesMajor := func(v string) bool { + _, pathMajor, ok := module.SplitPathVersion(path) + if !ok { + return false + } + return module.CheckPathMajor(v, pathMajor) == nil + } + var ( + ok func(module.Version) bool + prefix string + preferOlder bool + mayUseLatest bool + preferIncompatible bool = strings.HasSuffix(current, "+incompatible") + ) switch { case query == "latest": ok = allowed @@ -110,6 +137,9 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version) ok = func(m module.Version) bool { return semver.Compare(m.Version, v) <= 0 && allowed(m) } + if !matchesMajor(v) { + preferIncompatible = true + } case strings.HasPrefix(query, "<"): v := query[len("<"):] @@ -119,6 +149,9 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version) ok = func(m module.Version) bool { return semver.Compare(m.Version, v) < 0 && allowed(m) } + if !matchesMajor(v) { + preferIncompatible = true + } case strings.HasPrefix(query, ">="): v := query[len(">="):] @@ -129,6 +162,9 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version) return semver.Compare(m.Version, v) >= 0 && allowed(m) } preferOlder = true + if !matchesMajor(v) { + preferIncompatible = true + } case strings.HasPrefix(query, ">"): v := query[len(">"):] @@ -143,12 +179,18 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version) return semver.Compare(m.Version, v) > 0 && allowed(m) } preferOlder = true + if !matchesMajor(v) { + preferIncompatible = true + } case semver.IsValid(query) && isSemverPrefix(query): ok = func(m module.Version) bool { return matchSemverPrefix(query, m.Version) && allowed(m) } prefix = query + "." + if !matchesMajor(query) { + preferIncompatible = true + } default: // Direct lookup of semantic version or commit identifier. @@ -201,6 +243,10 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version) if err != nil { return nil, err } + releases, prereleases, err := filterVersions(path, versions, ok, preferIncompatible) + if err != nil { + return nil, err + } lookup := func(v string) (*modfetch.RevInfo, error) { rev, err := repo.Stat(v) @@ -221,28 +267,18 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version) } if preferOlder { - for _, v := range versions { - if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) { - return lookup(v) - } + if len(releases) > 0 { + return lookup(releases[0]) } - for _, v := range versions { - if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) { - return lookup(v) - } + if len(prereleases) > 0 { + return lookup(prereleases[0]) } } else { - for i := len(versions) - 1; i >= 0; i-- { - v := versions[i] - if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) { - return lookup(v) - } + if len(releases) > 0 { + return lookup(releases[len(releases)-1]) } - for i := len(versions) - 1; i >= 0; i-- { - v := versions[i] - if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) { - return lookup(v) - } + if len(prereleases) > 0 { + return lookup(prereleases[len(prereleases)-1]) } } @@ -286,6 +322,52 @@ func matchSemverPrefix(p, v string) bool { return len(v) > len(p) && v[len(p)] == '.' && v[:len(p)] == p && semver.Prerelease(v) == "" } +// filterVersions classifies versions into releases and pre-releases, filtering +// out: +// 1. versions that do not satisfy the 'ok' predicate, and +// 2. "+incompatible" versions, if a compatible one satisfies the predicate +// and the incompatible version is not preferred. +func filterVersions(path string, versions []string, ok func(module.Version) bool, preferIncompatible bool) (releases, prereleases []string, err error) { + var lastCompatible string + for _, v := range versions { + if !ok(module.Version{Path: path, Version: v}) { + continue + } + + if !preferIncompatible { + if !strings.HasSuffix(v, "+incompatible") { + lastCompatible = v + } else if lastCompatible != "" { + // If the latest compatible version is allowed and has a go.mod file, + // ignore any version with a higher (+incompatible) major version. (See + // https://golang.org/issue/34165.) Note that we even prefer a + // compatible pre-release over an incompatible release. + + ok, err := versionHasGoMod(module.Version{Path: path, Version: lastCompatible}) + if err != nil { + return nil, nil, err + } + if ok { + break + } + + // No acceptable compatible release has a go.mod file, so the versioning + // for the module might not be module-aware, and we should respect + // legacy major-version tags. + preferIncompatible = true + } + } + + if semver.Prerelease(v) != "" { + prereleases = append(prereleases, v) + } else { + releases = append(releases, v) + } + } + + return releases, prereleases, nil +} + type QueryResult struct { Mod module.Version Rev *modfetch.RevInfo @@ -381,9 +463,10 @@ func QueryPattern(pattern, query string, allowed func(module.Version) bool) ([]Q r.Packages = match(r.Mod, root, isLocal) if len(r.Packages) == 0 { return r, &PackageNotInModuleError{ - Mod: r.Mod, - Query: query, - Pattern: pattern, + Mod: r.Mod, + Replacement: Replacement(r.Mod), + Query: query, + Pattern: pattern, } } return r, nil @@ -471,7 +554,17 @@ func queryPrefixModules(candidateModules []string, queryModule func(path string) notExistErr = rErr } } else if err == nil { - err = r.err + if len(found) > 0 || noPackage != nil { + // golang.org/issue/34094: If we have already found a module that + // could potentially contain the target package, ignore unclassified + // errors for modules with shorter paths. + + // golang.org/issue/34383 is a special case of this: if we have + // already found example.com/foo/v2@v2.0.0 with a matching go.mod + // file, ignore the error from example.com/foo@v2.0.0. + } else { + err = r.err + } } } } @@ -526,21 +619,32 @@ func (e *NoMatchingVersionError) Error() string { // code for the versions it knows about, and thus did not have the opportunity // to return a non-400 status code to suppress fallback. type PackageNotInModuleError struct { - Mod module.Version - Query string - Pattern string + Mod module.Version + Replacement module.Version + Query string + Pattern string } func (e *PackageNotInModuleError) Error() string { found := "" - if e.Query != e.Mod.Version { + if r := e.Replacement; r.Path != "" { + replacement := r.Path + if r.Version != "" { + replacement = fmt.Sprintf("%s@%s", r.Path, r.Version) + } + if e.Query == e.Mod.Version { + found = fmt.Sprintf(" (replaced by %s)", replacement) + } else { + found = fmt.Sprintf(" (%s, replaced by %s)", e.Mod.Version, replacement) + } + } else if e.Query != e.Mod.Version { found = fmt.Sprintf(" (%s)", e.Mod.Version) } if strings.Contains(e.Pattern, "...") { - return fmt.Sprintf("module %s@%s%s found, but does not contain packages matching %s", e.Mod.Path, e.Query, found, e.Pattern) + return fmt.Sprintf("module %s@%s found%s, but does not contain packages matching %s", e.Mod.Path, e.Query, found, e.Pattern) } - return fmt.Sprintf("module %s@%s%s found, but does not contain package %s", e.Mod.Path, e.Query, found, e.Pattern) + return fmt.Sprintf("module %s@%s found%s, but does not contain package %s", e.Mod.Path, e.Query, found, e.Pattern) } // ModuleHasRootPackage returns whether module m contains a package m.Path. @@ -552,3 +656,12 @@ func ModuleHasRootPackage(m module.Version) (bool, error) { _, ok := dirInModule(m.Path, m.Path, root, isLocal) return ok, nil } + +func versionHasGoMod(m module.Version) (bool, error) { + root, _, err := fetch(m) + if err != nil { + return false, err + } + fi, err := os.Stat(filepath.Join(root, "go.mod")) + return err == nil && !fi.IsDir(), nil +} |