1package modload
2
3import (
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.
25func (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.
58func (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.
120func 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}