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 }