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