cmd/dep/root_analyzer.go
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"io/ioutil"
"log"
"github.com/golang/dep"
"github.com/golang/dep/gps"
fb "github.com/golang/dep/internal/feedback"
"github.com/golang/dep/internal/importers"
"golang.org/x/sync/errgroup"
)
// rootAnalyzer supplies manifest/lock data from both dep and external tool's
// configuration files.
// * When used on the root project, it imports only from external tools.
// * When used by the solver for dependencies, it first looks for dep config,
// then external tools.
type rootAnalyzer struct {
skipTools bool
ctx *dep.Ctx
sm gps.SourceManager
directDeps map[gps.ProjectRoot]bool
}
func newRootAnalyzer(skipTools bool, ctx *dep.Ctx, directDeps map[gps.ProjectRoot]bool, sm gps.SourceManager) *rootAnalyzer {
return &rootAnalyzer{
skipTools: skipTools,
ctx: ctx,
sm: sm,
directDeps: directDeps,
}
}
func (a *rootAnalyzer) InitializeRootManifestAndLock(dir string, pr gps.ProjectRoot) (rootM *dep.Manifest, rootL *dep.Lock, err error) {
if !a.skipTools {
rootM, rootL = a.importManifestAndLock(dir, pr, false)
}
if rootM == nil {
rootM = dep.NewManifest()
// Since we didn't find anything to import, dep's cache is empty.
// We are prefetching dependencies and logging so that the subsequent solve step
// doesn't spend a long time retrieving dependencies without feedback for the user.
if err := a.cacheDeps(pr); err != nil {
return nil, nil, err
}
}
if rootL == nil {
rootL = &dep.Lock{}
}
return
}
func (a *rootAnalyzer) cacheDeps(pr gps.ProjectRoot) error {
logger := a.ctx.Err
g, _ := errgroup.WithContext(context.TODO())
concurrency := 4
syncDep := func(pr gps.ProjectRoot, sm gps.SourceManager) error {
if err := sm.SyncSourceFor(gps.ProjectIdentifier{ProjectRoot: pr}); err != nil {
logger.Printf("Unable to cache %s - %s", pr, err)
return err
}
return nil
}
deps := make(chan gps.ProjectRoot)
for i := 0; i < concurrency; i++ {
g.Go(func() error {
for d := range deps {
err := syncDep(gps.ProjectRoot(d), a.sm)
if err != nil {
return err
}
}
return nil
})
}
g.Go(func() error {
defer close(deps)
for pr := range a.directDeps {
logger.Printf("Caching package %q", pr)
deps <- pr
}
return nil
})
if err := g.Wait(); err != nil {
return err
}
logger.Printf("Successfully cached all deps.")
return nil
}
func (a *rootAnalyzer) importManifestAndLock(dir string, pr gps.ProjectRoot, suppressLogs bool) (*dep.Manifest, *dep.Lock) {
logger := a.ctx.Err
if suppressLogs {
logger = log.New(ioutil.Discard, "", 0)
}
for _, i := range importers.BuildAll(logger, a.ctx.Verbose, a.sm) {
if i.HasDepMetadata(dir) {
a.ctx.Err.Printf("Importing configuration from %s. These are only initial constraints, and are further refined during the solve process.", i.Name())
m, l, err := i.Import(dir, pr)
if err != nil {
a.ctx.Err.Printf(
"Warning: Encountered an unrecoverable error while trying to import %s config from %q: %s",
i.Name(), dir, err,
)
break
}
a.removeTransitiveDependencies(m)
return m, l
}
}
var emptyManifest = dep.NewManifest()
return emptyManifest, nil
}
func (a *rootAnalyzer) removeTransitiveDependencies(m *dep.Manifest) {
for pr := range m.Constraints {
if _, isDirect := a.directDeps[pr]; !isDirect {
delete(m.Constraints, pr)
}
}
}
// DeriveManifestAndLock evaluates a dependency for existing dependency manager
// configuration (ours or external) and passes any configuration found back
// to the solver.
func (a *rootAnalyzer) DeriveManifestAndLock(dir string, pr gps.ProjectRoot) (gps.Manifest, gps.Lock, error) {
// Ignore other tools if we find dep configuration
var depAnalyzer dep.Analyzer
if depAnalyzer.HasDepMetadata(dir) {
return depAnalyzer.DeriveManifestAndLock(dir, pr)
}
if !a.skipTools {
// The assignment back to an interface prevents interface-based nil checks from failing later
var manifest gps.Manifest = gps.SimpleManifest{}
var lock gps.Lock
im, il := a.importManifestAndLock(dir, pr, true)
if im != nil {
manifest = im
}
if il != nil {
lock = il
}
return manifest, lock, nil
}
return gps.SimpleManifest{}, nil, nil
}
func (a *rootAnalyzer) FinalizeRootManifestAndLock(m *dep.Manifest, l *dep.Lock, ol dep.Lock) {
// Iterate through the new projects in solved lock and add them to manifest
// if they are direct deps and log feedback for all the new projects.
diff := fb.DiffLocks(&ol, l)
bi := fb.NewBrokenImportFeedback(diff)
bi.LogFeedback(a.ctx.Err)
for _, y := range l.Projects() {
var f *fb.ConstraintFeedback
pr := y.Ident().ProjectRoot
// New constraints: in new lock and dir dep but not in manifest
if _, ok := a.directDeps[pr]; ok {
if _, ok := m.Constraints[pr]; !ok {
pp := getProjectPropertiesFromVersion(y.Version())
if pp.Constraint != nil {
m.Constraints[pr] = pp
pc := gps.ProjectConstraint{Ident: y.Ident(), Constraint: pp.Constraint}
f = fb.NewConstraintFeedback(pc, fb.DepTypeDirect)
f.LogFeedback(a.ctx.Err)
}
f = fb.NewLockedProjectFeedback(y, fb.DepTypeDirect)
f.LogFeedback(a.ctx.Err)
}
} else {
// New locked projects: in new lock but not in old lock
newProject := true
for _, opl := range ol.Projects() {
if pr == opl.Ident().ProjectRoot {
newProject = false
}
}
if newProject {
f = fb.NewLockedProjectFeedback(y, fb.DepTypeTransitive)
f.LogFeedback(a.ctx.Err)
}
}
}
}
// Info provides metadata on the analyzer algorithm used during solve.
func (a *rootAnalyzer) Info() gps.ProjectAnalyzerInfo {
return gps.ProjectAnalyzerInfo{
Name: "dep",
Version: 1,
}
}