cuelang.org/go@v0.13.0/internal/mod/modrequirements/requirements.go (about)

     1  package modrequirements
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"runtime"
     7  	"slices"
     8  	"sync"
     9  	"sync/atomic"
    10  
    11  	"cuelang.org/go/cue/ast"
    12  	"cuelang.org/go/internal/mod/mvs"
    13  	"cuelang.org/go/internal/mod/semver"
    14  	"cuelang.org/go/internal/par"
    15  	"cuelang.org/go/mod/module"
    16  )
    17  
    18  type majorVersionDefault struct {
    19  	version          string
    20  	explicitDefault  bool
    21  	ambiguousDefault bool
    22  }
    23  
    24  // Requirements holds a set of module requirements. It does not
    25  // initially load the full module graph, as that can be expensive.
    26  // Instead the [Registry.Graph] method can be used to lazily construct
    27  // that.
    28  type Requirements struct {
    29  	registry          Registry
    30  	mainModuleVersion module.Version
    31  
    32  	// rootModules is the set of root modules of the graph, sorted and capped to
    33  	// length. It may contain duplicates, and may contain multiple versions for a
    34  	// given module path. The root modules are the main module's direct requirements.
    35  	rootModules    []module.Version
    36  	maxRootVersion map[string]string
    37  
    38  	// origDefaultMajorVersions holds the original passed to New.
    39  	origDefaultMajorVersions map[string]string
    40  
    41  	// defaultMajorVersions is derived from the above information,
    42  	// also holding modules that have a default due to being unique
    43  	// in the roots.
    44  	defaultMajorVersions map[string]majorVersionDefault
    45  
    46  	graphOnce sync.Once // guards writes to (but not reads from) graph
    47  	graph     atomic.Pointer[cachedGraph]
    48  }
    49  
    50  // Registry holds the contents of a registry. It's expected that this will
    51  // cache any results that it returns.
    52  type Registry interface {
    53  	Requirements(ctx context.Context, m module.Version) ([]module.Version, error)
    54  }
    55  
    56  // A cachedGraph is a non-nil *ModuleGraph, together with any error discovered
    57  // while loading that graph.
    58  type cachedGraph struct {
    59  	mg  *ModuleGraph
    60  	err error // If err is non-nil, mg may be incomplete (but must still be non-nil).
    61  }
    62  
    63  // NewRequirements returns a new requirement set with the given root modules.
    64  // The dependencies of the roots will be loaded lazily from the given
    65  // Registry value at the first call to the Graph method.
    66  //
    67  // The rootModules slice must be sorted according to [module.Sort].
    68  //
    69  // The defaultMajorVersions slice holds the default major version for (major-version-less)
    70  // mdule paths, if any have been specified. For example {"foo.com/bar": "v0"} specifies
    71  // that the default major version for the module `foo.com/bar` is `v0`.
    72  //
    73  // The caller must not modify rootModules or defaultMajorVersions after passing
    74  // them to NewRequirements.
    75  func NewRequirements(mainModulePath string, reg Registry, rootModules []module.Version, defaultMajorVersions map[string]string) *Requirements {
    76  	mainModuleVersion := module.MustNewVersion(mainModulePath, "")
    77  	// TODO add direct, so we can tell which modules are directly used by the
    78  	// main module.
    79  	for i, v := range rootModules {
    80  		if v.Path() == mainModulePath {
    81  			panic(fmt.Sprintf("NewRequirements called with untrimmed build list: rootModules[%v] is a main module", i))
    82  		}
    83  		if !v.IsValid() {
    84  			panic("NewRequirements with invalid zero version")
    85  		}
    86  	}
    87  	rs := &Requirements{
    88  		registry:          reg,
    89  		mainModuleVersion: mainModuleVersion,
    90  		rootModules:       rootModules,
    91  		maxRootVersion:    make(map[string]string, len(rootModules)),
    92  	}
    93  	for i, m := range rootModules {
    94  		if i > 0 {
    95  			prev := rootModules[i-1]
    96  			if prev.Path() > m.Path() || (prev.Path() == m.Path() && semver.Compare(prev.Version(), m.Version()) > 0) {
    97  				panic(fmt.Sprintf("NewRequirements called with unsorted roots: %v", rootModules))
    98  			}
    99  		}
   100  		if v, ok := rs.maxRootVersion[m.Path()]; !ok || semver.Compare(v, m.Version()) < 0 {
   101  			rs.maxRootVersion[m.Path()] = m.Version()
   102  		}
   103  	}
   104  	rs.initDefaultMajorVersions(defaultMajorVersions)
   105  	return rs
   106  }
   107  
   108  // WithDefaultMajorVersions returns rs but with the given default major versions.
   109  // The caller should not modify the map after calling this method.
   110  func (rs *Requirements) WithDefaultMajorVersions(defaults map[string]string) *Requirements {
   111  	rs1 := &Requirements{
   112  		registry:          rs.registry,
   113  		mainModuleVersion: rs.mainModuleVersion,
   114  		rootModules:       rs.rootModules,
   115  		maxRootVersion:    rs.maxRootVersion,
   116  	}
   117  	// Initialize graph and graphOnce in rs1 to mimic their state in rs.
   118  	// We can't copy the sync.Once, so if it's already triggered, we'll
   119  	// run the Once with a no-op function to get the same effect.
   120  	rs1.graph.Store(rs.graph.Load())
   121  	if rs1.GraphIsLoaded() {
   122  		rs1.graphOnce.Do(func() {})
   123  	}
   124  	rs1.initDefaultMajorVersions(defaults)
   125  	return rs1
   126  }
   127  
   128  func (rs *Requirements) initDefaultMajorVersions(defaultMajorVersions map[string]string) {
   129  	rs.origDefaultMajorVersions = defaultMajorVersions
   130  	rs.defaultMajorVersions = make(map[string]majorVersionDefault)
   131  	for mpath, v := range defaultMajorVersions {
   132  		if _, _, ok := ast.SplitPackageVersion(mpath); ok {
   133  			panic(fmt.Sprintf("NewRequirements called with major version in defaultMajorVersions %q", mpath))
   134  		}
   135  		if semver.Major(v) != v {
   136  			panic(fmt.Sprintf("NewRequirements called with invalid major version %q for module %q", v, mpath))
   137  		}
   138  		rs.defaultMajorVersions[mpath] = majorVersionDefault{
   139  			version:         v,
   140  			explicitDefault: true,
   141  		}
   142  	}
   143  	// Add defaults for all modules that have exactly one major version
   144  	// and no existing default.
   145  	for _, m := range rs.rootModules {
   146  		if m.IsLocal() {
   147  			continue
   148  		}
   149  		mpath := m.BasePath()
   150  		d, ok := rs.defaultMajorVersions[mpath]
   151  		if !ok {
   152  			rs.defaultMajorVersions[mpath] = majorVersionDefault{
   153  				version: semver.Major(m.Version()),
   154  			}
   155  			continue
   156  		}
   157  		if d.explicitDefault {
   158  			continue
   159  		}
   160  		d.ambiguousDefault = true
   161  		rs.defaultMajorVersions[mpath] = d
   162  	}
   163  }
   164  
   165  // RootSelected returns the version of the root dependency with the given module
   166  // path, or the zero module.Version and ok=false if the module is not a root
   167  // dependency.
   168  func (rs *Requirements) RootSelected(mpath string) (version string, ok bool) {
   169  	if mpath == rs.mainModuleVersion.Path() {
   170  		return "", true
   171  	}
   172  	if v, ok := rs.maxRootVersion[mpath]; ok {
   173  		return v, true
   174  	}
   175  	return "", false
   176  }
   177  
   178  // DefaultMajorVersions returns the defaults that the requirements was
   179  // created with. The returned map should not be modified.
   180  func (rs *Requirements) DefaultMajorVersions() map[string]string {
   181  	return rs.origDefaultMajorVersions
   182  }
   183  
   184  type MajorVersionDefaultStatus byte
   185  
   186  const (
   187  	ExplicitDefault MajorVersionDefaultStatus = iota
   188  	NonExplicitDefault
   189  	NoDefault
   190  	AmbiguousDefault
   191  )
   192  
   193  // DefaultMajorVersion returns the default major version for the given
   194  // module path (which should not itself contain a major version).
   195  //
   196  //	It also returns information about the default.
   197  func (rs *Requirements) DefaultMajorVersion(mpath string) (string, MajorVersionDefaultStatus) {
   198  	d, ok := rs.defaultMajorVersions[mpath]
   199  	switch {
   200  	case !ok:
   201  		return "", NoDefault
   202  	case d.ambiguousDefault:
   203  		return "", AmbiguousDefault
   204  	case d.explicitDefault:
   205  		return d.version, ExplicitDefault
   206  	default:
   207  		return d.version, NonExplicitDefault
   208  	}
   209  }
   210  
   211  // rootModules returns the set of root modules of the graph, sorted and capped to
   212  // length. It may contain duplicates, and may contain multiple versions for a
   213  // given module path.
   214  func (rs *Requirements) RootModules() []module.Version {
   215  	return slices.Clip(rs.rootModules)
   216  }
   217  
   218  // Graph returns the graph of module requirements loaded from the current
   219  // root modules (as reported by RootModules).
   220  //
   221  // Graph always makes a best effort to load the requirement graph despite any
   222  // errors, and always returns a non-nil *ModuleGraph.
   223  //
   224  // If the requirements of any relevant module fail to load, Graph also
   225  // returns a non-nil error of type *mvs.BuildListError.
   226  func (rs *Requirements) Graph(ctx context.Context) (*ModuleGraph, error) {
   227  	rs.graphOnce.Do(func() {
   228  		mg, mgErr := rs.readModGraph(ctx)
   229  		rs.graph.Store(&cachedGraph{mg, mgErr})
   230  	})
   231  	cached := rs.graph.Load()
   232  	return cached.mg, cached.err
   233  }
   234  
   235  // GraphIsLoaded reports whether Graph has been called previously.
   236  func (rs *Requirements) GraphIsLoaded() bool {
   237  	return rs.graph.Load() != nil
   238  }
   239  
   240  // A ModuleGraph represents the complete graph of module dependencies
   241  // of a main module.
   242  //
   243  // If the main module supports module graph pruning, the graph does not include
   244  // transitive dependencies of non-root (implicit) dependencies.
   245  type ModuleGraph struct {
   246  	g *mvs.Graph[module.Version]
   247  
   248  	buildListOnce sync.Once
   249  	buildList     []module.Version
   250  }
   251  
   252  // cueModSummary returns a summary of the cue.mod/module.cue file for module m,
   253  // taking into account any replacements for m, exclusions of its dependencies,
   254  // and/or vendoring.
   255  //
   256  // m must be a version in the module graph, reachable from the Target module.
   257  // cueModSummary must not be called for the Target module
   258  // itself, as its requirements may change.
   259  //
   260  // The caller must not modify the returned summary.
   261  func (rs *Requirements) cueModSummary(ctx context.Context, m module.Version) (*modFileSummary, error) {
   262  	require, err := rs.registry.Requirements(ctx, m)
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	// TODO account for replacements, exclusions, etc.
   267  	return &modFileSummary{
   268  		module:  m,
   269  		require: require,
   270  	}, nil
   271  }
   272  
   273  type modFileSummary struct {
   274  	module  module.Version
   275  	require []module.Version
   276  }
   277  
   278  // readModGraph reads and returns the module dependency graph starting at the
   279  // given roots.
   280  //
   281  // readModGraph does not attempt to diagnose or update inconsistent roots.
   282  func (rs *Requirements) readModGraph(ctx context.Context) (*ModuleGraph, error) {
   283  	var (
   284  		mu       sync.Mutex // guards mg.g and hasError during loading
   285  		hasError bool
   286  		mg       = &ModuleGraph{
   287  			g: mvs.NewGraph[module.Version](module.Versions{}, cmpVersion, []module.Version{rs.mainModuleVersion}),
   288  		}
   289  	)
   290  
   291  	mg.g.Require(rs.mainModuleVersion, rs.rootModules)
   292  
   293  	var (
   294  		loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))
   295  		loading   sync.Map // module.Version → nil; the set of modules that have been or are being loaded
   296  		loadCache par.ErrCache[module.Version, *modFileSummary]
   297  	)
   298  
   299  	// loadOne synchronously loads the explicit requirements for module m.
   300  	// It does not load the transitive requirements of m.
   301  	loadOne := func(m module.Version) (*modFileSummary, error) {
   302  		return loadCache.Do(m, func() (*modFileSummary, error) {
   303  			summary, err := rs.cueModSummary(ctx, m)
   304  
   305  			mu.Lock()
   306  			if err == nil {
   307  				mg.g.Require(m, summary.require)
   308  			} else {
   309  				hasError = true
   310  			}
   311  			mu.Unlock()
   312  
   313  			return summary, err
   314  		})
   315  	}
   316  
   317  	for _, m := range rs.rootModules {
   318  		m := m
   319  		if !m.IsValid() {
   320  			panic("root module version is invalid")
   321  		}
   322  		if m.IsLocal() || m.Version() == "none" {
   323  			continue
   324  		}
   325  
   326  		if _, dup := loading.LoadOrStore(m, nil); dup {
   327  			// m has already been enqueued for loading. Since unpruned loading may
   328  			// follow cycles in the requirement graph, we need to return early
   329  			// to avoid making the load queue infinitely long.
   330  			continue
   331  		}
   332  
   333  		loadQueue.Add(func() {
   334  			loadOne(m)
   335  			// If there's an error, findError will report it later.
   336  		})
   337  	}
   338  	<-loadQueue.Idle()
   339  
   340  	if hasError {
   341  		return mg, mg.findError(&loadCache)
   342  	}
   343  	return mg, nil
   344  }
   345  
   346  // RequiredBy returns the dependencies required by module m in the graph,
   347  // or ok=false if module m's dependencies are pruned out.
   348  //
   349  // The caller must not modify the returned slice, but may safely append to it
   350  // and may rely on it not to be modified.
   351  func (mg *ModuleGraph) RequiredBy(m module.Version) (reqs []module.Version, ok bool) {
   352  	return mg.g.RequiredBy(m)
   353  }
   354  
   355  // Selected returns the selected version of the module with the given path.
   356  //
   357  // If no version is selected, Selected returns version "none".
   358  func (mg *ModuleGraph) Selected(path string) (version string) {
   359  	return mg.g.Selected(path)
   360  }
   361  
   362  // WalkBreadthFirst invokes f once, in breadth-first order, for each module
   363  // version other than "none" that appears in the graph, regardless of whether
   364  // that version is selected.
   365  func (mg *ModuleGraph) WalkBreadthFirst(f func(m module.Version)) {
   366  	mg.g.WalkBreadthFirst(f)
   367  }
   368  
   369  // BuildList returns the selected versions of all modules present in the graph,
   370  // beginning with the main modules.
   371  //
   372  // The order of the remaining elements in the list is deterministic
   373  // but arbitrary.
   374  //
   375  // The caller must not modify the returned list, but may safely append to it
   376  // and may rely on it not to be modified.
   377  func (mg *ModuleGraph) BuildList() []module.Version {
   378  	mg.buildListOnce.Do(func() {
   379  		mg.buildList = slices.Clip(mg.g.BuildList())
   380  	})
   381  	return mg.buildList
   382  }
   383  
   384  func (mg *ModuleGraph) findError(loadCache *par.ErrCache[module.Version, *modFileSummary]) error {
   385  	errStack := mg.g.FindPath(func(m module.Version) bool {
   386  		_, err := loadCache.Get(m)
   387  		return err != nil && err != par.ErrCacheEntryNotFound
   388  	})
   389  	if len(errStack) > 0 {
   390  		// TODO it seems that this stack can never be more than one
   391  		// element long, becasuse readModGraph never goes more
   392  		// than one depth level below the root requirements.
   393  		// Given that the top of the stack will always be the main
   394  		// module and that BuildListError elides the first element
   395  		// in this case, is it really worth using FindPath?
   396  		_, err := loadCache.Get(errStack[len(errStack)-1])
   397  		var noUpgrade func(from, to module.Version) bool
   398  		return mvs.NewBuildListError[module.Version](err, errStack, module.Versions{}, noUpgrade)
   399  	}
   400  
   401  	return nil
   402  }
   403  
   404  // cmpVersion implements the comparison for versions in the module loader.
   405  //
   406  // It is consistent with semver.Compare except that as a special case,
   407  // the version "" is considered higher than all other versions.
   408  // The main module (also known as the target) has no version and must be chosen
   409  // over other versions of the same module in the module dependency graph.
   410  func cmpVersion(v1, v2 string) int {
   411  	if v2 == "" {
   412  		if v1 == "" {
   413  			return 0
   414  		}
   415  		return -1
   416  	}
   417  	if v1 == "" {
   418  		return 1
   419  	}
   420  	return semver.Compare(v1, v2)
   421  }