github.com/sirkon/goproxy@v1.4.8/internal/mvs/mvs.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package mvs implements Minimal Version Selection.
     6  // See https://research.swtch.com/vgo-mvs.
     7  package mvs
     8  
     9  import (
    10  	"fmt"
    11  	"sort"
    12  	"sync"
    13  
    14  	"github.com/sirkon/goproxy/internal/base"
    15  	"github.com/sirkon/goproxy/internal/module"
    16  	"github.com/sirkon/goproxy/internal/par"
    17  )
    18  
    19  // A Reqs is the requirement graph on which Minimal Version Selection (MVS) operates.
    20  //
    21  // The version strings are opaque except for the special version "none"
    22  // (see the documentation for module.Version). In particular, MVS does not
    23  // assume that the version strings are semantic versions; instead, the Max method
    24  // gives access to the comparison operation.
    25  //
    26  // It must be safe to call methods on a Reqs from multiple goroutines simultaneously.
    27  // Because a Reqs may read the underlying graph from the network on demand,
    28  // the MVS algorithms parallelize the traversal to overlap network delays.
    29  type Reqs interface {
    30  	// Required returns the module versions explicitly required by m itself.
    31  	// The caller must not modify the returned list.
    32  	Required(m module.Version) ([]module.Version, error)
    33  
    34  	// Max returns the maximum of v1 and v2 (it returns either v1 or v2).
    35  	//
    36  	// For all versions v, Max(v, "none") must be v,
    37  	// and for the tanget passed as the first argument to MVS functions,
    38  	// Max(target, v) must be target.
    39  	//
    40  	// Note that v1 < v2 can be written Max(v1, v2) != v1
    41  	// and similarly v1 <= v2 can be written Max(v1, v2) == v2.
    42  	Max(v1, v2 string) string
    43  
    44  	// Upgrade returns the upgraded version of m,
    45  	// for use during an UpgradeAll operation.
    46  	// If m should be kept as is, Upgrade returns m.
    47  	// If m is not yet used in the build, then m.Version will be "none".
    48  	// More typically, m.Version will be the version required
    49  	// by some other module in the build.
    50  	//
    51  	// If no module version is available for the given path,
    52  	// Upgrade returns a non-nil error.
    53  	// TODO(rsc): Upgrade must be able to return errors,
    54  	// but should "no latest version" just return m instead?
    55  	Upgrade(m module.Version) (module.Version, error)
    56  
    57  	// Previous returns the version of m.Path immediately prior to m.Version,
    58  	// or "none" if no such version is known.
    59  	Previous(m module.Version) (module.Version, error)
    60  }
    61  
    62  type MissingModuleError struct {
    63  	Module module.Version
    64  }
    65  
    66  func (e *MissingModuleError) Error() string {
    67  	return fmt.Sprintf("missing module: %v", e.Module)
    68  }
    69  
    70  // BuildList returns the build list for the target module.
    71  func BuildList(target module.Version, reqs Reqs) ([]module.Version, error) {
    72  	return buildList(target, reqs, nil)
    73  }
    74  
    75  func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) module.Version) ([]module.Version, error) {
    76  	// Explore work graph in parallel in case reqs.Required
    77  	// does high-latency network operations.
    78  	var work par.Work
    79  	work.Add(target)
    80  	var (
    81  		mu       sync.Mutex
    82  		min      = map[string]string{target.Path: target.Version}
    83  		firstErr error
    84  	)
    85  	work.Do(10, func(item interface{}) {
    86  		m := item.(module.Version)
    87  		required, err := reqs.Required(m)
    88  
    89  		mu.Lock()
    90  		if err != nil && firstErr == nil {
    91  			firstErr = err
    92  		}
    93  		if firstErr != nil {
    94  			mu.Unlock()
    95  			return
    96  		}
    97  		if v, ok := min[m.Path]; !ok || reqs.Max(v, m.Version) != v {
    98  			min[m.Path] = m.Version
    99  		}
   100  		mu.Unlock()
   101  
   102  		for _, r := range required {
   103  			if r.Path == "" {
   104  				base.Errorf("Required(%v) returned zero module in list", m)
   105  				continue
   106  			}
   107  			work.Add(r)
   108  		}
   109  
   110  		if upgrade != nil {
   111  			u := upgrade(m)
   112  			if u.Path == "" {
   113  				base.Errorf("Upgrade(%v) returned zero module", m)
   114  				return
   115  			}
   116  			work.Add(u)
   117  		}
   118  	})
   119  
   120  	if firstErr != nil {
   121  		return nil, firstErr
   122  	}
   123  	if v := min[target.Path]; v != target.Version {
   124  		panic(fmt.Sprintf("mistake: chose version %q instead of target %+v", v, target)) // TODO: Don't panic.
   125  	}
   126  
   127  	list := []module.Version{target}
   128  	listed := map[string]bool{target.Path: true}
   129  	for i := 0; i < len(list); i++ {
   130  		m := list[i]
   131  		required, err := reqs.Required(m)
   132  		if err != nil {
   133  			return nil, err
   134  		}
   135  		for _, r := range required {
   136  			v := min[r.Path]
   137  			if r.Path != target.Path && reqs.Max(v, r.Version) != v {
   138  				panic(fmt.Sprintf("mistake: version %q does not satisfy requirement %+v", v, r)) // TODO: Don't panic.
   139  			}
   140  			if !listed[r.Path] {
   141  				list = append(list, module.Version{Path: r.Path, Version: v})
   142  				listed[r.Path] = true
   143  			}
   144  		}
   145  	}
   146  
   147  	tail := list[1:]
   148  	sort.Slice(tail, func(i, j int) bool {
   149  		return tail[i].Path < tail[j].Path
   150  	})
   151  	return list, nil
   152  }
   153  
   154  // Req returns the minimal requirement list for the target module
   155  // that results in the given build list, with the constraint that all
   156  // module paths listed in base must appear in the returned list.
   157  func Req(target module.Version, list []module.Version, base []string, reqs Reqs) ([]module.Version, error) {
   158  	// Note: Not running in parallel because we assume
   159  	// that list came from a previous operation that paged
   160  	// in all the requirements, so there's no I/O to overlap now.
   161  
   162  	// Compute postorder, cache requirements.
   163  	var postorder []module.Version
   164  	reqCache := map[module.Version][]module.Version{}
   165  	reqCache[target] = nil
   166  	var walk func(module.Version) error
   167  	walk = func(m module.Version) error {
   168  		_, ok := reqCache[m]
   169  		if ok {
   170  			return nil
   171  		}
   172  		required, err := reqs.Required(m)
   173  		if err != nil {
   174  			return err
   175  		}
   176  		reqCache[m] = required
   177  		for _, m1 := range required {
   178  			if err := walk(m1); err != nil {
   179  				return err
   180  			}
   181  		}
   182  		postorder = append(postorder, m)
   183  		return nil
   184  	}
   185  	for _, m := range list {
   186  		if err := walk(m); err != nil {
   187  			return nil, err
   188  		}
   189  	}
   190  
   191  	// Walk modules in reverse post-order, only adding those not implied already.
   192  	have := map[string]string{}
   193  	walk = func(m module.Version) error {
   194  		if v, ok := have[m.Path]; ok && reqs.Max(m.Version, v) == v {
   195  			return nil
   196  		}
   197  		have[m.Path] = m.Version
   198  		for _, m1 := range reqCache[m] {
   199  			walk(m1)
   200  		}
   201  		return nil
   202  	}
   203  	max := map[string]string{}
   204  	for _, m := range list {
   205  		if v, ok := max[m.Path]; ok {
   206  			max[m.Path] = reqs.Max(m.Version, v)
   207  		} else {
   208  			max[m.Path] = m.Version
   209  		}
   210  	}
   211  	// First walk the base modules that must be listed.
   212  	var min []module.Version
   213  	for _, path := range base {
   214  		m := module.Version{Path: path, Version: max[path]}
   215  		min = append(min, m)
   216  		walk(m)
   217  	}
   218  	// Now the reverse postorder to bring in anything else.
   219  	for i := len(postorder) - 1; i >= 0; i-- {
   220  		m := postorder[i]
   221  		if max[m.Path] != m.Version {
   222  			// Older version.
   223  			continue
   224  		}
   225  		if have[m.Path] != m.Version {
   226  			min = append(min, m)
   227  			walk(m)
   228  		}
   229  	}
   230  	sort.Slice(min, func(i, j int) bool {
   231  		return min[i].Path < min[j].Path
   232  	})
   233  	return min, nil
   234  }
   235  
   236  // UpgradeAll returns a build list for the target module
   237  // in which every module is upgraded to its latest version.
   238  func UpgradeAll(target module.Version, reqs Reqs) ([]module.Version, error) {
   239  	return buildList(target, reqs, func(m module.Version) module.Version {
   240  		if m.Path == target.Path {
   241  			return target
   242  		}
   243  
   244  		latest, err := reqs.Upgrade(m)
   245  		if err != nil {
   246  			panic(err) // TODO
   247  		}
   248  		m.Version = latest.Version
   249  		return m
   250  	})
   251  }
   252  
   253  // Upgrade returns a build list for the target module
   254  // in which the given additional modules are upgraded.
   255  func Upgrade(target module.Version, reqs Reqs, upgrade ...module.Version) ([]module.Version, error) {
   256  	list, err := reqs.Required(target)
   257  	if err != nil {
   258  		panic(err) // TODO
   259  	}
   260  	// TODO: Maybe if an error is given,
   261  	// rerun with BuildList(upgrade[0], reqs) etc
   262  	// to find which ones are the buggy ones.
   263  	list = append([]module.Version(nil), list...)
   264  	list = append(list, upgrade...)
   265  	return BuildList(target, &override{target, list, reqs})
   266  }
   267  
   268  // Downgrade returns a build list for the target module
   269  // in which the given additional modules are downgraded.
   270  //
   271  // The versions to be downgraded may be unreachable from reqs.Latest and
   272  // reqs.Previous, but the methods of reqs must otherwise handle such versions
   273  // correctly.
   274  func Downgrade(target module.Version, reqs Reqs, downgrade ...module.Version) ([]module.Version, error) {
   275  	list, err := reqs.Required(target)
   276  	if err != nil {
   277  		panic(err) // TODO
   278  	}
   279  	max := make(map[string]string)
   280  	for _, r := range list {
   281  		max[r.Path] = r.Version
   282  	}
   283  	for _, d := range downgrade {
   284  		if v, ok := max[d.Path]; !ok || reqs.Max(v, d.Version) != d.Version {
   285  			max[d.Path] = d.Version
   286  		}
   287  	}
   288  
   289  	var (
   290  		added    = make(map[module.Version]bool)
   291  		rdeps    = make(map[module.Version][]module.Version)
   292  		excluded = make(map[module.Version]bool)
   293  	)
   294  	var exclude func(module.Version)
   295  	exclude = func(m module.Version) {
   296  		if excluded[m] {
   297  			return
   298  		}
   299  		excluded[m] = true
   300  		for _, p := range rdeps[m] {
   301  			exclude(p)
   302  		}
   303  	}
   304  	var add func(module.Version)
   305  	add = func(m module.Version) {
   306  		if added[m] {
   307  			return
   308  		}
   309  		added[m] = true
   310  		if v, ok := max[m.Path]; ok && reqs.Max(m.Version, v) != v {
   311  			exclude(m)
   312  			return
   313  		}
   314  		list, err := reqs.Required(m)
   315  		if err != nil {
   316  			panic(err) // TODO
   317  		}
   318  		for _, r := range list {
   319  			add(r)
   320  			if excluded[r] {
   321  				exclude(m)
   322  				return
   323  			}
   324  			rdeps[r] = append(rdeps[r], m)
   325  		}
   326  	}
   327  
   328  	var out []module.Version
   329  	out = append(out, target)
   330  List:
   331  	for _, r := range list {
   332  		add(r)
   333  		for excluded[r] {
   334  			p, err := reqs.Previous(r)
   335  			if err != nil {
   336  				return nil, err // TODO
   337  			}
   338  			// If the target version is a pseudo-version, it may not be
   339  			// included when iterating over prior versions using reqs.Previous.
   340  			// Insert it into the right place in the iteration.
   341  			// If v is excluded, p should be returned again by reqs.Previous on the next iteration.
   342  			if v := max[r.Path]; reqs.Max(v, r.Version) != v && reqs.Max(p.Version, v) != p.Version {
   343  				p.Version = v
   344  			}
   345  			if p.Version == "none" {
   346  				continue List
   347  			}
   348  			add(p)
   349  			r = p
   350  		}
   351  		out = append(out, r)
   352  	}
   353  
   354  	return out, nil
   355  }
   356  
   357  type override struct {
   358  	target module.Version
   359  	list   []module.Version
   360  	Reqs
   361  }
   362  
   363  func (r *override) Required(m module.Version) ([]module.Version, error) {
   364  	if m == r.target {
   365  		return r.list, nil
   366  	}
   367  	return r.Reqs.Required(m)
   368  }