github.com/golang/dep@v0.5.4/cmd/dep/gopath_scanner.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 "fmt" 9 "os" 10 "path/filepath" 11 "strings" 12 "sync" 13 14 "github.com/golang/dep" 15 "github.com/golang/dep/gps" 16 "github.com/golang/dep/gps/paths" 17 "github.com/golang/dep/gps/pkgtree" 18 fb "github.com/golang/dep/internal/feedback" 19 "github.com/golang/dep/internal/fs" 20 "github.com/pkg/errors" 21 ) 22 23 // gopathScanner supplies manifest/lock data by scanning the contents of GOPATH 24 // It uses its results to fill-in any missing details left by the rootAnalyzer. 25 type gopathScanner struct { 26 ctx *dep.Ctx 27 directDeps map[gps.ProjectRoot]bool 28 sm gps.SourceManager 29 30 pd projectData 31 origM *dep.Manifest 32 origL *dep.Lock 33 } 34 35 func newGopathScanner(ctx *dep.Ctx, directDeps map[gps.ProjectRoot]bool, sm gps.SourceManager) *gopathScanner { 36 return &gopathScanner{ 37 ctx: ctx, 38 directDeps: directDeps, 39 sm: sm, 40 } 41 } 42 43 // InitializeRootManifestAndLock performs analysis of the filesystem tree rooted 44 // at path, with the root import path importRoot, to determine the project's 45 // constraints. Respect any initial constraints defined in the root manifest and 46 // lock. 47 func (g *gopathScanner) InitializeRootManifestAndLock(rootM *dep.Manifest, rootL *dep.Lock) error { 48 var err error 49 50 g.ctx.Err.Println("Searching GOPATH for projects...") 51 g.pd, err = g.scanGopathForDependencies() 52 if err != nil { 53 return err 54 } 55 56 g.origM = dep.NewManifest() 57 g.origM.Constraints = g.pd.constraints 58 59 g.origL = &dep.Lock{ 60 P: make([]gps.LockedProject, 0, len(g.pd.ondisk)), 61 } 62 63 for pr, v := range g.pd.ondisk { 64 // That we have to chop off these path prefixes is a symptom of 65 // a problem in gps itself 66 pkgs := make([]string, 0, len(g.pd.dependencies[pr])) 67 prslash := string(pr) + "/" 68 for _, pkg := range g.pd.dependencies[pr] { 69 if pkg == string(pr) { 70 pkgs = append(pkgs, ".") 71 } else { 72 pkgs = append(pkgs, trimPathPrefix(pkg, prslash)) 73 } 74 } 75 76 g.origL.P = append(g.origL.P, gps.NewLockedProject( 77 gps.ProjectIdentifier{ProjectRoot: pr}, v, pkgs), 78 ) 79 } 80 81 g.overlay(rootM, rootL) 82 83 return nil 84 } 85 86 // Fill in gaps in the root manifest/lock with data found from the GOPATH. 87 func (g *gopathScanner) overlay(rootM *dep.Manifest, rootL *dep.Lock) { 88 for pkg, prj := range g.origM.Constraints { 89 if _, has := rootM.Constraints[pkg]; has { 90 continue 91 } 92 rootM.Constraints[pkg] = prj 93 v := g.pd.ondisk[pkg] 94 95 pi := gps.ProjectIdentifier{ProjectRoot: pkg, Source: prj.Source} 96 f := fb.NewConstraintFeedback(gps.ProjectConstraint{Ident: pi, Constraint: v}, fb.DepTypeDirect) 97 f.LogFeedback(g.ctx.Err) 98 f = fb.NewLockedProjectFeedback(gps.NewLockedProject(pi, v, nil), fb.DepTypeDirect) 99 f.LogFeedback(g.ctx.Err) 100 } 101 102 // Keep track of which projects have been locked 103 lockedProjects := map[gps.ProjectRoot]bool{} 104 for _, lp := range rootL.P { 105 lockedProjects[lp.Ident().ProjectRoot] = true 106 } 107 108 for _, lp := range g.origL.P { 109 pkg := lp.Ident().ProjectRoot 110 if _, isLocked := lockedProjects[pkg]; isLocked { 111 continue 112 } 113 rootL.P = append(rootL.P, lp) 114 lockedProjects[pkg] = true 115 116 if _, isDirect := g.directDeps[pkg]; !isDirect { 117 f := fb.NewLockedProjectFeedback(lp, fb.DepTypeTransitive) 118 f.LogFeedback(g.ctx.Err) 119 } 120 } 121 122 // Identify projects whose version is unknown and will have to be solved for 123 var missing []string // all project roots missing from GOPATH 124 var missingVCS []string // all project roots missing VCS information 125 for pr := range g.pd.notondisk { 126 if _, isLocked := lockedProjects[pr]; isLocked { 127 continue 128 } 129 if g.pd.invalidSVC[pr] { 130 missingVCS = append(missingVCS, string(pr)) 131 } else { 132 missing = append(missing, string(pr)) 133 } 134 } 135 136 missingStr := "" 137 missingVCSStr := "" 138 if len(missing) > 0 { 139 missingStr = fmt.Sprintf("The following dependencies were not found in GOPATH:\n %s\n\n", 140 strings.Join(missing, "\n ")) 141 } 142 if len(missingVCS) > 0 { 143 missingVCSStr = fmt.Sprintf("The following dependencies found in GOPATH were missing VCS information (a remote source is required):\n %s\n\n", 144 strings.Join(missingVCS, "\n ")) 145 } 146 if len(missingVCS)+len(missing) > 0 { 147 g.ctx.Err.Printf("\n%s%sThe most recent version of these projects will be used.\n\n", missingStr, missingVCSStr) 148 } 149 } 150 151 func trimPathPrefix(p1, p2 string) string { 152 if isPrefix, _ := fs.HasFilepathPrefix(p1, p2); isPrefix { 153 return p1[len(p2):] 154 } 155 return p1 156 } 157 158 // contains checks if a array of strings contains a value 159 func contains(a []string, b string) bool { 160 for _, v := range a { 161 if b == v { 162 return true 163 } 164 } 165 return false 166 } 167 168 // getProjectPropertiesFromVersion takes a Version and returns a proper 169 // ProjectProperties with Constraint value based on the provided version. 170 func getProjectPropertiesFromVersion(v gps.Version) gps.ProjectProperties { 171 pp := gps.ProjectProperties{} 172 173 // extract version and ignore if it's revision only 174 switch tv := v.(type) { 175 case gps.PairedVersion: 176 v = tv.Unpair() 177 case gps.Revision: 178 return pp 179 } 180 181 switch v.Type() { 182 case gps.IsBranch, gps.IsVersion: 183 pp.Constraint = v 184 case gps.IsSemver: 185 c, err := gps.NewSemverConstraintIC(v.String()) 186 if err != nil { 187 panic(err) 188 } 189 pp.Constraint = c 190 } 191 192 return pp 193 } 194 195 type projectData struct { 196 constraints gps.ProjectConstraints // constraints that could be found 197 dependencies map[gps.ProjectRoot][]string // all dependencies (imports) found by project root 198 notondisk map[gps.ProjectRoot]bool // projects that were not found on disk 199 invalidSVC map[gps.ProjectRoot]bool // projects that were found on disk but SVC data could not be read 200 ondisk map[gps.ProjectRoot]gps.Version // projects that were found on disk 201 } 202 203 func (g *gopathScanner) scanGopathForDependencies() (projectData, error) { 204 constraints := make(gps.ProjectConstraints) 205 dependencies := make(map[gps.ProjectRoot][]string) 206 packages := make(map[string]bool) 207 notondisk := make(map[gps.ProjectRoot]bool) 208 invalidSVC := make(map[gps.ProjectRoot]bool) 209 ondisk := make(map[gps.ProjectRoot]gps.Version) 210 211 var syncDepGroup sync.WaitGroup 212 syncDep := func(pr gps.ProjectRoot, sm gps.SourceManager) { 213 if err := sm.SyncSourceFor(gps.ProjectIdentifier{ProjectRoot: pr}); err != nil { 214 g.ctx.Err.Printf("%+v", errors.Wrapf(err, "Unable to cache %s", pr)) 215 } 216 syncDepGroup.Done() 217 } 218 219 if len(g.directDeps) == 0 { 220 return projectData{}, nil 221 } 222 223 for ippr := range g.directDeps { 224 // TODO(sdboyer) these are not import paths by this point, they've 225 // already been worked down to project roots. 226 ip := string(ippr) 227 pr, err := g.sm.DeduceProjectRoot(ip) 228 if err != nil { 229 return projectData{}, errors.Wrap(err, "sm.DeduceProjectRoot") 230 } 231 232 packages[ip] = true 233 if _, has := dependencies[pr]; has { 234 dependencies[pr] = append(dependencies[pr], ip) 235 continue 236 } 237 syncDepGroup.Add(1) 238 go syncDep(pr, g.sm) 239 240 dependencies[pr] = []string{ip} 241 abs, err := g.ctx.AbsForImport(string(pr)) 242 if err != nil { 243 notondisk[pr] = true 244 continue 245 } 246 v, err := gps.VCSVersion(abs) 247 if err != nil { 248 invalidSVC[pr] = true 249 notondisk[pr] = true 250 continue 251 } 252 253 ondisk[pr] = v 254 pp := getProjectPropertiesFromVersion(v) 255 if pp.Constraint != nil || pp.Source != "" { 256 constraints[pr] = pp 257 } 258 } 259 260 // Explore the packages we've found for transitive deps, either 261 // completing the lock or identifying (more) missing projects that we'll 262 // need to ask gps to solve for us. 263 colors := make(map[string]uint8) 264 const ( 265 white uint8 = iota 266 grey 267 black 268 ) 269 270 // cache of PackageTrees, so we don't parse projects more than once 271 ptrees := make(map[gps.ProjectRoot]pkgtree.PackageTree) 272 273 // depth-first traverser 274 var dft func(string) error 275 dft = func(pkg string) error { 276 switch colors[pkg] { 277 case white: 278 colors[pkg] = grey 279 280 pr, err := g.sm.DeduceProjectRoot(pkg) 281 if err != nil { 282 return errors.Wrap(err, "could not deduce project root for "+pkg) 283 } 284 285 // We already visited this project root earlier via some other 286 // pkg within it, and made the decision that it's not on disk. 287 // Respect that decision, and pop the stack. 288 if notondisk[pr] { 289 colors[pkg] = black 290 return nil 291 } 292 293 ptree, has := ptrees[pr] 294 if !has { 295 // It's fine if the root does not exist - it indicates that this 296 // project is not present in the workspace, and so we need to 297 // solve to deal with this dep. 298 r := filepath.Join(g.ctx.GOPATH, "src", string(pr)) 299 fi, err := os.Stat(r) 300 if os.IsNotExist(err) || !fi.IsDir() { 301 colors[pkg] = black 302 notondisk[pr] = true 303 return nil 304 } 305 306 // We know the project is on disk; the question is whether we're 307 // first seeing it here, in the transitive exploration, or if it 308 // was found in the initial pass on direct imports. We know it's 309 // the former if there's no entry for it in the ondisk map. 310 if _, in := ondisk[pr]; !in { 311 abs, err := g.ctx.AbsForImport(string(pr)) 312 if err != nil { 313 colors[pkg] = black 314 notondisk[pr] = true 315 return nil 316 } 317 v, err := gps.VCSVersion(abs) 318 if err != nil { 319 // Even if we know it's on disk, errors are still 320 // possible when trying to deduce version. If we 321 // encounter such an error, just treat the project as 322 // not being on disk; the solver will work it out. 323 colors[pkg] = black 324 notondisk[pr] = true 325 return nil 326 } 327 ondisk[pr] = v 328 } 329 330 ptree, err = pkgtree.ListPackages(r, string(pr)) 331 if err != nil { 332 // Any error here other than an a nonexistent dir (which 333 // can't happen because we covered that case above) is 334 // probably critical, so bail out. 335 return errors.Wrap(err, "gps.ListPackages") 336 } 337 ptrees[pr] = ptree 338 } 339 340 // Get a reachmap that includes main pkgs (even though importing 341 // them is an error, what we're checking right now is simply whether 342 // there's a package with go code present on disk), and does not 343 // backpropagate errors (again, because our only concern right now 344 // is package existence). 345 rm, errmap := ptree.ToReachMap(true, false, false, nil) 346 reached, ok := rm[pkg] 347 if !ok { 348 colors[pkg] = black 349 // not on disk... 350 notondisk[pr] = true 351 return nil 352 } 353 if _, ok := errmap[pkg]; ok { 354 // The package is on disk, but contains some errors. 355 colors[pkg] = black 356 return nil 357 } 358 359 if deps, has := dependencies[pr]; has { 360 if !contains(deps, pkg) { 361 dependencies[pr] = append(deps, pkg) 362 } 363 } else { 364 dependencies[pr] = []string{pkg} 365 syncDepGroup.Add(1) 366 go syncDep(pr, g.sm) 367 } 368 369 // recurse 370 for _, rpkg := range reached.External { 371 if paths.IsStandardImportPath(rpkg) { 372 continue 373 } 374 375 err := dft(rpkg) 376 if err != nil { 377 // Bubble up any errors we encounter 378 return err 379 } 380 } 381 382 colors[pkg] = black 383 case grey: 384 return errors.Errorf("Import cycle detected on %s", pkg) 385 } 386 return nil 387 } 388 389 // run the depth-first traversal from the set of immediate external 390 // package imports we found in the current project 391 for pkg := range packages { 392 err := dft(pkg) 393 if err != nil { 394 return projectData{}, err // already errors.Wrap()'d internally 395 } 396 } 397 398 syncDepGroup.Wait() 399 400 pd := projectData{ 401 constraints: constraints, 402 dependencies: dependencies, 403 invalidSVC: invalidSVC, 404 notondisk: notondisk, 405 ondisk: ondisk, 406 } 407 return pd, nil 408 }