github.com/golang/dep@v0.5.4/gps/solution.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  	"context"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"sync"
    13  
    14  	"github.com/pkg/errors"
    15  	"golang.org/x/sync/errgroup"
    16  )
    17  
    18  // A Solution is returned by a solver run. It is mostly just a Lock, with some
    19  // additional methods that report information about the solve run.
    20  type Solution interface {
    21  	Lock
    22  	// The name of the ProjectAnalyzer used in generating this solution.
    23  	AnalyzerName() string
    24  	// The version of the ProjectAnalyzer used in generating this solution.
    25  	AnalyzerVersion() int
    26  	// The name of the Solver used in generating this solution.
    27  	SolverName() string
    28  	// The version of the Solver used in generating this solution.
    29  	SolverVersion() int
    30  	Attempts() int
    31  }
    32  
    33  type solution struct {
    34  	// The projects selected by the solver.
    35  	p []LockedProject
    36  
    37  	// The import inputs that created this solution (including requires).
    38  	i []string
    39  
    40  	// The number of solutions that were attempted
    41  	att int
    42  
    43  	// The analyzer info
    44  	analyzerInfo ProjectAnalyzerInfo
    45  
    46  	// The solver used in producing this solution
    47  	solv Solver
    48  }
    49  
    50  // WriteProgress informs about the progress of WriteDepTree.
    51  type WriteProgress struct {
    52  	Count   int
    53  	Total   int
    54  	LP      LockedProject
    55  	Failure bool
    56  }
    57  
    58  func (p WriteProgress) String() string {
    59  	msg := "Wrote"
    60  	if p.Failure {
    61  		msg = "Failed to write"
    62  	}
    63  	return fmt.Sprintf("(%d/%d) %s %s@%s", p.Count, p.Total, msg, p.LP.Ident(), p.LP.Version())
    64  }
    65  
    66  const concurrentWriters = 16
    67  
    68  // WriteDepTree takes a basedir, a Lock and a RootPruneOptions and exports all
    69  // the projects listed in the lock to the appropriate target location within basedir.
    70  //
    71  // If the goal is to populate a vendor directory, basedir should be the absolute
    72  // path to that vendor directory, not its parent (a project root, typically).
    73  //
    74  // It requires a SourceManager to do the work. Prune options are read from the
    75  // passed manifest.
    76  //
    77  // If onWrite is not nil, it will be called after each project write. Calls are ordered and atomic.
    78  func WriteDepTree(basedir string, l Lock, sm SourceManager, co CascadingPruneOptions, onWrite func(WriteProgress)) error {
    79  	if l == nil {
    80  		return fmt.Errorf("must provide non-nil Lock to WriteDepTree")
    81  	}
    82  
    83  	if err := os.MkdirAll(basedir, 0777); err != nil {
    84  		return err
    85  	}
    86  
    87  	g, ctx := errgroup.WithContext(context.TODO())
    88  	lps := l.Projects()
    89  	sem := make(chan struct{}, concurrentWriters)
    90  	var cnt struct {
    91  		sync.Mutex
    92  		i int
    93  	}
    94  
    95  	for i := range lps {
    96  		p := lps[i] // per-iteration copy
    97  
    98  		g.Go(func() error {
    99  			err := func() error {
   100  				select {
   101  				case sem <- struct{}{}:
   102  					defer func() { <-sem }()
   103  				case <-ctx.Done():
   104  					return ctx.Err()
   105  				}
   106  
   107  				ident := p.Ident()
   108  				projectRoot := string(ident.ProjectRoot)
   109  				to := filepath.FromSlash(filepath.Join(basedir, projectRoot))
   110  
   111  				if err := sm.ExportProject(ctx, ident, p.Version(), to); err != nil {
   112  					return errors.Wrapf(err, "failed to export %s", projectRoot)
   113  				}
   114  
   115  				err := PruneProject(to, p, co.PruneOptionsFor(ident.ProjectRoot))
   116  				if err != nil {
   117  					return errors.Wrapf(err, "failed to prune %s", projectRoot)
   118  				}
   119  
   120  				return ctx.Err()
   121  			}()
   122  
   123  			switch err {
   124  			case context.Canceled, context.DeadlineExceeded:
   125  				// Don't report "secondary" errors.
   126  			default:
   127  				if onWrite != nil {
   128  					// Increment and call atomically to prevent re-ordering.
   129  					cnt.Lock()
   130  					cnt.i++
   131  					onWrite(WriteProgress{
   132  						Count:   cnt.i,
   133  						Total:   len(lps),
   134  						LP:      p,
   135  						Failure: err != nil,
   136  					})
   137  					cnt.Unlock()
   138  				}
   139  			}
   140  
   141  			return err
   142  		})
   143  	}
   144  
   145  	err := g.Wait()
   146  	if err != nil {
   147  		os.RemoveAll(basedir)
   148  	}
   149  	return errors.Wrap(err, "failed to write dep tree")
   150  }
   151  
   152  func (r solution) Projects() []LockedProject {
   153  	return r.p
   154  }
   155  
   156  func (r solution) InputImports() []string {
   157  	return r.i
   158  }
   159  
   160  func (r solution) Attempts() int {
   161  	return r.att
   162  }
   163  
   164  func (r solution) AnalyzerName() string {
   165  	return r.analyzerInfo.Name
   166  }
   167  
   168  func (r solution) AnalyzerVersion() int {
   169  	return r.analyzerInfo.Version
   170  }
   171  
   172  func (r solution) SolverName() string {
   173  	return r.solv.Name()
   174  }
   175  
   176  func (r solution) SolverVersion() int {
   177  	return r.solv.Version()
   178  }