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

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