github.com/golang/dep@v0.5.4/gps/solver.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  import (
     8  	"container/heap"
     9  	"context"
    10  	"fmt"
    11  	"log"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  	"sync/atomic"
    16  
    17  	"github.com/armon/go-radix"
    18  	"github.com/golang/dep/gps/paths"
    19  	"github.com/golang/dep/gps/pkgtree"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  var rootRev = Revision("")
    24  
    25  // SolveParameters hold all arguments to a solver run.
    26  //
    27  // Only RootDir and RootPackageTree are absolutely required. A nil Manifest is
    28  // allowed, though it usually makes little sense.
    29  //
    30  // Of these properties, only the Manifest and RootPackageTree are (directly)
    31  // incorporated in memoization hashing.
    32  type SolveParameters struct {
    33  	// The path to the root of the project on which the solver should operate.
    34  	// This should point to the directory that should contain the vendor/
    35  	// directory.
    36  	//
    37  	// In general, it is wise for this to be under an active GOPATH, though it
    38  	// is not (currently) required.
    39  	//
    40  	// A real path to a readable directory is required.
    41  	RootDir string
    42  
    43  	// The ProjectAnalyzer is responsible for extracting Manifest and
    44  	// (optionally) Lock information from dependencies. The solver passes it
    45  	// along to its SourceManager's GetManifestAndLock() method as needed.
    46  	//
    47  	// An analyzer is required.
    48  	ProjectAnalyzer ProjectAnalyzer
    49  
    50  	// The tree of packages that comprise the root project, as well as the
    51  	// import path that should identify the root of that tree.
    52  	//
    53  	// In most situations, tools should simply pass the result of ListPackages()
    54  	// directly through here.
    55  	//
    56  	// The ImportRoot property must be a non-empty string, and at least one
    57  	// element must be present in the Packages map.
    58  	RootPackageTree pkgtree.PackageTree
    59  
    60  	// The root manifest. This contains all the dependency constraints
    61  	// associated with normal Manifests, as well as the particular controls
    62  	// afforded only to the root project.
    63  	//
    64  	// May be nil, but for most cases, that would be unwise.
    65  	Manifest RootManifest
    66  
    67  	// The root lock. Optional. Generally, this lock is the output of a previous
    68  	// solve run.
    69  	//
    70  	// If provided, the solver will attempt to preserve the versions specified
    71  	// in the lock, unless ToChange or ChangeAll settings indicate otherwise.
    72  	Lock Lock
    73  
    74  	// ToChange is a list of project names that should be changed - that is, any
    75  	// versions specified for those projects in the root lock file should be
    76  	// ignored.
    77  	//
    78  	// Passing ChangeAll has subtly different behavior from enumerating all
    79  	// projects into ToChange. In general, ToChange should *only* be used if the
    80  	// user expressly requested an upgrade for a specific project.
    81  	ToChange []ProjectRoot
    82  
    83  	// ChangeAll indicates that all projects should be changed - that is, any
    84  	// versions specified in the root lock file should be ignored.
    85  	ChangeAll bool
    86  
    87  	// Downgrade indicates whether the solver will attempt to upgrade (false) or
    88  	// downgrade (true) projects that are not locked, or are marked for change.
    89  	//
    90  	// Upgrading is, by far, the most typical case. The field is named
    91  	// 'Downgrade' so that the bool's zero value corresponds to that most
    92  	// typical case.
    93  	Downgrade bool
    94  
    95  	// TraceLogger is the logger to use for generating trace output. If set, the
    96  	// solver will generate informative trace output as it moves through the
    97  	// solving process.
    98  	TraceLogger *log.Logger
    99  
   100  	// stdLibFn is the function to use to recognize standard library import paths.
   101  	// Only overridden for tests. Defaults to paths.IsStandardImportPath if nil.
   102  	stdLibFn func(string) bool
   103  
   104  	// mkBridgeFn is the function to use to create sourceBridges.
   105  	// Only overridden for tests (so we can run with virtual RootDir).
   106  	// Defaults to mkBridge if nil.
   107  	mkBridgeFn func(*solver, SourceManager, bool) sourceBridge
   108  }
   109  
   110  // solver is a CDCL-style constraint solver with satisfiability conditions
   111  // hardcoded to the needs of the Go package management problem space.
   112  type solver struct {
   113  	// The current number of attempts made over the course of this solve. This
   114  	// number increments each time the algorithm completes a backtrack and
   115  	// starts moving forward again.
   116  	attempts int
   117  
   118  	// Logger used exclusively for trace output, or nil to suppress.
   119  	tl *log.Logger
   120  
   121  	// The function to use to recognize standard library import paths.
   122  	stdLibFn func(string) bool
   123  
   124  	// A bridge to the standard SourceManager. The adapter does some local
   125  	// caching of pre-sorted version lists, as well as translation between the
   126  	// full-on ProjectIdentifiers that the solver deals with and the simplified
   127  	// names a SourceManager operates on.
   128  	b sourceBridge
   129  
   130  	// A stack containing projects and packages that are currently "selected" -
   131  	// that is, they have passed all satisfiability checks, and are part of the
   132  	// current solution.
   133  	//
   134  	// The *selection type is mostly just a dumb data container; the solver
   135  	// itself is responsible for maintaining that invariant.
   136  	sel *selection
   137  
   138  	// The current list of projects that we need to incorporate into the solution in
   139  	// order for the solution to be complete. This list is implemented as a
   140  	// priority queue that places projects least likely to induce errors at the
   141  	// front, in order to minimize the amount of backtracking required to find a
   142  	// solution.
   143  	//
   144  	// Entries are added to and removed from this list by the solver at the same
   145  	// time that the selected queue is updated, either with an addition or
   146  	// removal.
   147  	unsel *unselected
   148  
   149  	// A stack of all the currently active versionQueues in the solver. The set
   150  	// of projects represented here corresponds closely to what's in s.sel,
   151  	// although s.sel will always contain the root project, and s.vqs never
   152  	// will. Also, s.vqs is only added to (or popped from during backtracking)
   153  	// when a new project is selected; it is untouched when new packages are
   154  	// added to an existing project.
   155  	vqs []*versionQueue
   156  
   157  	// Contains data and constraining information from the root project
   158  	rd rootdata
   159  
   160  	// metrics for the current solve run.
   161  	mtr *metrics
   162  
   163  	// Indicates whether the solver has been run. It is invalid to run this type
   164  	// of solver more than once.
   165  	hasrun int32
   166  }
   167  
   168  func (params SolveParameters) toRootdata() (rootdata, error) {
   169  	if params.ProjectAnalyzer == nil {
   170  		return rootdata{}, badOptsFailure("must provide a ProjectAnalyzer")
   171  	}
   172  	if params.RootDir == "" {
   173  		return rootdata{}, badOptsFailure("params must specify a non-empty root directory")
   174  	}
   175  	if params.RootPackageTree.ImportRoot == "" {
   176  		return rootdata{}, badOptsFailure("params must include a non-empty import root")
   177  	}
   178  	if len(params.RootPackageTree.Packages) == 0 {
   179  		return rootdata{}, badOptsFailure("at least one package must be present in the PackageTree")
   180  	}
   181  	if params.Lock == nil && len(params.ToChange) != 0 {
   182  		return rootdata{}, badOptsFailure(fmt.Sprintf("update specifically requested for %s, but no lock was provided to upgrade from", params.ToChange))
   183  	}
   184  
   185  	if params.Manifest == nil {
   186  		params.Manifest = simpleRootManifest{}
   187  	}
   188  
   189  	rd := rootdata{
   190  		ir:      params.Manifest.IgnoredPackages(),
   191  		req:     params.Manifest.RequiredPackages(),
   192  		ovr:     params.Manifest.Overrides(),
   193  		rpt:     params.RootPackageTree.Copy(),
   194  		chng:    make(map[ProjectRoot]struct{}),
   195  		rlm:     make(map[ProjectRoot]LockedProject),
   196  		chngall: params.ChangeAll,
   197  		dir:     params.RootDir,
   198  		an:      params.ProjectAnalyzer,
   199  	}
   200  
   201  	// Ensure the required and overrides maps are at least initialized
   202  	if rd.req == nil {
   203  		rd.req = make(map[string]bool)
   204  	}
   205  	if rd.ovr == nil {
   206  		rd.ovr = make(ProjectConstraints)
   207  	}
   208  
   209  	if rd.ir.Len() > 0 {
   210  		var both []string
   211  		for pkg := range params.Manifest.RequiredPackages() {
   212  			if rd.ir.IsIgnored(pkg) {
   213  				both = append(both, pkg)
   214  			}
   215  		}
   216  		switch len(both) {
   217  		case 0:
   218  			break
   219  		case 1:
   220  			return rootdata{}, badOptsFailure(fmt.Sprintf("%q was given as both a required and ignored package", both[0]))
   221  		default:
   222  			return rootdata{}, badOptsFailure(fmt.Sprintf("multiple packages given as both required and ignored: %s", strings.Join(both, ", ")))
   223  		}
   224  	}
   225  
   226  	// Validate no empties in the overrides map
   227  	var eovr []string
   228  	for pr, pp := range rd.ovr {
   229  		if pp.Constraint == nil && pp.Source == "" {
   230  			eovr = append(eovr, string(pr))
   231  		}
   232  	}
   233  
   234  	if eovr != nil {
   235  		// Maybe it's a little nitpicky to do this (we COULD proceed; empty
   236  		// overrides have no effect), but this errs on the side of letting the
   237  		// tool/user know there's bad input. Purely as a principle, that seems
   238  		// preferable to silently allowing progress with icky input.
   239  		if len(eovr) > 1 {
   240  			return rootdata{}, badOptsFailure(fmt.Sprintf("Overrides lacked any non-zero properties for multiple project roots: %s", strings.Join(eovr, " ")))
   241  		}
   242  		return rootdata{}, badOptsFailure(fmt.Sprintf("An override was declared for %s, but without any non-zero properties", eovr[0]))
   243  	}
   244  
   245  	// Prep safe, normalized versions of root manifest and lock data
   246  	rd.rm = prepManifest(params.Manifest)
   247  
   248  	if params.Lock != nil {
   249  		for _, lp := range params.Lock.Projects() {
   250  			rd.rlm[lp.Ident().ProjectRoot] = lp
   251  		}
   252  
   253  		// Also keep a prepped one, mostly for the bridge. This is probably
   254  		// wasteful, but only minimally so, and yay symmetry
   255  		rd.rl = prepLock(params.Lock)
   256  	}
   257  
   258  	for _, p := range params.ToChange {
   259  		if _, exists := rd.rlm[p]; !exists {
   260  			return rootdata{}, badOptsFailure(fmt.Sprintf("cannot update %s as it is not in the lock", p))
   261  		}
   262  		rd.chng[p] = struct{}{}
   263  	}
   264  
   265  	return rd, nil
   266  }
   267  
   268  // Prepare readies a Solver for use.
   269  //
   270  // This function reads and validates the provided SolveParameters. If a problem
   271  // with the inputs is detected, an error is returned. Otherwise, a Solver is
   272  // returned, ready to hash and check inputs or perform a solving run.
   273  func Prepare(params SolveParameters, sm SourceManager) (Solver, error) {
   274  	if sm == nil {
   275  		return nil, badOptsFailure("must provide non-nil SourceManager")
   276  	}
   277  
   278  	rd, err := params.toRootdata()
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  
   283  	if params.stdLibFn == nil {
   284  		params.stdLibFn = paths.IsStandardImportPath
   285  	}
   286  
   287  	s := &solver{
   288  		tl:       params.TraceLogger,
   289  		stdLibFn: params.stdLibFn,
   290  		rd:       rd,
   291  	}
   292  
   293  	// Set up the bridge and ensure the root dir is in good, working order
   294  	// before doing anything else.
   295  	if params.mkBridgeFn == nil {
   296  		s.b = mkBridge(s, sm, params.Downgrade)
   297  	} else {
   298  		s.b = params.mkBridgeFn(s, sm, params.Downgrade)
   299  	}
   300  	err = s.b.verifyRootDir(params.RootDir)
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  
   305  	// Initialize stacks and queues
   306  	s.sel = &selection{
   307  		deps:      make(map[ProjectRoot][]dependency),
   308  		foldRoots: make(map[string]ProjectRoot),
   309  	}
   310  	s.unsel = &unselected{
   311  		sl:  make([]bimodalIdentifier, 0),
   312  		cmp: s.unselectedComparator,
   313  	}
   314  
   315  	return s, nil
   316  }
   317  
   318  // A Solver is the main workhorse of gps: given a set of project inputs, it
   319  // performs a constraint solving analysis to develop a complete Solution, or
   320  // else fail with an informative error.
   321  //
   322  // If a Solution is found, an implementing tool may persist it - typically into
   323  // a "lock file" - and/or use it to write out a directory tree of dependencies,
   324  // suitable to be a vendor directory, via CreateVendorTree.
   325  type Solver interface {
   326  	// Solve initiates a solving run. It will either abort due to a canceled
   327  	// Context, complete successfully with a Solution, or fail with an
   328  	// informative error.
   329  	//
   330  	// It is generally not allowed that this method be called twice for any
   331  	// given solver.
   332  	Solve(context.Context) (Solution, error)
   333  
   334  	// Name returns a string identifying the particular solver backend.
   335  	//
   336  	// Different solvers likely have different invariants, and likely will not
   337  	// have the same result sets for any particular inputs.
   338  	Name() string
   339  
   340  	// Version returns an int indicating the version of the solver of the given
   341  	// Name(). Implementations should change their reported version ONLY when
   342  	// the logic is changed in such a way that substantially changes the result
   343  	// set that is possible for a substantial subset of likely inputs.
   344  	//
   345  	// "Substantial" is an imprecise term, and it is used intentionally. There
   346  	// are no easy, general ways of subdividing constraint solving problems such
   347  	// that one can know, a priori, the full impact that subtle algorithmic
   348  	// changes will have on possible result sets. Consequently, we have to fall
   349  	// back on coarser, intuition-based reasoning as to whether a change is
   350  	// large enough that it is likely to be broadly user-visible.
   351  	//
   352  	// This is acceptable, because this value is not used programmatically by
   353  	// the solver in any way. Rather, it is intend for implementing tools to
   354  	// use as a coarse signal to users about compatibility between their tool's
   355  	// version and the current data, typically via persistence to a Lock.
   356  	// Changes to the version number reported should be weighed between
   357  	// confusing teams by having two members' tools continuously rolling back
   358  	// each others' chosen Solutions for no apparent reason, and annoying teams
   359  	// by changing the number for changes so remote that warnings about solver
   360  	// version mismatches become meaningless.
   361  	//
   362  	// Err on the side of caution.
   363  	//
   364  	// Chronology is the only implication of the ordering - that lower version
   365  	// numbers were published before higher numbers.
   366  	Version() int
   367  }
   368  
   369  func (s *solver) Name() string {
   370  	return "gps-cdcl"
   371  }
   372  
   373  func (s *solver) Version() int {
   374  	return 1
   375  }
   376  
   377  // DeductionErrs maps package import path to errors occurring during deduction.
   378  type DeductionErrs map[string]error
   379  
   380  func (e DeductionErrs) Error() string {
   381  	return "could not deduce external imports' project roots"
   382  }
   383  
   384  // ValidateParams validates the solver parameters to ensure solving can be completed.
   385  func ValidateParams(params SolveParameters, sm SourceManager) error {
   386  	// Ensure that all packages are deducible without issues.
   387  	var deducePkgsGroup sync.WaitGroup
   388  	deductionErrs := make(DeductionErrs)
   389  	var errsMut sync.Mutex
   390  
   391  	rd, err := params.toRootdata()
   392  	if err != nil {
   393  		return err
   394  	}
   395  
   396  	deducePkg := func(ip string, sm SourceManager) {
   397  		_, err := sm.DeduceProjectRoot(ip)
   398  		if err != nil {
   399  			errsMut.Lock()
   400  			deductionErrs[ip] = err
   401  			errsMut.Unlock()
   402  		}
   403  		deducePkgsGroup.Done()
   404  	}
   405  
   406  	for _, ip := range rd.externalImportList(paths.IsStandardImportPath) {
   407  		deducePkgsGroup.Add(1)
   408  		go deducePkg(ip, sm)
   409  	}
   410  
   411  	deducePkgsGroup.Wait()
   412  
   413  	if len(deductionErrs) > 0 {
   414  		return deductionErrs
   415  	}
   416  
   417  	return nil
   418  }
   419  
   420  // Solve attempts to find a dependency solution for the given project, as
   421  // represented by the SolveParameters with which this Solver was created.
   422  //
   423  // This is the entry point to the main gps workhorse.
   424  func (s *solver) Solve(ctx context.Context) (Solution, error) {
   425  	// Solving can only be run once per solver.
   426  	if !atomic.CompareAndSwapInt32(&s.hasrun, 0, 1) {
   427  		return nil, errors.New("solve method can only be run once per instance")
   428  	}
   429  	// Make sure the bridge has the context before we start.
   430  	//s.b.ctx = ctx
   431  
   432  	// Set up a metrics object
   433  	s.mtr = newMetrics()
   434  
   435  	// Prime the queues with the root project
   436  	if err := s.selectRoot(); err != nil {
   437  		return nil, err
   438  	}
   439  
   440  	all, err := s.solve(ctx)
   441  
   442  	s.mtr.pop()
   443  	var soln solution
   444  	if err == nil {
   445  		soln = solution{
   446  			att:  s.attempts,
   447  			solv: s,
   448  		}
   449  		soln.analyzerInfo = s.rd.an.Info()
   450  		soln.i = s.rd.externalImportList(s.stdLibFn)
   451  
   452  		// Convert ProjectAtoms into LockedProjects
   453  		soln.p = make([]LockedProject, 0, len(all))
   454  		for pa, pl := range all {
   455  			lp := pa2lp(pa, pl)
   456  			// Pass back the original inputlp directly if it Eqs what was
   457  			// selected.
   458  			if inputlp, has := s.rd.rlm[lp.Ident().ProjectRoot]; has && lp.Eq(inputlp) {
   459  				lp = inputlp
   460  			}
   461  
   462  			soln.p = append(soln.p, lp)
   463  		}
   464  	}
   465  
   466  	s.traceFinish(soln, err)
   467  	if s.tl != nil {
   468  		s.mtr.dump(s.tl)
   469  	}
   470  	return soln, err
   471  }
   472  
   473  // solve is the top-level loop for the solving process.
   474  func (s *solver) solve(ctx context.Context) (map[atom]map[string]struct{}, error) {
   475  	// Pull out the donechan once up front so that we're not potentially
   476  	// triggering mutex cycling and channel creation on each iteration.
   477  	donechan := ctx.Done()
   478  
   479  	// Main solving loop
   480  	for {
   481  		select {
   482  		case <-donechan:
   483  			return nil, ctx.Err()
   484  		default:
   485  		}
   486  
   487  		bmi, has := s.nextUnselected()
   488  
   489  		if !has {
   490  			// no more packages to select - we're done.
   491  			break
   492  		}
   493  
   494  		// This split is the heart of "bimodal solving": we follow different
   495  		// satisfiability and selection paths depending on whether we've already
   496  		// selected the base project/repo that came off the unselected queue.
   497  		//
   498  		// (If we've already selected the project, other parts of the algorithm
   499  		// guarantee the bmi will contain at least one package from this project
   500  		// that has yet to be selected.)
   501  		if awp, is := s.sel.selected(bmi.id); !is {
   502  			s.mtr.push("new-atom")
   503  			// Analysis path for when we haven't selected the project yet - need
   504  			// to create a version queue.
   505  			queue, err := s.createVersionQueue(bmi)
   506  			if err != nil {
   507  				s.mtr.pop()
   508  				// Err means a failure somewhere down the line; try backtracking.
   509  				s.traceStartBacktrack(bmi, err, false)
   510  				success, berr := s.backtrack(ctx)
   511  				if berr != nil {
   512  					err = berr
   513  				} else if success {
   514  					// backtracking succeeded, move to the next unselected id
   515  					continue
   516  				}
   517  				return nil, err
   518  			}
   519  
   520  			if queue.current() == nil {
   521  				panic("canary - queue is empty, but flow indicates success")
   522  			}
   523  
   524  			awp := atomWithPackages{
   525  				a: atom{
   526  					id: queue.id,
   527  					v:  queue.current(),
   528  				},
   529  				pl: bmi.pl,
   530  			}
   531  			err = s.selectAtom(awp, false)
   532  			s.mtr.pop()
   533  			if err != nil {
   534  				// Only a released SourceManager should be able to cause this.
   535  				return nil, err
   536  			}
   537  
   538  			s.vqs = append(s.vqs, queue)
   539  		} else {
   540  			s.mtr.push("add-atom")
   541  			// We're just trying to add packages to an already-selected project.
   542  			// That means it's not OK to burn through the version queue for that
   543  			// project as we do when first selecting a project, as doing so
   544  			// would upend the guarantees on which all previous selections of
   545  			// the project are based (both the initial one, and any package-only
   546  			// ones).
   547  
   548  			// Because we can only safely operate within the scope of the
   549  			// single, currently selected version, we can skip looking for the
   550  			// queue and just use the version given in what came back from
   551  			// s.sel.selected().
   552  			nawp := atomWithPackages{
   553  				a: atom{
   554  					id: bmi.id,
   555  					v:  awp.a.v,
   556  				},
   557  				pl: bmi.pl,
   558  			}
   559  
   560  			s.traceCheckPkgs(bmi)
   561  			err := s.check(nawp, true)
   562  			if err != nil {
   563  				s.mtr.pop()
   564  				// Err means a failure somewhere down the line; try backtracking.
   565  				s.traceStartBacktrack(bmi, err, true)
   566  				success, berr := s.backtrack(ctx)
   567  				if berr != nil {
   568  					err = berr
   569  				} else if success {
   570  					// backtracking succeeded, move to the next unselected id
   571  					continue
   572  				}
   573  				return nil, err
   574  			}
   575  			err = s.selectAtom(nawp, true)
   576  			s.mtr.pop()
   577  			if err != nil {
   578  				// Only a released SourceManager should be able to cause this.
   579  				return nil, err
   580  			}
   581  
   582  			// We don't add anything to the stack of version queues because the
   583  			// backtracker knows not to pop the vqstack if it backtracks
   584  			// across a pure-package addition.
   585  		}
   586  	}
   587  
   588  	// Getting this far means we successfully found a solution. Combine the
   589  	// selected projects and packages.
   590  	projs := make(map[atom]map[string]struct{})
   591  
   592  	// Skip the first project. It's always the root, and that shouldn't be
   593  	// included in results.
   594  	for _, sel := range s.sel.projects[1:] {
   595  		pm, exists := projs[sel.a.a]
   596  		if !exists {
   597  			pm = make(map[string]struct{})
   598  			projs[sel.a.a] = pm
   599  		}
   600  
   601  		for _, path := range sel.a.pl {
   602  			pm[path] = struct{}{}
   603  		}
   604  	}
   605  	return projs, nil
   606  }
   607  
   608  // selectRoot is a specialized selectAtom, used solely to initially
   609  // populate the queues at the beginning of a solve run.
   610  func (s *solver) selectRoot() error {
   611  	s.mtr.push("select-root")
   612  	// Push the root project onto the queue.
   613  	awp := s.rd.rootAtom()
   614  	s.sel.pushSelection(awp, false)
   615  
   616  	// If we're looking for root's deps, get it from opts and local root
   617  	// analysis, rather than having the sm do it.
   618  	deps, err := s.intersectConstraintsWithImports(s.rd.combineConstraints(), s.rd.externalImportList(s.stdLibFn))
   619  	if err != nil {
   620  		if contextCanceledOrSMReleased(err) {
   621  			return err
   622  		}
   623  		// TODO(sdboyer) this could well happen; handle it with a more graceful error
   624  		panic(fmt.Sprintf("canary - shouldn't be possible %s", err))
   625  	}
   626  
   627  	for _, dep := range deps {
   628  		// If we have no lock, or if this dep isn't in the lock, then prefetch
   629  		// it. See longer explanation in selectAtom() for how we benefit from
   630  		// parallelism here.
   631  		if s.rd.needVersionsFor(dep.Ident.ProjectRoot) {
   632  			go s.b.SyncSourceFor(dep.Ident)
   633  		}
   634  
   635  		s.sel.pushDep(dependency{depender: awp.a, dep: dep})
   636  		// Add all to unselected queue
   637  		heap.Push(s.unsel, bimodalIdentifier{id: dep.Ident, pl: dep.pl, fromRoot: true})
   638  	}
   639  
   640  	s.traceSelectRoot(s.rd.rpt, deps)
   641  	s.mtr.pop()
   642  	return nil
   643  }
   644  
   645  func (s *solver) getImportsAndConstraintsOf(a atomWithPackages) ([]string, []completeDep, error) {
   646  	var err error
   647  
   648  	if s.rd.isRoot(a.a.id.ProjectRoot) {
   649  		panic("Should never need to recheck imports/constraints from root during solve")
   650  	}
   651  
   652  	// Work through the source manager to get project info and static analysis
   653  	// information.
   654  	m, _, err := s.b.GetManifestAndLock(a.a.id, a.a.v, s.rd.an)
   655  	if err != nil {
   656  		return nil, nil, err
   657  	}
   658  
   659  	ptree, err := s.b.ListPackages(a.a.id, a.a.v)
   660  	if err != nil {
   661  		return nil, nil, err
   662  	}
   663  
   664  	rm, em := ptree.ToReachMap(true, false, true, s.rd.ir)
   665  	// Use maps to dedupe the unique internal and external packages.
   666  	exmap, inmap := make(map[string]struct{}), make(map[string]struct{})
   667  
   668  	for _, pkg := range a.pl {
   669  		inmap[pkg] = struct{}{}
   670  		for _, ipkg := range rm[pkg].Internal {
   671  			inmap[ipkg] = struct{}{}
   672  		}
   673  	}
   674  
   675  	var pl []string
   676  	// If lens are the same, then the map must have the same contents as the
   677  	// slice; no need to build a new one.
   678  	if len(inmap) == len(a.pl) {
   679  		pl = a.pl
   680  	} else {
   681  		pl = make([]string, 0, len(inmap))
   682  		for pkg := range inmap {
   683  			pl = append(pl, pkg)
   684  		}
   685  		sort.Strings(pl)
   686  	}
   687  
   688  	// Add to the list those packages that are reached by the packages
   689  	// explicitly listed in the atom
   690  	for _, pkg := range a.pl {
   691  		// Skip ignored packages
   692  		if s.rd.ir.IsIgnored(pkg) {
   693  			continue
   694  		}
   695  
   696  		ie, exists := rm[pkg]
   697  		if !exists {
   698  			// Missing package here *should* only happen if the target pkg was
   699  			// poisoned; check the errors map.
   700  			if importErr, eexists := em[pkg]; eexists {
   701  				return nil, nil, importErr
   702  			}
   703  
   704  			// Nope, it's actually full-on not there.
   705  			return nil, nil, fmt.Errorf("package %s does not exist within project %s", pkg, a.a.id)
   706  		}
   707  
   708  		for _, ex := range ie.External {
   709  			exmap[ex] = struct{}{}
   710  		}
   711  	}
   712  
   713  	reach := make([]string, 0, len(exmap))
   714  	for pkg := range exmap {
   715  		reach = append(reach, pkg)
   716  	}
   717  	sort.Strings(reach)
   718  
   719  	deps := s.rd.ovr.overrideAll(m.DependencyConstraints())
   720  	cd, err := s.intersectConstraintsWithImports(deps, reach)
   721  	return pl, cd, err
   722  }
   723  
   724  // intersectConstraintsWithImports takes a list of constraints and a list of
   725  // externally reached packages, and creates a []completeDep that is guaranteed
   726  // to include all packages named by import reach, using constraints where they
   727  // are available, or Any() where they are not.
   728  func (s *solver) intersectConstraintsWithImports(deps []workingConstraint, reach []string) ([]completeDep, error) {
   729  	// Create a radix tree with all the projects we know from the manifest
   730  	xt := radix.New()
   731  	for _, dep := range deps {
   732  		xt.Insert(string(dep.Ident.ProjectRoot), dep)
   733  	}
   734  
   735  	// Step through the reached packages; if they have prefix matches in
   736  	// the trie, assume (mostly) it's a correct correspondence.
   737  	dmap := make(map[ProjectRoot]completeDep)
   738  	for _, rp := range reach {
   739  		// If it's a stdlib-shaped package, skip it.
   740  		if s.stdLibFn(rp) {
   741  			continue
   742  		}
   743  
   744  		// Look for a prefix match; it'll be the root project/repo containing
   745  		// the reached package
   746  		if pre, idep, match := xt.LongestPrefix(rp); match && isPathPrefixOrEqual(pre, rp) {
   747  			// Match is valid; put it in the dmap, either creating a new
   748  			// completeDep or appending it to the existing one for this base
   749  			// project/prefix.
   750  			dep := idep.(workingConstraint)
   751  			if cdep, exists := dmap[dep.Ident.ProjectRoot]; exists {
   752  				cdep.pl = append(cdep.pl, rp)
   753  				dmap[dep.Ident.ProjectRoot] = cdep
   754  			} else {
   755  				dmap[dep.Ident.ProjectRoot] = completeDep{
   756  					workingConstraint: dep,
   757  					pl:                []string{rp},
   758  				}
   759  			}
   760  			continue
   761  		}
   762  
   763  		// No match. Let the SourceManager try to figure out the root
   764  		root, err := s.b.DeduceProjectRoot(rp)
   765  		if err != nil {
   766  			// Nothing we can do if we can't suss out a root
   767  			return nil, err
   768  		}
   769  
   770  		// Make a new completeDep with an open constraint, respecting overrides
   771  		pd := s.rd.ovr.override(root, ProjectProperties{Constraint: Any()})
   772  
   773  		// Insert the pd into the trie so that further deps from this
   774  		// project get caught by the prefix search
   775  		xt.Insert(string(root), pd)
   776  		// And also put the complete dep into the dmap
   777  		dmap[root] = completeDep{
   778  			workingConstraint: pd,
   779  			pl:                []string{rp},
   780  		}
   781  	}
   782  
   783  	// Dump all the deps from the map into the expected return slice
   784  	cdeps := make([]completeDep, 0, len(dmap))
   785  	for _, cdep := range dmap {
   786  		cdeps = append(cdeps, cdep)
   787  	}
   788  
   789  	return cdeps, nil
   790  }
   791  
   792  func (s *solver) createVersionQueue(bmi bimodalIdentifier) (*versionQueue, error) {
   793  	id := bmi.id
   794  	// If on the root package, there's no queue to make
   795  	if s.rd.isRoot(id.ProjectRoot) {
   796  		return newVersionQueue(id, nil, nil, s.b)
   797  	}
   798  
   799  	exists, err := s.b.SourceExists(id)
   800  	if err != nil {
   801  		return nil, err
   802  	}
   803  	if !exists {
   804  		exists, err = s.b.vendorCodeExists(id)
   805  		if err != nil {
   806  			return nil, err
   807  		}
   808  		if exists {
   809  			// Project exists only in vendor
   810  			// FIXME(sdboyer) this just totally doesn't work at all right now
   811  		} else {
   812  			return nil, fmt.Errorf("project '%s' could not be located", id)
   813  		}
   814  	}
   815  
   816  	var lockv Version
   817  	if len(s.rd.rlm) > 0 {
   818  		lockv, err = s.getLockVersionIfValid(id)
   819  		if err != nil {
   820  			// Can only get an error here if an upgrade was expressly requested on
   821  			// code that exists only in vendor
   822  			return nil, err
   823  		}
   824  	}
   825  
   826  	var prefv Version
   827  	if bmi.fromRoot {
   828  		// If this bmi came from the root, then we want to search through things
   829  		// with a dependency on it in order to see if any have a lock that might
   830  		// express a prefv
   831  		//
   832  		// TODO(sdboyer) nested loop; prime candidate for a cache somewhere
   833  		for _, dep := range s.sel.getDependenciesOn(bmi.id) {
   834  			// Skip the root, of course
   835  			if s.rd.isRoot(dep.depender.id.ProjectRoot) {
   836  				continue
   837  			}
   838  
   839  			_, l, err := s.b.GetManifestAndLock(dep.depender.id, dep.depender.v, s.rd.an)
   840  			if err != nil || l == nil {
   841  				// err being non-nil really shouldn't be possible, but the lock
   842  				// being nil is quite likely
   843  				continue
   844  			}
   845  
   846  			for _, lp := range l.Projects() {
   847  				if lp.Ident().eq(bmi.id) {
   848  					prefv = lp.Version()
   849  				}
   850  			}
   851  		}
   852  
   853  		// OTHER APPROACH - WRONG, BUT MAYBE USEFUL FOR REFERENCE?
   854  		// If this bmi came from the root, then we want to search the unselected
   855  		// queue to see if anything *else* wants this ident, in which case we
   856  		// pick up that prefv
   857  		//for _, bmi2 := range s.unsel.sl {
   858  		//// Take the first thing from the queue that's for the same ident,
   859  		//// and has a non-nil prefv
   860  		//if bmi.id.eq(bmi2.id) {
   861  		//if bmi2.prefv != nil {
   862  		//prefv = bmi2.prefv
   863  		//}
   864  		//}
   865  		//}
   866  
   867  	} else {
   868  		// Otherwise, just use the preferred version expressed in the bmi
   869  		prefv = bmi.prefv
   870  	}
   871  
   872  	q, err := newVersionQueue(id, lockv, prefv, s.b)
   873  	if err != nil {
   874  		// TODO(sdboyer) this particular err case needs to be improved to be ONLY for cases
   875  		// where there's absolutely nothing findable about a given project name
   876  		return nil, err
   877  	}
   878  
   879  	// Hack in support for revisions.
   880  	//
   881  	// By design, revs aren't returned from ListVersion(). Thus, if the dep in
   882  	// the bmi was has a rev constraint, it is (almost) guaranteed to fail, even
   883  	// if that rev does exist in the repo. So, detect a rev and push it into the
   884  	// vq here, instead.
   885  	//
   886  	// Happily, the solver maintains the invariant that constraints on a given
   887  	// ident cannot be incompatible, so we know that if we find one rev, then
   888  	// any other deps will have to also be on that rev (or Any).
   889  	//
   890  	// TODO(sdboyer) while this does work, it bypasses the interface-implied guarantees
   891  	// of the version queue, and is therefore not a great strategy for API
   892  	// coherency. Folding this in to a formal interface would be better.
   893  	if tc, ok := s.sel.getConstraint(bmi.id).(Revision); ok && q.pi[0] != tc {
   894  		// We know this is the only thing that could possibly match, so put it
   895  		// in at the front - if it isn't there already.
   896  		// TODO(sdboyer) existence of the revision is guaranteed by checkRevisionExists(); restore that call.
   897  		q.pi = append([]Version{tc}, q.pi...)
   898  	}
   899  
   900  	// Having assembled the queue, search it for a valid version.
   901  	s.traceCheckQueue(q, bmi, false, 1)
   902  	return q, s.findValidVersion(q, bmi.pl)
   903  }
   904  
   905  // findValidVersion walks through a versionQueue until it finds a version that
   906  // satisfies the constraints held in the current state of the solver.
   907  //
   908  // The satisfiability checks triggered from here are constrained to operate only
   909  // on those dependencies induced by the list of packages given in the second
   910  // parameter.
   911  func (s *solver) findValidVersion(q *versionQueue, pl []string) error {
   912  	if nil == q.current() {
   913  		// this case should not be reachable, but reflects improper solver state
   914  		// if it is, so panic immediately
   915  		panic("version queue is empty, should not happen: " + string(q.id.ProjectRoot) + " " + q.id.Source)
   916  	}
   917  
   918  	faillen := len(q.fails)
   919  
   920  	for {
   921  		cur := q.current()
   922  		s.traceInfo("try %s@%s", q.id, cur)
   923  		err := s.check(atomWithPackages{
   924  			a: atom{
   925  				id: q.id,
   926  				v:  cur,
   927  			},
   928  			pl: pl,
   929  		}, false)
   930  		if err == nil {
   931  			// we have a good version, can return safely
   932  			return nil
   933  		}
   934  
   935  		if q.advance(err) != nil {
   936  			// Error on advance, have to bail out
   937  			break
   938  		}
   939  		if q.isExhausted() {
   940  			// Queue is empty, bail with error
   941  			break
   942  		}
   943  	}
   944  
   945  	s.fail(s.sel.getDependenciesOn(q.id)[0].depender.id)
   946  
   947  	// Return a compound error of all the new errors encountered during this
   948  	// attempt to find a new, valid version
   949  	return &noVersionError{
   950  		pn:    q.id,
   951  		fails: q.fails[faillen:],
   952  	}
   953  }
   954  
   955  // getLockVersionIfValid finds an atom for the given ProjectIdentifier from the
   956  // root lock, assuming:
   957  //
   958  // 1. A root lock was provided
   959  // 2. The general flag to change all projects was not passed
   960  // 3. A flag to change this particular ProjectIdentifier was not passed
   961  //
   962  // If any of these three conditions are true (or if the id cannot be found in
   963  // the root lock), then no atom will be returned.
   964  func (s *solver) getLockVersionIfValid(id ProjectIdentifier) (Version, error) {
   965  	// If the project is specifically marked for changes, then don't look for a
   966  	// locked version.
   967  	if _, explicit := s.rd.chng[id.ProjectRoot]; explicit || s.rd.chngall {
   968  		// For projects with an upstream or cache repository, it's safe to
   969  		// ignore what's in the lock, because there's presumably more versions
   970  		// to be found and attempted in the repository. If it's only in vendor,
   971  		// though, then we have to try to use what's in the lock, because that's
   972  		// the only version we'll be able to get.
   973  		if exist, _ := s.b.SourceExists(id); exist {
   974  			// Upgrades mean breaking the lock
   975  			s.b.breakLock()
   976  			return nil, nil
   977  		}
   978  
   979  		// However, if a change was *expressly* requested for something that
   980  		// exists only in vendor, then that guarantees we don't have enough
   981  		// information to complete a solution. In that case, error out.
   982  		if explicit {
   983  			return nil, &missingSourceFailure{
   984  				goal: id,
   985  				prob: "Cannot upgrade %s, as no source repository could be found.",
   986  			}
   987  		}
   988  	}
   989  
   990  	lp, exists := s.rd.rlm[id.ProjectRoot]
   991  	if !exists {
   992  		return nil, nil
   993  	}
   994  
   995  	constraint := s.sel.getConstraint(id)
   996  	v := lp.Version()
   997  	if !constraint.Matches(v) {
   998  		// No match found, which means we're going to be breaking the lock
   999  		// Still return the invalid version so that is included in the trace
  1000  		s.b.breakLock()
  1001  	}
  1002  
  1003  	return v, nil
  1004  }
  1005  
  1006  // backtrack works backwards from the current failed solution to find the next
  1007  // solution to try.
  1008  func (s *solver) backtrack(ctx context.Context) (bool, error) {
  1009  	if len(s.vqs) == 0 {
  1010  		// nothing to backtrack to
  1011  		return false, nil
  1012  	}
  1013  
  1014  	donechan := ctx.Done()
  1015  	s.mtr.push("backtrack")
  1016  	defer s.mtr.pop()
  1017  	for {
  1018  		for {
  1019  			select {
  1020  			case <-donechan:
  1021  				return false, ctx.Err()
  1022  			default:
  1023  			}
  1024  
  1025  			if len(s.vqs) == 0 {
  1026  				// no more versions, nowhere further to backtrack
  1027  				return false, nil
  1028  			}
  1029  			if s.vqs[len(s.vqs)-1].failed {
  1030  				break
  1031  			}
  1032  
  1033  			s.vqs, s.vqs[len(s.vqs)-1] = s.vqs[:len(s.vqs)-1], nil
  1034  
  1035  			// Pop selections off until we get to a project.
  1036  			var proj bool
  1037  			var awp atomWithPackages
  1038  			for !proj {
  1039  				var err error
  1040  				awp, proj, err = s.unselectLast()
  1041  				if err != nil {
  1042  					if !contextCanceledOrSMReleased(err) {
  1043  						panic(fmt.Sprintf("canary - should only have been able to get a context cancellation or SM release, got %T %s", err, err))
  1044  					}
  1045  					return false, err
  1046  				}
  1047  				s.traceBacktrack(awp.bmi(), !proj)
  1048  			}
  1049  		}
  1050  
  1051  		// Grab the last versionQueue off the list of queues
  1052  		q := s.vqs[len(s.vqs)-1]
  1053  
  1054  		// Walk back to the next project. This may entail walking through some
  1055  		// package-only selections.
  1056  		var proj bool
  1057  		var awp atomWithPackages
  1058  		for !proj {
  1059  			var err error
  1060  			awp, proj, err = s.unselectLast()
  1061  			if err != nil {
  1062  				if !contextCanceledOrSMReleased(err) {
  1063  					panic(fmt.Sprintf("canary - should only have been able to get a context cancellation or SM release, got %T %s", err, err))
  1064  				}
  1065  				return false, err
  1066  			}
  1067  			s.traceBacktrack(awp.bmi(), !proj)
  1068  		}
  1069  
  1070  		if !q.id.eq(awp.a.id) {
  1071  			panic("canary - version queue stack and selected project stack are misaligned")
  1072  		}
  1073  
  1074  		// Advance the queue past the current version, which we know is bad
  1075  		// TODO(sdboyer) is it feasible to make available the failure reason here?
  1076  		if q.advance(nil) == nil && !q.isExhausted() {
  1077  			// Search for another acceptable version of this failed dep in its queue
  1078  			s.traceCheckQueue(q, awp.bmi(), true, 0)
  1079  			if s.findValidVersion(q, awp.pl) == nil {
  1080  				// Found one! Put it back on the selected queue and stop
  1081  				// backtracking
  1082  
  1083  				// reusing the old awp is fine
  1084  				awp.a.v = q.current()
  1085  				err := s.selectAtom(awp, false)
  1086  				if err != nil {
  1087  					if !contextCanceledOrSMReleased(err) {
  1088  						panic(fmt.Sprintf("canary - should only have been able to get a context cancellation or SM release, got %T %s", err, err))
  1089  					}
  1090  					return false, err
  1091  				}
  1092  				break
  1093  			}
  1094  		}
  1095  
  1096  		s.traceBacktrack(awp.bmi(), false)
  1097  
  1098  		// No solution found; continue backtracking after popping the queue
  1099  		// we just inspected off the list
  1100  		// GC-friendly pop pointer elem in slice
  1101  		s.vqs, s.vqs[len(s.vqs)-1] = s.vqs[:len(s.vqs)-1], nil
  1102  	}
  1103  
  1104  	// Backtracking was successful if loop ended before running out of versions
  1105  	if len(s.vqs) == 0 {
  1106  		return false, nil
  1107  	}
  1108  	s.attempts++
  1109  	return true, nil
  1110  }
  1111  
  1112  func (s *solver) nextUnselected() (bimodalIdentifier, bool) {
  1113  	if len(s.unsel.sl) > 0 {
  1114  		return s.unsel.sl[0], true
  1115  	}
  1116  
  1117  	return bimodalIdentifier{}, false
  1118  }
  1119  
  1120  func (s *solver) unselectedComparator(i, j int) bool {
  1121  	ibmi, jbmi := s.unsel.sl[i], s.unsel.sl[j]
  1122  	iname, jname := ibmi.id, jbmi.id
  1123  
  1124  	// Most important thing is pushing package additions ahead of project
  1125  	// additions. Package additions can't walk their version queue, so all they
  1126  	// do is narrow the possibility of success; better to find out early and
  1127  	// fast if they're going to fail than wait until after we've done real work
  1128  	// on a project and have to backtrack across it.
  1129  
  1130  	// FIXME the impl here is currently O(n) in the number of selections; it
  1131  	// absolutely cannot stay in a hot sorting path like this
  1132  	// FIXME while other solver invariants probably protect us from it, this
  1133  	// call-out means that it's possible for external state change to invalidate
  1134  	// heap invariants.
  1135  	_, isel := s.sel.selected(iname)
  1136  	_, jsel := s.sel.selected(jname)
  1137  
  1138  	if isel && !jsel {
  1139  		return true
  1140  	}
  1141  	if !isel && jsel {
  1142  		return false
  1143  	}
  1144  
  1145  	if iname.eq(jname) {
  1146  		return false
  1147  	}
  1148  
  1149  	_, ilock := s.rd.rlm[iname.ProjectRoot]
  1150  	_, jlock := s.rd.rlm[jname.ProjectRoot]
  1151  
  1152  	switch {
  1153  	case ilock && !jlock:
  1154  		return true
  1155  	case !ilock && jlock:
  1156  		return false
  1157  	case ilock && jlock:
  1158  		return iname.Less(jname)
  1159  	}
  1160  
  1161  	// Now, sort by number of available versions. This will trigger network
  1162  	// activity, but at this point we know that the project we're looking at
  1163  	// isn't locked by the root. And, because being locked by root is the only
  1164  	// way avoid that call when making a version queue, we know we're gonna have
  1165  	// to pay that cost anyway.
  1166  
  1167  	// We can safely ignore an err from listVersions here because, if there is
  1168  	// an actual problem, it'll be noted and handled somewhere else saner in the
  1169  	// solving algorithm.
  1170  	ivl, _ := s.b.listVersions(iname)
  1171  	jvl, _ := s.b.listVersions(jname)
  1172  	iv, jv := len(ivl), len(jvl)
  1173  
  1174  	// Packages with fewer versions to pick from are less likely to benefit from
  1175  	// backtracking, so deal with them earlier in order to minimize the amount
  1176  	// of superfluous backtracking through them we do.
  1177  	switch {
  1178  	case iv == 0 && jv != 0:
  1179  		return true
  1180  	case iv != 0 && jv == 0:
  1181  		return false
  1182  	case iv != jv:
  1183  		return iv < jv
  1184  	}
  1185  
  1186  	// Finally, if all else fails, fall back to comparing by name
  1187  	return iname.Less(jname)
  1188  }
  1189  
  1190  func (s *solver) fail(id ProjectIdentifier) {
  1191  	// TODO(sdboyer) does this need updating, now that we have non-project package
  1192  	// selection?
  1193  
  1194  	// skip if the root project
  1195  	if !s.rd.isRoot(id.ProjectRoot) {
  1196  		// just look for the first (oldest) one; the backtracker will necessarily
  1197  		// traverse through and pop off any earlier ones
  1198  		for _, vq := range s.vqs {
  1199  			if vq.id.eq(id) {
  1200  				vq.failed = true
  1201  				return
  1202  			}
  1203  		}
  1204  	}
  1205  }
  1206  
  1207  // selectAtom pulls an atom into the selection stack, alongside some of
  1208  // its contained packages. New resultant dependency requirements are added to
  1209  // the unselected priority queue.
  1210  //
  1211  // Behavior is slightly diffferent if pkgonly is true.
  1212  func (s *solver) selectAtom(a atomWithPackages, pkgonly bool) error {
  1213  	s.mtr.push("select-atom")
  1214  	s.unsel.remove(bimodalIdentifier{
  1215  		id: a.a.id,
  1216  		pl: a.pl,
  1217  	})
  1218  
  1219  	pl, deps, err := s.getImportsAndConstraintsOf(a)
  1220  	if err != nil {
  1221  		if contextCanceledOrSMReleased(err) {
  1222  			return err
  1223  		}
  1224  		// This shouldn't be possible; other checks should have ensured all
  1225  		// packages and deps are present for any argument passed to this method.
  1226  		panic(fmt.Sprintf("canary - shouldn't be possible %s", err))
  1227  	}
  1228  	// Assign the new internal package list into the atom, then push it onto the
  1229  	// selection stack
  1230  	a.pl = pl
  1231  	s.sel.pushSelection(a, pkgonly)
  1232  
  1233  	// If this atom has a lock, pull it out so that we can potentially inject
  1234  	// preferred versions into any bmis we enqueue
  1235  	//
  1236  	// TODO(sdboyer) making this call here could be the first thing to trigger
  1237  	// network activity...maybe? if so, can we mitigate by deferring the work to
  1238  	// queue consumption time?
  1239  	_, l, _ := s.b.GetManifestAndLock(a.a.id, a.a.v, s.rd.an)
  1240  	var lmap map[ProjectIdentifier]Version
  1241  	if l != nil {
  1242  		lmap = make(map[ProjectIdentifier]Version)
  1243  		for _, lp := range l.Projects() {
  1244  			lmap[lp.Ident()] = lp.Version()
  1245  		}
  1246  	}
  1247  
  1248  	for _, dep := range deps {
  1249  		// Root can come back up here if there's a project-level cycle.
  1250  		// Satisfiability checks have already ensured invariants are maintained,
  1251  		// so we know we can just skip it here.
  1252  		if s.rd.isRoot(dep.Ident.ProjectRoot) {
  1253  			continue
  1254  		}
  1255  		// If this is dep isn't in the lock, do some prefetching. (If it is, we
  1256  		// might be able to get away with zero network activity for it, so don't
  1257  		// prefetch). This provides an opportunity for some parallelism wins, on
  1258  		// two fronts:
  1259  		//
  1260  		// 1. Because this loop may have multiple deps in it, we could end up
  1261  		// simultaneously fetching both in the background while solving proceeds
  1262  		//
  1263  		// 2. Even if only one dep gets prefetched here, the worst case is that
  1264  		// that same dep comes out of the unselected queue next, and we gain a
  1265  		// few microseconds before blocking later. Best case, the dep doesn't
  1266  		// come up next, but some other dep comes up that wasn't prefetched, and
  1267  		// both fetches proceed in parallel.
  1268  		if s.rd.needVersionsFor(dep.Ident.ProjectRoot) {
  1269  			go s.b.SyncSourceFor(dep.Ident)
  1270  		}
  1271  
  1272  		s.sel.pushDep(dependency{depender: a.a, dep: dep})
  1273  		// Go through all the packages introduced on this dep, selecting only
  1274  		// the ones where the only depper on them is what the preceding line just
  1275  		// pushed in. Then, put those into the unselected queue.
  1276  		rpm := s.sel.getRequiredPackagesIn(dep.Ident)
  1277  		var newp []string
  1278  		for _, pkg := range dep.pl {
  1279  			// Just one means that the dep we're visiting is the sole importer.
  1280  			if rpm[pkg] == 1 {
  1281  				newp = append(newp, pkg)
  1282  			}
  1283  		}
  1284  
  1285  		if len(newp) > 0 {
  1286  			// If there was a previously-established alternate source for this
  1287  			// dependency, but the current atom did not express one (and getting
  1288  			// here means the atom passed the source hot-swapping check - see
  1289  			// checkIdentMatches()), then we have to create the new bmi with the
  1290  			// alternate source. Otherwise, we end up with two discrete project
  1291  			// entries for the project root in the final output, one with the
  1292  			// alternate source, and one without. See #969.
  1293  			id, _ := s.sel.getIdentFor(dep.Ident.ProjectRoot)
  1294  			bmi := bimodalIdentifier{
  1295  				id: id,
  1296  				pl: newp,
  1297  				// This puts in a preferred version if one's in the map, else
  1298  				// drops in the zero value (nil)
  1299  				prefv: lmap[dep.Ident],
  1300  			}
  1301  			heap.Push(s.unsel, bmi)
  1302  		}
  1303  	}
  1304  
  1305  	s.traceSelect(a, pkgonly)
  1306  	s.mtr.pop()
  1307  
  1308  	return nil
  1309  }
  1310  
  1311  func (s *solver) unselectLast() (atomWithPackages, bool, error) {
  1312  	s.mtr.push("unselect")
  1313  	defer s.mtr.pop()
  1314  	awp, first := s.sel.popSelection()
  1315  	heap.Push(s.unsel, bimodalIdentifier{id: awp.a.id, pl: awp.pl})
  1316  
  1317  	_, deps, err := s.getImportsAndConstraintsOf(awp)
  1318  	if err != nil {
  1319  		if contextCanceledOrSMReleased(err) {
  1320  			return atomWithPackages{}, false, err
  1321  		}
  1322  		// This shouldn't be possible; other checks should have ensured all
  1323  		// packages and deps are present for any argument passed to this method.
  1324  		panic(fmt.Sprintf("canary - shouldn't be possible %s", err))
  1325  	}
  1326  
  1327  	for _, dep := range deps {
  1328  		// Skip popping if the dep is the root project, which can occur if
  1329  		// there's a project-level import cycle. (This occurs frequently with
  1330  		// e.g. kubernetes and docker)
  1331  		if s.rd.isRoot(dep.Ident.ProjectRoot) {
  1332  			continue
  1333  		}
  1334  		s.sel.popDep(dep.Ident)
  1335  
  1336  		// if no parents/importers, remove from unselected queue
  1337  		if s.sel.depperCount(dep.Ident) == 0 {
  1338  			s.unsel.remove(bimodalIdentifier{id: dep.Ident, pl: dep.pl})
  1339  		}
  1340  	}
  1341  
  1342  	return awp, first, nil
  1343  }
  1344  
  1345  // simple (temporary?) helper just to convert atoms into locked projects
  1346  func pa2lp(pa atom, pkgs map[string]struct{}) LockedProject {
  1347  	lp := lockedProject{
  1348  		pi: pa.id,
  1349  	}
  1350  
  1351  	switch v := pa.v.(type) {
  1352  	case UnpairedVersion:
  1353  		lp.v = v
  1354  	case Revision:
  1355  		lp.r = v
  1356  	case versionPair:
  1357  		lp.v = v.v
  1358  		lp.r = v.r
  1359  	default:
  1360  		panic("unreachable")
  1361  	}
  1362  
  1363  	lp.pkgs = make([]string, 0, len(pkgs))
  1364  
  1365  	pr := string(pa.id.ProjectRoot)
  1366  	trim := pr + "/"
  1367  	for pkg := range pkgs {
  1368  		if pkg == string(pa.id.ProjectRoot) {
  1369  			lp.pkgs = append(lp.pkgs, ".")
  1370  		} else {
  1371  			lp.pkgs = append(lp.pkgs, strings.TrimPrefix(pkg, trim))
  1372  		}
  1373  	}
  1374  	sort.Strings(lp.pkgs)
  1375  
  1376  	return lp
  1377  }
  1378  
  1379  func contextCanceledOrSMReleased(err error) bool {
  1380  	return err == context.Canceled || err == context.DeadlineExceeded || err == ErrSourceManagerIsReleased
  1381  }