...

Source file src/pkg/cmd/go/internal/modload/query.go

     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		"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	// Query looks up a revision of a given module given a version query string.
    24	// The module must be a complete module path.
    25	// The version must take one of the following forms:
    26	//
    27	// - the literal string "latest", denoting the latest available, allowed
    28	//   tagged version, with non-prereleases preferred over prereleases.
    29	//   If there are no tagged versions in the repo, latest returns the most
    30	//   recent commit.
    31	// - the literal string "upgrade", equivalent to "latest" except that if
    32	//   current is a newer version, current will be returned (see below).
    33	// - the literal string "patch", denoting the latest available tagged version
    34	//   with the same major and minor number as current (see below).
    35	// - v1, denoting the latest available tagged version v1.x.x.
    36	// - v1.2, denoting the latest available tagged version v1.2.x.
    37	// - v1.2.3, a semantic version string denoting that tagged version.
    38	// - <v1.2.3, <=v1.2.3, >v1.2.3, >=v1.2.3,
    39	//   denoting the version closest to the target and satisfying the given operator,
    40	//   with non-prereleases preferred over prereleases.
    41	// - a repository commit identifier or tag, denoting that commit.
    42	//
    43	// current denotes the current version of the module; it may be "" if the
    44	// current version is unknown or should not be considered. If query is
    45	// "upgrade" or "patch", current will be returned if it is a newer
    46	// semantic version or a chronologically later pseudo-version than the
    47	// version that would otherwise be chosen. This prevents accidental downgrades
    48	// from newer pre-release or development versions.
    49	//
    50	// If the allowed function is non-nil, Query excludes any versions for which
    51	// allowed returns false.
    52	//
    53	// If path is the path of the main module and the query is "latest",
    54	// Query returns Target.Version as the version.
    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		// Parse query to detect parse errors (and possibly handle query)
    73		// before any network I/O.
    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				// Refuse to say whether <=v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).
   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				// Refuse to say whether >v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).
   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			// Direct lookup of semantic version or commit identifier.
   155			//
   156			// If the identifier is not a canonical semver tag — including if it's a
   157			// semver tag with a +metadata suffix — then modfetch.Stat will populate
   158			// info.Version with a suitable pseudo-version.
   159			info, err := modfetch.Stat(proxy, path, query)
   160			if err != nil {
   161				queryErr := err
   162				// The full query doesn't correspond to a tag. If it is a semantic version
   163				// with a +metadata suffix, see if there is a tag without that suffix:
   164				// semantic versioning defines them to be equivalent.
   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		// Load versions and execute query.
   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			// For "upgrade" and "patch", make sure we don't accidentally downgrade
   212			// from a newer prerelease or from a chronologically newer pseudoversion.
   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			// Special case for "latest": if no tags match, use latest commit in repo,
   251			// provided it is not excluded.
   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	// isSemverPrefix reports whether v is a semantic version prefix: v1 or v1.2 (not v1.2.3).
   266	// The caller is assumed to have checked that semver.IsValid(v) is true.
   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	// matchSemverPrefix reports whether the shortened semantic version p
   284	// matches the full-width (non-shortened) semantic version v.
   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	// QueryPackage looks up the module(s) containing path at a revision matching
   296	// query. The results are sorted by module path length in descending order.
   297	//
   298	// If the package is in the main module, QueryPackage considers only the main
   299	// module and only the version "latest", without checking for other possible
   300	// modules.
   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	// QueryPattern looks up the module(s) containing at least one package matching
   309	// the given pattern at the given version. The results are sorted by module path
   310	// length in descending order.
   311	//
   312	// QueryPattern queries modules with package paths up to the first "..."
   313	// in the pattern. For the pattern "example.com/a/b.../c", QueryPattern would
   314	// consider prefixes of "example.com/a". If multiple modules have versions
   315	// that match the query and packages that match the pattern, QueryPattern
   316	// picks the one with the longest module path.
   317	//
   318	// If any matching package is in the main module, QueryPattern considers only
   319	// the main module and only the version "latest", without checking for other
   320	// possible modules.
   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	// modulePrefixesExcludingTarget returns all prefixes of path that may plausibly
   402	// exist as a module, excluding targetPrefix but otherwise including path
   403	// itself, sorted by descending length.
   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		// If the path we're attempting is not in the module cache and we don't have a
   431		// fetch result cached either, we'll end up making a (potentially slow)
   432		// request to the proxy or (often even slower) the origin server.
   433		// To minimize latency, execute all of those requests in parallel.
   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		// Classify the results. In case of failure, identify the error that the user
   450		// is most likely to find helpful: the most useful class of error at the
   451		// longest matching path.
   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						// golang.org/issue/34094: If we have already found a module that
   477						// could potentially contain the target package, ignore unclassified
   478						// errors for modules with shorter paths.
   479	
   480						// golang.org/issue/34383 is a special case of this: if we have
   481						// already found example.com/foo/v2@v2.0.0 with a matching go.mod
   482						// file, ignore the error from example.com/foo@v2.0.0.
   483					} else {
   484						err = r.err
   485					}
   486				}
   487			}
   488		}
   489	
   490		// TODO(#26232): If len(found) == 0 and some of the errors are 4xx HTTP
   491		// codes, have the auth package recheck the failed paths.
   492		// If we obtain new credentials for any of them, re-run the above loop.
   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	// A NoMatchingVersionError indicates that Query found a module at the requested
   511	// path, but not at any versions satisfying the query string and allow-function.
   512	//
   513	// NOTE: NoMatchingVersionError MUST NOT implement Is(os.ErrNotExist).
   514	//
   515	// If the module came from a proxy, that proxy had to return a successful status
   516	// code for the versions it knows about, and thus did not have the opportunity
   517	// to return a non-400 status code to suppress fallback.
   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	// A PackageNotInModuleError indicates that QueryPattern found a candidate
   531	// module at the requested version, but that module did not contain any packages
   532	// matching the requested pattern.
   533	//
   534	// NOTE: PackageNotInModuleError MUST NOT implement Is(os.ErrNotExist).
   535	//
   536	// If the module came from a proxy, that proxy had to return a successful status
   537	// code for the versions it knows about, and thus did not have the opportunity
   538	// to return a non-400 status code to suppress fallback.
   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	// ModuleHasRootPackage returns whether module m contains a package m.Path.
   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