1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
package modload
import (
"context"
"fmt"
"io/fs"
"runtime"
"strings"
"sync/atomic"
"cuelang.org/go/internal/mod/modrequirements"
"cuelang.org/go/internal/mod/semver"
"cuelang.org/go/internal/par"
"cuelang.org/go/mod/modfile"
"cuelang.org/go/mod/module"
)
// UpdateVersions returns the main module's module file with the specified module versions
// updated if possible and added if not already present. It returns an error if asked
// to downgrade a module below a version already required by an external dependency.
//
// A module in the versions slice can be specified as one of the following:
// - $module@$fullVersion: a specific exact version
// - $module@$partialVersion: a non-canonical version
// specifies the latest version that has the same major/minor numbers.
// - $module@latest: the latest non-prerelease version, or latest prerelease version if
// there is no non-prerelease version
// - $module: equivalent to $module@latest if $module doesn't have a default major
// version or $module@$majorVersion if it does, where $majorVersion is the
// default major version for $module.
func UpdateVersions(ctx context.Context, fsys fs.FS, modRoot string, reg Registry, versions []string) (*modfile.File, error) {
mainModuleVersion, mf, err := readModuleFile(fsys, modRoot)
if err != nil {
return nil, err
}
rs := modrequirements.NewRequirements(mf.QualifiedModule(), reg, mf.DepVersions(), mf.DefaultMajorVersions())
mversions, err := resolveUpdateVersions(ctx, reg, rs, mainModuleVersion, versions)
if err != nil {
return nil, err
}
// Now we know what versions we want to update to, make a new set of
// requirements with these versions in place.
mversionsMap := make(map[string]module.Version)
for _, v := range mversions {
// Check existing membership of the map: if the same module has been specified
// twice, then choose t
if v1, ok := mversionsMap[v.Path()]; ok && v1.Version() != v.Version() {
// The same module has been specified twice with different requirements.
// Treat it as an error (an alternative approach might be to choose the greater
// version, but making it an error seems more appropriate to the "choose exact
// version" semantics of UpdateVersions.
return nil, fmt.Errorf("conflicting version update requirements %v vs %v", v1, v)
}
mversionsMap[v.Path()] = v
}
g, err := rs.Graph(ctx)
if err != nil {
return nil, fmt.Errorf("cannot determine module graph: %v", err)
}
var newVersions []module.Version
for _, v := range g.BuildList() {
if v.Path() == mainModuleVersion.Path() {
continue
}
if newv, ok := mversionsMap[v.Path()]; ok {
newVersions = append(newVersions, newv)
delete(mversionsMap, v.Path())
} else {
newVersions = append(newVersions, v)
}
}
for _, v := range mversionsMap {
newVersions = append(newVersions, v)
}
module.Sort(newVersions)
rs = modrequirements.NewRequirements(mf.QualifiedModule(), reg, newVersions, mf.DefaultMajorVersions())
g, err = rs.Graph(ctx)
if err != nil {
return nil, fmt.Errorf("cannot determine new module graph: %v", err)
}
// Now check that the resulting versions are the ones we wanted.
for _, v := range mversions {
actualVers := g.Selected(v.Path())
if actualVers != v.Version() {
return nil, fmt.Errorf("other requirements prevent changing module %v to version %v (actual selected version: %v)", v.Path(), v.Version(), actualVers)
}
}
// Make a new requirements with the selected versions of the above as roots.
var finalVersions []module.Version
for _, v := range g.BuildList() {
if v.Path() != mainModuleVersion.Path() {
finalVersions = append(finalVersions, v)
}
}
rs = modrequirements.NewRequirements(mf.QualifiedModule(), reg, finalVersions, mf.DefaultMajorVersions())
return modfileFromRequirements(mf, rs), nil
}
// resolveUpdateVersions resolves a set of version strings as accepted by [UpdateVersions]
// into the actual module versions they represent.
func resolveUpdateVersions(ctx context.Context, reg Registry, rs *modrequirements.Requirements, mainModuleVersion module.Version, versions []string) ([]module.Version, error) {
work := par.NewQueue(runtime.GOMAXPROCS(0))
mversions := make([]module.Version, len(versions))
var queryErr atomic.Pointer[error]
setError := func(err error) {
queryErr.CompareAndSwap(nil, &err)
}
for i, v := range versions {
i, v := i, v
if mv, err := module.ParseVersion(v); err == nil {
// It's already canonical: nothing more to do.
mversions[i] = mv
continue
}
mpath, vers, ok := strings.Cut(v, "@")
if !ok {
if major, status := rs.DefaultMajorVersion(mpath); status == modrequirements.ExplicitDefault {
// TODO allow a non-explicit default too?
vers = major
} else {
vers = "latest"
}
}
if err := module.CheckPathWithoutVersion(mpath); err != nil {
return nil, fmt.Errorf("invalid module path in %q", v)
}
versionPrefix := ""
if vers != "latest" {
if !semver.IsValid(vers) {
return nil, fmt.Errorf("%q does not specify a valid semantic version", v)
}
if semver.Build(vers) != "" {
return nil, fmt.Errorf("build version suffixes not supported (%v)", v)
}
// It's a valid version but has no build suffix and it's not canonical,
// which means it must be either a major-only or major-minor, so
// the conforming canonical versions must have it as a prefix, with
// a dot separating the last component and the next.
versionPrefix = vers + "."
}
work.Add(func() {
allVersions, err := reg.ModuleVersions(ctx, mpath)
if err != nil {
setError(err)
return
}
possibleVersions := make([]string, 0, len(allVersions))
for _, v := range allVersions {
if strings.HasPrefix(v, versionPrefix) {
possibleVersions = append(possibleVersions, v)
}
}
if len(possibleVersions) == 0 {
setError(fmt.Errorf("no versions found for module %s", v))
return
}
chosen := latestVersion(possibleVersions)
mv, err := module.NewVersion(mpath, chosen)
if err != nil {
// Should never happen, because we've checked that
// mpath is valid and ModuleVersions
// should always return valid semver versions.
setError(err)
return
}
mversions[i] = mv
})
}
<-work.Idle()
if errPtr := queryErr.Load(); errPtr != nil {
return nil, *errPtr
}
for _, v := range mversions {
if v.Path() == mainModuleVersion.Path() {
return nil, fmt.Errorf("cannot update version of main module")
}
}
return mversions, nil
}
|