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  }