cuelang.org/go@v0.13.0/internal/mod/modload/query.go (about)

     1  package modload
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path"
     7  	"runtime"
     8  	"sync"
     9  
    10  	"cuelang.org/go/cue/ast"
    11  	"cuelang.org/go/internal/mod/modpkgload"
    12  	"cuelang.org/go/internal/mod/modrequirements"
    13  	"cuelang.org/go/internal/mod/semver"
    14  	"cuelang.org/go/internal/par"
    15  	"cuelang.org/go/mod/module"
    16  )
    17  
    18  // queryImport attempts to locate a module that can be added to the
    19  // current build list to provide the package with the given import path.
    20  // It also reports whether a default major version will be required
    21  // to select the candidates (this will be true if pkgPath lacks
    22  // a major version).
    23  //
    24  // It avoids results that are already in the given requirements.
    25  func (ld *loader) queryImport(ctx context.Context, pkgPath string, rs *modrequirements.Requirements) (candidates []module.Version, needsDefault bool, err error) {
    26  	if modpkgload.IsStdlibPackage(pkgPath) {
    27  		// This package isn't in the standard library and isn't in any module already
    28  		// in the build list.
    29  		//
    30  		// Moreover, the import path is reserved for the standard library, so
    31  		// QueryPattern cannot possibly find a module containing this package.
    32  		//
    33  		// Instead of trying QueryPattern, report an ImportMissingError immediately.
    34  		return nil, false, &modpkgload.ImportMissingError{Path: pkgPath}
    35  	}
    36  
    37  	// Look up module containing the package, for addition to the build list.
    38  	// Goal is to determine the module, download it to dir,
    39  	// and return m, dir, ImportMissingError.
    40  
    41  	// TODO this should probably be a non-debug log message.
    42  	logf("cue: finding module for package %s", pkgPath)
    43  
    44  	candidates, needsDefault, err = ld.queryLatestModules(ctx, pkgPath, rs)
    45  	if err != nil {
    46  		return nil, false, err
    47  	}
    48  	if len(candidates) == 0 {
    49  		return nil, false, fmt.Errorf("%v", &modpkgload.ImportMissingError{Path: pkgPath})
    50  	}
    51  	return candidates, needsDefault, nil
    52  }
    53  
    54  // queryLatestModules looks for potential modules that might contain the given
    55  // package by looking for the latest module version of all viable prefixes of pkgPath.
    56  // It does not return modules that are already present in the given requirements.
    57  // It also reports whether a default major version will be required.
    58  func (ld *loader) queryLatestModules(ctx context.Context, pkgPath string, rs *modrequirements.Requirements) ([]module.Version, bool, error) {
    59  	parts := ast.ParseImportPath(pkgPath)
    60  	latestModuleForPrefix := func(prefix string) (module.Version, error) {
    61  		mv := parts.Version
    62  		if mv == "" {
    63  			var status modrequirements.MajorVersionDefaultStatus
    64  			mv, status = rs.DefaultMajorVersion(prefix)
    65  			if status == modrequirements.AmbiguousDefault {
    66  				// There are already multiple possibilities and
    67  				// we don't have any way of choosing one.
    68  				return module.Version{}, nil
    69  			}
    70  		}
    71  		mpath := prefix
    72  		if mv != "" {
    73  			mpath = prefix + "@" + mv
    74  			if _, ok := rs.RootSelected(mpath); ok {
    75  				// Already present in current requirements.
    76  				return module.Version{}, nil
    77  			}
    78  		}
    79  
    80  		versions, err := ld.registry.ModuleVersions(ctx, mpath)
    81  		logf("getting module versions for %q (prefix %q) -> %q, %v", mpath, prefix, versions, err)
    82  		if err != nil {
    83  			return module.Version{}, err
    84  		}
    85  		logf("-> %q", versions)
    86  		if v := LatestVersion(versions); v != "" {
    87  			return module.NewVersion(prefix, v)
    88  		}
    89  		return module.Version{}, nil
    90  	}
    91  	work := par.NewQueue(runtime.GOMAXPROCS(0))
    92  	var (
    93  		mu         sync.Mutex
    94  		candidates []module.Version
    95  		queryErr   error
    96  	)
    97  	logf("initial module path %q", parts.Path)
    98  	for prefix := parts.Path; prefix != "."; prefix = path.Dir(prefix) {
    99  		work.Add(func() {
   100  			v, err := latestModuleForPrefix(prefix)
   101  			mu.Lock()
   102  			defer mu.Unlock()
   103  			if err != nil {
   104  				if queryErr == nil {
   105  					queryErr = err
   106  				}
   107  				return
   108  			}
   109  			if v.IsValid() {
   110  				candidates = append(candidates, v)
   111  			}
   112  		})
   113  	}
   114  	<-work.Idle()
   115  	return candidates, parts.Version == "", queryErr
   116  }
   117  
   118  // LatestVersion returns the latest of any of the given versions,
   119  // ignoring prerelease versions if there is any stable version.
   120  func LatestVersion(versions []string) string {
   121  	maxStable := ""
   122  	maxAny := ""
   123  	for _, v := range versions {
   124  		if semver.Prerelease(v) == "" && (maxStable == "" || semver.Compare(v, maxStable) > 0) {
   125  			maxStable = v
   126  		}
   127  		if maxAny == "" || semver.Compare(v, maxAny) > 0 {
   128  			maxAny = v
   129  		}
   130  	}
   131  	if maxStable != "" {
   132  		return maxStable
   133  	}
   134  	return maxAny
   135  }