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 }