github.com/sdboyer/gps@v0.16.3/selection.go (about)

     1  package gps
     2  
     3  type selection struct {
     4  	projects []selected
     5  	deps     map[ProjectRoot][]dependency
     6  	vu       versionUnifier
     7  }
     8  
     9  type selected struct {
    10  	a     atomWithPackages
    11  	first bool
    12  }
    13  
    14  func (s *selection) getDependenciesOn(id ProjectIdentifier) []dependency {
    15  	if deps, exists := s.deps[id.ProjectRoot]; exists {
    16  		return deps
    17  	}
    18  
    19  	return nil
    20  }
    21  
    22  // getIdentFor returns the ProjectIdentifier (so, the network name) currently in
    23  // use for the provided ProjectRoot.
    24  //
    25  // If no dependencies are present yet that designate a network name for
    26  // the provided root, this will return an empty ProjectIdentifier and false.
    27  func (s *selection) getIdentFor(pr ProjectRoot) (ProjectIdentifier, bool) {
    28  	deps := s.getDependenciesOn(ProjectIdentifier{ProjectRoot: pr})
    29  	if len(deps) == 0 {
    30  		return ProjectIdentifier{}, false
    31  	}
    32  
    33  	// For now, at least, the solver maintains (assumes?) the invariant that
    34  	// whatever is first in the deps list decides the net name to be used.
    35  	return deps[0].dep.Ident, true
    36  }
    37  
    38  // pushSelection pushes a new atomWithPackages onto the selection stack, along
    39  // with an indicator as to whether this selection indicates a new project *and*
    40  // packages, or merely some new packages on a project that was already selected.
    41  func (s *selection) pushSelection(a atomWithPackages, pkgonly bool) {
    42  	s.projects = append(s.projects, selected{
    43  		a:     a,
    44  		first: !pkgonly,
    45  	})
    46  }
    47  
    48  // popSelection removes and returns the last atomWithPackages from the selection
    49  // stack, along with an indication of whether that element was the first from
    50  // that project - that is, if it represented an addition of both a project and
    51  // one or more packages to the overall selection.
    52  func (s *selection) popSelection() (atomWithPackages, bool) {
    53  	var sel selected
    54  	sel, s.projects = s.projects[len(s.projects)-1], s.projects[:len(s.projects)-1]
    55  	return sel.a, sel.first
    56  }
    57  
    58  func (s *selection) pushDep(dep dependency) {
    59  	s.deps[dep.dep.Ident.ProjectRoot] = append(s.deps[dep.dep.Ident.ProjectRoot], dep)
    60  }
    61  
    62  func (s *selection) popDep(id ProjectIdentifier) (dep dependency) {
    63  	deps := s.deps[id.ProjectRoot]
    64  	dep, s.deps[id.ProjectRoot] = deps[len(deps)-1], deps[:len(deps)-1]
    65  	return dep
    66  }
    67  
    68  func (s *selection) depperCount(id ProjectIdentifier) int {
    69  	return len(s.deps[id.ProjectRoot])
    70  }
    71  
    72  func (s *selection) setDependenciesOn(id ProjectIdentifier, deps []dependency) {
    73  	s.deps[id.ProjectRoot] = deps
    74  }
    75  
    76  // Compute a list of the unique packages within the given ProjectIdentifier that
    77  // have dependers, and the number of dependers they have.
    78  func (s *selection) getRequiredPackagesIn(id ProjectIdentifier) map[string]int {
    79  	// TODO(sdboyer) this is horribly inefficient to do on the fly; we need a method to
    80  	// precompute it on pushing a new dep, and preferably with an immut
    81  	// structure so that we can pop with zero cost.
    82  	uniq := make(map[string]int)
    83  	for _, dep := range s.deps[id.ProjectRoot] {
    84  		for _, pkg := range dep.dep.pl {
    85  			uniq[pkg] = uniq[pkg] + 1
    86  		}
    87  	}
    88  
    89  	return uniq
    90  }
    91  
    92  // Compute a list of the unique packages within the given ProjectIdentifier that
    93  // are currently selected, and the number of times each package has been
    94  // independently selected.
    95  func (s *selection) getSelectedPackagesIn(id ProjectIdentifier) map[string]int {
    96  	// TODO(sdboyer) this is horribly inefficient to do on the fly; we need a method to
    97  	// precompute it on pushing a new dep, and preferably with an immut
    98  	// structure so that we can pop with zero cost.
    99  	uniq := make(map[string]int)
   100  	for _, p := range s.projects {
   101  		if p.a.a.id.eq(id) {
   102  			for _, pkg := range p.a.pl {
   103  				uniq[pkg] = uniq[pkg] + 1
   104  			}
   105  		}
   106  	}
   107  
   108  	return uniq
   109  }
   110  
   111  func (s *selection) getConstraint(id ProjectIdentifier) Constraint {
   112  	deps, exists := s.deps[id.ProjectRoot]
   113  	if !exists || len(deps) == 0 {
   114  		return any
   115  	}
   116  
   117  	// TODO(sdboyer) recomputing this sucks and is quite wasteful. Precompute/cache it
   118  	// on changes to the constraint set, instead.
   119  
   120  	// The solver itself is expected to maintain the invariant that all the
   121  	// constraints kept here collectively admit a non-empty set of versions. We
   122  	// assume this is the case here while assembling a composite constraint.
   123  
   124  	// Start with the open set
   125  	var ret Constraint = any
   126  	for _, dep := range deps {
   127  		ret = s.vu.intersect(id, ret, dep.dep.Constraint)
   128  	}
   129  
   130  	return ret
   131  }
   132  
   133  // selected checks to see if the given ProjectIdentifier has been selected, and
   134  // if so, returns the corresponding atomWithPackages.
   135  //
   136  // It walks the projects selection list from front to back and returns the first
   137  // match it finds, which means it will always and only return the base selection
   138  // of the project, without any additional package selections that may or may not
   139  // have happened later.
   140  func (s *selection) selected(id ProjectIdentifier) (atomWithPackages, bool) {
   141  	for _, p := range s.projects {
   142  		if p.a.a.id.ProjectRoot == id.ProjectRoot {
   143  			return p.a, true
   144  		}
   145  	}
   146  
   147  	return atomWithPackages{a: nilpa}, false
   148  }
   149  
   150  type unselected struct {
   151  	sl  []bimodalIdentifier
   152  	cmp func(i, j int) bool
   153  }
   154  
   155  func (u unselected) Len() int {
   156  	return len(u.sl)
   157  }
   158  
   159  func (u unselected) Less(i, j int) bool {
   160  	return u.cmp(i, j)
   161  }
   162  
   163  func (u unselected) Swap(i, j int) {
   164  	u.sl[i], u.sl[j] = u.sl[j], u.sl[i]
   165  }
   166  
   167  func (u *unselected) Push(x interface{}) {
   168  	u.sl = append(u.sl, x.(bimodalIdentifier))
   169  }
   170  
   171  func (u *unselected) Pop() (v interface{}) {
   172  	v, u.sl = u.sl[len(u.sl)-1], u.sl[:len(u.sl)-1]
   173  	return v
   174  }
   175  
   176  // remove takes a bimodalIdentifier out of the priority queue, if present. Only
   177  // the first matching bmi will be removed.
   178  //
   179  // There are two events that cause this to be called: bmi selection, when the
   180  // bmi at the front of the queue is removed, and backtracking, when a bmi
   181  // becomes unnecessary because the dependency that induced it was backtracked
   182  // and popped off.
   183  //
   184  // The worst case for both of these is O(n), but in practice the first case is
   185  // O(1), as we iterate the queue from front to back.
   186  func (u *unselected) remove(bmi bimodalIdentifier) {
   187  	plen := len(bmi.pl)
   188  outer:
   189  	for i, pi := range u.sl {
   190  		if pi.id.eq(bmi.id) && len(pi.pl) == plen {
   191  			// Simple slice comparison - assume they're both sorted the same
   192  			for i2, pkg := range pi.pl {
   193  				if bmi.pl[i2] != pkg {
   194  					continue outer
   195  				}
   196  			}
   197  
   198  			if i == len(u.sl)-1 {
   199  				// if we're on the last element, just pop, no splice
   200  				u.sl = u.sl[:len(u.sl)-1]
   201  			} else {
   202  				u.sl = append(u.sl[:i], u.sl[i+1:]...)
   203  			}
   204  			break
   205  		}
   206  	}
   207  }