github.com/golang/dep@v0.5.4/gps/selection.go (about)

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