github.com/golang/dep@v0.5.4/cmd/dep/root_analyzer.go (about)

     1  // Copyright 2016 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 main
     6  
     7  import (
     8  	"context"
     9  	"io/ioutil"
    10  	"log"
    11  
    12  	"github.com/golang/dep"
    13  	"github.com/golang/dep/gps"
    14  	fb "github.com/golang/dep/internal/feedback"
    15  	"github.com/golang/dep/internal/importers"
    16  	"golang.org/x/sync/errgroup"
    17  )
    18  
    19  // rootAnalyzer supplies manifest/lock data from both dep and external tool's
    20  // configuration files.
    21  // * When used on the root project, it imports only from external tools.
    22  // * When used by the solver for dependencies, it first looks for dep config,
    23  //   then external tools.
    24  type rootAnalyzer struct {
    25  	skipTools  bool
    26  	ctx        *dep.Ctx
    27  	sm         gps.SourceManager
    28  	directDeps map[gps.ProjectRoot]bool
    29  }
    30  
    31  func newRootAnalyzer(skipTools bool, ctx *dep.Ctx, directDeps map[gps.ProjectRoot]bool, sm gps.SourceManager) *rootAnalyzer {
    32  	return &rootAnalyzer{
    33  		skipTools:  skipTools,
    34  		ctx:        ctx,
    35  		sm:         sm,
    36  		directDeps: directDeps,
    37  	}
    38  }
    39  
    40  func (a *rootAnalyzer) InitializeRootManifestAndLock(dir string, pr gps.ProjectRoot) (rootM *dep.Manifest, rootL *dep.Lock, err error) {
    41  	if !a.skipTools {
    42  		rootM, rootL = a.importManifestAndLock(dir, pr, false)
    43  	}
    44  
    45  	if rootM == nil {
    46  		rootM = dep.NewManifest()
    47  
    48  		// Since we didn't find anything to import, dep's cache is empty.
    49  		// We are prefetching dependencies and logging so that the subsequent solve step
    50  		// doesn't spend a long time retrieving dependencies without feedback for the user.
    51  		if err := a.cacheDeps(pr); err != nil {
    52  			return nil, nil, err
    53  		}
    54  	}
    55  	if rootL == nil {
    56  		rootL = &dep.Lock{}
    57  	}
    58  
    59  	return
    60  }
    61  
    62  func (a *rootAnalyzer) cacheDeps(pr gps.ProjectRoot) error {
    63  	logger := a.ctx.Err
    64  	g, _ := errgroup.WithContext(context.TODO())
    65  	concurrency := 4
    66  
    67  	syncDep := func(pr gps.ProjectRoot, sm gps.SourceManager) error {
    68  		if err := sm.SyncSourceFor(gps.ProjectIdentifier{ProjectRoot: pr}); err != nil {
    69  			logger.Printf("Unable to cache %s - %s", pr, err)
    70  			return err
    71  		}
    72  		return nil
    73  	}
    74  
    75  	deps := make(chan gps.ProjectRoot)
    76  
    77  	for i := 0; i < concurrency; i++ {
    78  		g.Go(func() error {
    79  			for d := range deps {
    80  				err := syncDep(gps.ProjectRoot(d), a.sm)
    81  				if err != nil {
    82  					return err
    83  				}
    84  			}
    85  			return nil
    86  		})
    87  	}
    88  
    89  	g.Go(func() error {
    90  		defer close(deps)
    91  		for pr := range a.directDeps {
    92  			logger.Printf("Caching package %q", pr)
    93  			deps <- pr
    94  		}
    95  		return nil
    96  	})
    97  
    98  	if err := g.Wait(); err != nil {
    99  		return err
   100  	}
   101  	logger.Printf("Successfully cached all deps.")
   102  	return nil
   103  }
   104  
   105  func (a *rootAnalyzer) importManifestAndLock(dir string, pr gps.ProjectRoot, suppressLogs bool) (*dep.Manifest, *dep.Lock) {
   106  	logger := a.ctx.Err
   107  	if suppressLogs {
   108  		logger = log.New(ioutil.Discard, "", 0)
   109  	}
   110  
   111  	for _, i := range importers.BuildAll(logger, a.ctx.Verbose, a.sm) {
   112  		if i.HasDepMetadata(dir) {
   113  			a.ctx.Err.Printf("Importing configuration from %s. These are only initial constraints, and are further refined during the solve process.", i.Name())
   114  			m, l, err := i.Import(dir, pr)
   115  			if err != nil {
   116  				a.ctx.Err.Printf(
   117  					"Warning: Encountered an unrecoverable error while trying to import %s config from %q: %s",
   118  					i.Name(), dir, err,
   119  				)
   120  				break
   121  			}
   122  			a.removeTransitiveDependencies(m)
   123  			return m, l
   124  		}
   125  	}
   126  
   127  	var emptyManifest = dep.NewManifest()
   128  
   129  	return emptyManifest, nil
   130  }
   131  
   132  func (a *rootAnalyzer) removeTransitiveDependencies(m *dep.Manifest) {
   133  	for pr := range m.Constraints {
   134  		if _, isDirect := a.directDeps[pr]; !isDirect {
   135  			delete(m.Constraints, pr)
   136  		}
   137  	}
   138  }
   139  
   140  // DeriveManifestAndLock evaluates a dependency for existing dependency manager
   141  // configuration (ours or external) and passes any configuration found back
   142  // to the solver.
   143  func (a *rootAnalyzer) DeriveManifestAndLock(dir string, pr gps.ProjectRoot) (gps.Manifest, gps.Lock, error) {
   144  	// Ignore other tools if we find dep configuration
   145  	var depAnalyzer dep.Analyzer
   146  	if depAnalyzer.HasDepMetadata(dir) {
   147  		return depAnalyzer.DeriveManifestAndLock(dir, pr)
   148  	}
   149  
   150  	if !a.skipTools {
   151  		// The assignment back to an interface prevents interface-based nil checks from failing later
   152  		var manifest gps.Manifest = gps.SimpleManifest{}
   153  		var lock gps.Lock
   154  		im, il := a.importManifestAndLock(dir, pr, true)
   155  		if im != nil {
   156  			manifest = im
   157  		}
   158  		if il != nil {
   159  			lock = il
   160  		}
   161  		return manifest, lock, nil
   162  	}
   163  
   164  	return gps.SimpleManifest{}, nil, nil
   165  }
   166  
   167  func (a *rootAnalyzer) FinalizeRootManifestAndLock(m *dep.Manifest, l *dep.Lock, ol dep.Lock) {
   168  	// Iterate through the new projects in solved lock and add them to manifest
   169  	// if they are direct deps and log feedback for all the new projects.
   170  	diff := fb.DiffLocks(&ol, l)
   171  	bi := fb.NewBrokenImportFeedback(diff)
   172  	bi.LogFeedback(a.ctx.Err)
   173  	for _, y := range l.Projects() {
   174  		var f *fb.ConstraintFeedback
   175  		pr := y.Ident().ProjectRoot
   176  		// New constraints: in new lock and dir dep but not in manifest
   177  		if _, ok := a.directDeps[pr]; ok {
   178  			if _, ok := m.Constraints[pr]; !ok {
   179  				pp := getProjectPropertiesFromVersion(y.Version())
   180  				if pp.Constraint != nil {
   181  					m.Constraints[pr] = pp
   182  					pc := gps.ProjectConstraint{Ident: y.Ident(), Constraint: pp.Constraint}
   183  					f = fb.NewConstraintFeedback(pc, fb.DepTypeDirect)
   184  					f.LogFeedback(a.ctx.Err)
   185  				}
   186  				f = fb.NewLockedProjectFeedback(y, fb.DepTypeDirect)
   187  				f.LogFeedback(a.ctx.Err)
   188  			}
   189  		} else {
   190  			// New locked projects: in new lock but not in old lock
   191  			newProject := true
   192  			for _, opl := range ol.Projects() {
   193  				if pr == opl.Ident().ProjectRoot {
   194  					newProject = false
   195  				}
   196  			}
   197  			if newProject {
   198  				f = fb.NewLockedProjectFeedback(y, fb.DepTypeTransitive)
   199  				f.LogFeedback(a.ctx.Err)
   200  			}
   201  		}
   202  	}
   203  }
   204  
   205  // Info provides metadata on the analyzer algorithm used during solve.
   206  func (a *rootAnalyzer) Info() gps.ProjectAnalyzerInfo {
   207  	return gps.ProjectAnalyzerInfo{
   208  		Name:    "dep",
   209  		Version: 1,
   210  	}
   211  }