github.com/comcast/canticle@v0.0.0-20161108184242-c53cface56e8/canticles/depwalker.go (about) 1 package canticles 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "sort" 8 ) 9 10 // PkgReaderFunc takes a given package string and returns all 11 // the dependencies for that package. If error is not nil on 12 // return the walker halts and returns the error. 13 type PkgReaderFunc func(pkg string) ([]string, error) 14 15 // PkgHandlerFunc is called once for each loaded package. If the error 16 // ErrorSkip is returned deps or this package are no read. All other 17 // non nil errors halt the walker and return the value. 18 type PkgHandlerFunc func(pkg string) error 19 20 // ErrorSkip tells a walker to skip loading the deps of this dep. 21 var ErrorSkip = errors.New("skip this dep") 22 23 // DependencyWalker is used to walker the dependencies of a package. 24 // It will walk the dependencies for an import path only once. 25 type DependencyWalker struct { 26 nodeQueue []string 27 visited map[string]bool 28 readPackage PkgReaderFunc 29 handleDep PkgHandlerFunc 30 } 31 32 // NewDependencyWalker creates a new dep loader. It uses the 33 // specified depReader to load dependencies. It will call the handler 34 // with the resulting dependencies. 35 func NewDependencyWalker(reader PkgReaderFunc, handler PkgHandlerFunc) *DependencyWalker { 36 return &DependencyWalker{ 37 visited: make(map[string]bool), 38 handleDep: handler, 39 readPackage: reader, 40 } 41 } 42 43 // TraverseDependencies reads and loads all dependencies of dep. It is 44 // a breadth first search. If handler returns the special error 45 // ErrorSkip it does not read the deps of this package. 46 func (dw *DependencyWalker) TraverseDependencies(pkg string) error { 47 dw.nodeQueue = append(dw.nodeQueue, pkg) 48 for len(dw.nodeQueue) > 0 { 49 // Dequeue and mark loaded 50 p := dw.nodeQueue[0] 51 dw.nodeQueue = dw.nodeQueue[1:] 52 dw.visited[p] = true 53 LogVerbose("Handling pkg: %+v", p) 54 55 // Inform our handler of this package 56 err := dw.handleDep(p) 57 switch { 58 case err == ErrorSkip: 59 continue 60 case err != nil: 61 return err 62 } 63 64 // Read out our children 65 children, err := dw.readPackage(p) 66 if err != nil { 67 return fmt.Errorf("cant read deps of package %s with error %s", pkg, err.Error()) 68 } 69 sort.Strings(children) 70 LogVerbose("Package %s has children %v", p, children) 71 72 for _, child := range children { 73 if dw.visited[child] { 74 continue 75 } 76 dw.nodeQueue = append(dw.nodeQueue, child) 77 } 78 } 79 80 return nil 81 } 82 83 // A DependencyReader reads the set of deps for a package 84 type DependencyReader func(importPath string) (Dependencies, error) 85 86 // A DependencyLoader fetches and set the correct revision for a 87 // dependency using the specified resolver. 88 type DependencyLoader struct { 89 deps Dependencies 90 cdeps []*CanticleDependency 91 gopath string 92 resolver RepoResolver 93 readDeps DependencyReader 94 } 95 96 // NewDependencyLoader returns a DependencyLoader initialized with the 97 // resolver func. 98 func NewDependencyLoader(resolver RepoResolver, depReader DependencyReader, cdeps []*CanticleDependency, gopath string) *DependencyLoader { 99 return &DependencyLoader{ 100 deps: NewDependencies(), 101 readDeps: depReader, 102 resolver: resolver, 103 cdeps: cdeps, 104 gopath: gopath, 105 } 106 } 107 108 // TODO: This shares a ton of code with depsaver, look into that 109 110 // FetchUpdatePackage will fetch or set the specified path to the version 111 // defined by the Dependency or if no version is defined will use 112 // the VCS default. 113 func (dl *DependencyLoader) FetchUpdatePackage(pkg string) error { 114 LogVerbose("DepLoader handling pkg: %s", pkg) 115 path := PackageSource(dl.gopath, pkg) 116 117 // See if this path is on disk, if so we don't need to fetch anything 118 ondisk := true 119 s, err := os.Stat(path) 120 switch { 121 case err != nil && os.IsNotExist(err): 122 ondisk = false 123 case err != nil: 124 fmt.Errorf("cant fetch package error when stating import path %s", err.Error()) 125 case s != nil && !s.IsDir(): 126 return fmt.Errorf("cant fetch pkg for path %s is a file not a directory", path) 127 } 128 129 // Fetch the package 130 LogVerbose("DepLoader check path: %s", path) 131 if !ondisk { 132 // Resolve the vcs using our cdep if available 133 cdep := dl.cdepForPkg(pkg) 134 LogVerbose("Resolving repo for %s ondisk %v path %s", pkg, ondisk, path) 135 vcs, err := dl.resolver.ResolveRepo(pkg, cdep) 136 if err != nil { 137 return fmt.Errorf("%s version control %s", pkg, err.Error()) 138 } 139 140 if err := dl.fetchPackage(vcs, cdep); err != nil { 141 return fmt.Errorf("cant fetch package %s %s", pkg, err.Error()) 142 } 143 } 144 145 // Load all the deps for this file directly 146 LogVerbose("DepLoader reading deps of path: %s", path) 147 deps, err := dl.readDeps(path) 148 if err != nil { 149 return fmt.Errorf("package %s couldn't read deps %s", pkg, err.Error()) 150 } 151 LogVerbose("Read package %s deps:\n[\n%+v]", pkg, deps) 152 153 // Setup our deps 154 dep := NewDependency(pkg) 155 for _, d := range deps { 156 d.ImportedFrom.Add(pkg) 157 } 158 dl.deps.AddDependencies(deps) 159 for _, pkgDep := range deps { 160 dep.Imports.Add(pkgDep.ImportPath) 161 } 162 LogVerbose("Adding dep %+v\n", dep) 163 dl.deps.AddDependency(dep) 164 165 return nil 166 } 167 168 func (dl *DependencyLoader) cdepForPkg(pkg string) *CanticleDependency { 169 for _, dep := range dl.cdeps { 170 if PathIsChild(dep.Root, pkg) { 171 return dep 172 } 173 } 174 return nil 175 } 176 177 // PackagePaths determines the set of import paths for package. 178 func (dl *DependencyLoader) PackageImports(pkg string) ([]string, error) { 179 dep := dl.deps.Dependency(pkg) 180 if dep == nil { 181 return []string{}, fmt.Errorf("no dep for %s, should not be requested", pkg) 182 } 183 return dep.Imports.Array(), nil 184 } 185 186 func (dl *DependencyLoader) setRevision(vcs VCS, dep *CanticleDependency) error { 187 LogVerbose("Setting rev on dep %+v", dep) 188 if err := vcs.SetRev(""); err != nil { 189 return fmt.Errorf("failed to set revision because %s", err.Error()) 190 } 191 return nil 192 } 193 194 func (dl *DependencyLoader) fetchPackage(vcs VCS, dep *CanticleDependency) error { 195 LogVerbose("Fetching dep %+v", dep) 196 if err := vcs.Create(""); err != nil { 197 return fmt.Errorf("failed to fetch because %s", err.Error()) 198 } 199 return nil 200 } 201 202 type DepReaderFunc func(importPath string) (Dependencies, error) 203 204 // DependencySaver is a handler for dependencies that will save all 205 // dependencies current revisions. Call Dependencies() to retrieve the 206 // loaded Dependencies. 207 type DependencySaver struct { 208 deps Dependencies 209 gopath string 210 root string 211 read DepReaderFunc 212 // NoRecur contains a list of directories this will not recur 213 // into under root. 214 NoRecur StringSet 215 } 216 217 // NewDependencySaver builds a new dependencysaver to work in the 218 // specified gopath and resolve using the resolverfunc. A 219 // DependencySaver should generally only be used once. A 220 // DependencySaver will not attempt to load remote dependencies even 221 // if the resolverfunc can handle them. Deps that resolve using ignore 222 // will not be saved. 223 func NewDependencySaver(reader DepReaderFunc, gopath, root string) *DependencySaver { 224 return &DependencySaver{ 225 deps: NewDependencies(), 226 root: root, 227 read: reader, 228 gopath: gopath, 229 NoRecur: NewStringSet(), 230 } 231 } 232 233 // SavePackageDeps uses the reader to read all 1st order deps of this 234 // pkg. 235 func (ds *DependencySaver) SavePackageDeps(path string) error { 236 LogVerbose("Examine path %s", path) 237 pkg, err := PackageName(ds.gopath, path) 238 if err != nil { 239 return fmt.Errorf("Error getting package name for path %s", path) 240 } 241 242 // Check if we can find this package 243 s, err := os.Stat(path) 244 switch { 245 case s != nil && !s.IsDir(): 246 err = fmt.Errorf("cant save deps for path %s is a file not a directory", path) 247 case err != nil && os.IsNotExist(err): 248 err = fmt.Errorf("cant save deps for path %s could not be found on disk", path) 249 case err != nil: 250 err = fmt.Errorf("cant save deps for path %s due to %s", path, err.Error()) 251 } 252 if err != nil { 253 LogVerbose("Error stating path %s %s", path, err.Error()) 254 dep := NewDependency(pkg) 255 dep.Err = err 256 ds.deps.AddDependency(dep) 257 return ErrorSkip 258 } 259 // Don't attempt to read the dependencies of the "src" dir... 260 if path == PackageSource(ds.gopath, "") { 261 return nil 262 } 263 264 // If we get back a no buildable with no read imports return 265 // nil (this is an empty dir, so we don't want it in our 266 // package setup). If we have any pkgDeps though (from a cant file) 267 // we need this. 268 pkgDeps, err := ds.read(path) 269 if len(pkgDeps) == 0 && err != nil { 270 if e, ok := err.(*PackageError); ok { 271 if e.IsNoBuildable() { 272 LogVerbose("Unbuildable pkg") 273 return nil 274 } 275 } 276 LogVerbose("Error reading pkg deps %s %s", pkg, err.Error()) 277 dep := NewDependency(pkg) 278 dep.Err = fmt.Errorf("cant read deps for package %s %s", pkg, err.Error()) 279 ds.deps.AddDependency(dep) 280 return nil 281 } 282 283 dep := NewDependency(pkg) 284 for _, d := range pkgDeps { 285 d.ImportedFrom.Add(pkg) 286 } 287 ds.deps.AddDependencies(pkgDeps) 288 for _, pkgDep := range pkgDeps { 289 dep.Imports.Add(pkgDep.ImportPath) 290 } 291 LogVerbose("Adding dep for pkg %v", dep) 292 ds.deps.AddDependency(dep) 293 return nil 294 } 295 296 // PackagePaths returns d all import paths for a pkg, and all subdirs 297 // if the pkg is under the root of the passed to the ds at construction. 298 func (ds *DependencySaver) PackagePaths(path string) ([]string, error) { 299 paths := NewStringSet() 300 if PathIsChild(ds.root, path) { 301 subdirs, err := VisibleSubDirectories(path) 302 if err != nil { 303 return []string{}, err 304 } 305 paths.Add(subdirs...) 306 LogVerbose("Package has subdirs %v", subdirs) 307 } 308 paths.Difference(ds.NoRecur) 309 pkg, err := PackageName(ds.gopath, path) 310 if err != nil { 311 LogVerbose("Package name error %s", err.Error()) 312 return []string{}, err 313 } 314 dep := ds.deps.Dependency(pkg) 315 if dep == nil { 316 LogVerbose("Package has no dep %s", pkg) 317 return paths.Array(), nil 318 } 319 if dep.Err != nil { 320 LogVerbose("Package dep err not nil %s %v", pkg, dep.Err) 321 return []string{}, nil 322 } 323 imports := dep.Imports.Array() 324 for _, imp := range imports { 325 paths.Add(PackageSource(ds.gopath, imp)) 326 } 327 LogVerbose("Package has imports %v", imports) 328 return paths.Array(), nil 329 } 330 331 // Dependencies returns the resolved dependencies from dependency 332 // saver. 333 func (ds *DependencySaver) Dependencies() Dependencies { 334 return ds.deps 335 }