github.com/golang/dep@v0.5.4/gps/bridge.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  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"sync/atomic"
    12  
    13  	"github.com/golang/dep/gps/pkgtree"
    14  )
    15  
    16  // sourceBridge is an adapter to SourceManagers that tailor operations for a
    17  // single solve run.
    18  type sourceBridge interface {
    19  	// sourceBridge includes many methods from the SourceManager interface.
    20  	SourceExists(ProjectIdentifier) (bool, error)
    21  	SyncSourceFor(ProjectIdentifier) error
    22  	RevisionPresentIn(ProjectIdentifier, Revision) (bool, error)
    23  	ListPackages(ProjectIdentifier, Version) (pkgtree.PackageTree, error)
    24  	GetManifestAndLock(ProjectIdentifier, Version, ProjectAnalyzer) (Manifest, Lock, error)
    25  	ExportProject(ProjectIdentifier, Version, string) error
    26  	DeduceProjectRoot(ip string) (ProjectRoot, error)
    27  
    28  	listVersions(ProjectIdentifier) ([]Version, error)
    29  	verifyRootDir(path string) error
    30  	vendorCodeExists(ProjectIdentifier) (bool, error)
    31  	breakLock()
    32  }
    33  
    34  // bridge is an adapter around a proper SourceManager. It provides localized
    35  // caching that's tailored to the requirements of a particular solve run.
    36  //
    37  // Finally, it provides authoritative version/constraint operations, ensuring
    38  // that any possible approach to a match - even those not literally encoded in
    39  // the inputs - is achieved.
    40  type bridge struct {
    41  	// The underlying, adapted-to SourceManager
    42  	sm SourceManager
    43  
    44  	// The solver which we're assisting.
    45  	//
    46  	// The link between solver and bridge is circular, which is typically a bit
    47  	// awkward, but the bridge needs access to so many of the input arguments
    48  	// held by the solver that it ends up being easier and saner to do this.
    49  	s *solver
    50  
    51  	// Map of project root name to their available version list. This cache is
    52  	// layered on top of the proper SourceManager's cache; the only difference
    53  	// is that this keeps the versions sorted in the direction required by the
    54  	// current solve run.
    55  	vlists map[ProjectIdentifier][]Version
    56  
    57  	// Indicates whether lock breaking has already been run
    58  	lockbroken int32
    59  
    60  	// Whether to sort version lists for downgrade.
    61  	down bool
    62  
    63  	// The cancellation context provided to the solver. Threading it through the
    64  	// various solver methods is needlessly verbose so long as we maintain the
    65  	// lifetime guarantees that a solver can only be run once.
    66  	// TODO(sdboyer) uncomment this and thread it through SourceManager methods
    67  	//ctx context.Context
    68  }
    69  
    70  // mkBridge creates a bridge
    71  func mkBridge(s *solver, sm SourceManager, down bool) *bridge {
    72  	return &bridge{
    73  		sm:     sm,
    74  		s:      s,
    75  		down:   down,
    76  		vlists: make(map[ProjectIdentifier][]Version),
    77  	}
    78  }
    79  
    80  func (b *bridge) GetManifestAndLock(id ProjectIdentifier, v Version, an ProjectAnalyzer) (Manifest, Lock, error) {
    81  	if b.s.rd.isRoot(id.ProjectRoot) {
    82  		return b.s.rd.rm, b.s.rd.rl, nil
    83  	}
    84  
    85  	b.s.mtr.push("b-gmal")
    86  	m, l, e := b.sm.GetManifestAndLock(id, v, an)
    87  	b.s.mtr.pop()
    88  	return m, l, e
    89  }
    90  
    91  func (b *bridge) listVersions(id ProjectIdentifier) ([]Version, error) {
    92  	if vl, exists := b.vlists[id]; exists {
    93  		return vl, nil
    94  	}
    95  
    96  	b.s.mtr.push("b-list-versions")
    97  	pvl, err := b.sm.ListVersions(id)
    98  	if err != nil {
    99  		b.s.mtr.pop()
   100  		return nil, err
   101  	}
   102  
   103  	vl := hidePair(pvl)
   104  	if b.down {
   105  		SortForDowngrade(vl)
   106  	} else {
   107  		SortForUpgrade(vl)
   108  	}
   109  
   110  	b.vlists[id] = vl
   111  	b.s.mtr.pop()
   112  	return vl, nil
   113  }
   114  
   115  func (b *bridge) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) {
   116  	b.s.mtr.push("b-rev-present-in")
   117  	i, e := b.sm.RevisionPresentIn(id, r)
   118  	b.s.mtr.pop()
   119  	return i, e
   120  }
   121  
   122  func (b *bridge) SourceExists(id ProjectIdentifier) (bool, error) {
   123  	b.s.mtr.push("b-source-exists")
   124  	i, e := b.sm.SourceExists(id)
   125  	b.s.mtr.pop()
   126  	return i, e
   127  }
   128  
   129  func (b *bridge) vendorCodeExists(id ProjectIdentifier) (bool, error) {
   130  	fi, err := os.Stat(filepath.Join(b.s.rd.dir, "vendor", string(id.ProjectRoot)))
   131  	if err != nil {
   132  		return false, err
   133  	} else if fi.IsDir() {
   134  		return true, nil
   135  	}
   136  
   137  	return false, nil
   138  }
   139  
   140  // listPackages lists all the packages contained within the given project at a
   141  // particular version.
   142  //
   143  // The root project is handled separately, as the source manager isn't
   144  // responsible for that code.
   145  func (b *bridge) ListPackages(id ProjectIdentifier, v Version) (pkgtree.PackageTree, error) {
   146  	if b.s.rd.isRoot(id.ProjectRoot) {
   147  		return b.s.rd.rpt, nil
   148  	}
   149  
   150  	b.s.mtr.push("b-list-pkgs")
   151  	pt, err := b.sm.ListPackages(id, v)
   152  	b.s.mtr.pop()
   153  	return pt, err
   154  }
   155  
   156  func (b *bridge) ExportProject(id ProjectIdentifier, v Version, path string) error {
   157  	panic("bridge should never be used to ExportProject")
   158  }
   159  
   160  // verifyRoot ensures that the provided path to the project root is in good
   161  // working condition. This check is made only once, at the beginning of a solve
   162  // run.
   163  func (b *bridge) verifyRootDir(path string) error {
   164  	if fi, err := os.Stat(path); err != nil {
   165  		return badOptsFailure(fmt.Sprintf("could not read project root (%s): %s", path, err))
   166  	} else if !fi.IsDir() {
   167  		return badOptsFailure(fmt.Sprintf("project root (%s) is a file, not a directory", path))
   168  	}
   169  
   170  	return nil
   171  }
   172  
   173  func (b *bridge) DeduceProjectRoot(ip string) (ProjectRoot, error) {
   174  	b.s.mtr.push("b-deduce-proj-root")
   175  	pr, e := b.sm.DeduceProjectRoot(ip)
   176  	b.s.mtr.pop()
   177  	return pr, e
   178  }
   179  
   180  // breakLock is called when the solver has to break a version recorded in the
   181  // lock file. It prefetches all the projects in the solver's lock, so that the
   182  // information is already on hand if/when the solver needs it.
   183  //
   184  // Projects that have already been selected are skipped, as it's generally unlikely that the
   185  // solver will have to backtrack through and fully populate their version queues.
   186  func (b *bridge) breakLock() {
   187  	// No real conceivable circumstance in which multiple calls are made to
   188  	// this, but being that this is the entrance point to a bunch of async work,
   189  	// protect it with an atomic CAS in case things change in the future.
   190  	//
   191  	// We avoid using a sync.Once here, as there's no reason for other callers
   192  	// to block until completion.
   193  	if !atomic.CompareAndSwapInt32(&b.lockbroken, 0, 1) {
   194  		return
   195  	}
   196  
   197  	for _, lp := range b.s.rd.rl.Projects() {
   198  		if _, is := b.s.sel.selected(lp.Ident()); !is {
   199  			pi, v := lp.Ident(), lp.Version()
   200  			go func() {
   201  				// Sync first
   202  				b.sm.SyncSourceFor(pi)
   203  				// Preload the package info for the locked version, too, as
   204  				// we're more likely to need that
   205  				b.sm.ListPackages(pi, v)
   206  			}()
   207  		}
   208  	}
   209  }
   210  
   211  func (b *bridge) SyncSourceFor(id ProjectIdentifier) error {
   212  	// we don't track metrics here b/c this is often called in its own goroutine
   213  	// by the solver, and the metrics design is for wall time on a single thread
   214  	return b.sm.SyncSourceFor(id)
   215  }