...

Source file src/cuelang.org/go/internal/mod/modload/update.go

Documentation: cuelang.org/go/internal/mod/modload

     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(ctx, fsys, modRoot)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  	rs := modrequirements.NewRequirements(mf.Module, 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.Module, 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.Module, 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  }
   176  

View as plain text