github.com/kngu9/glide@v0.0.0-20160505135211-e73500c73591/tree/tree.go (about)

     1  package tree
     2  
     3  import (
     4  	"container/list"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/Masterminds/glide/dependency"
    10  	"github.com/Masterminds/glide/msg"
    11  	gpath "github.com/Masterminds/glide/path"
    12  	"github.com/Masterminds/glide/util"
    13  )
    14  
    15  // Display displays a tree view of the given project.
    16  //
    17  // FIXME: The output formatting could use some TLC.
    18  func Display(b *util.BuildCtxt, basedir, myName string, level int, core bool, l *list.List) {
    19  	deps := walkDeps(b, basedir, myName)
    20  	for _, name := range deps {
    21  		found := findPkg(b, name, basedir)
    22  		if found.Loc == dependency.LocUnknown {
    23  			m := "glide get " + found.Name
    24  			msg.Puts("\t%s\t(%s)", found.Name, m)
    25  			continue
    26  		}
    27  		if !core && found.Loc == dependency.LocGoroot || found.Loc == dependency.LocCgo {
    28  			continue
    29  		}
    30  		msg.Print(strings.Repeat("|\t", level-1) + "|-- ")
    31  
    32  		f := findInList(found.Name, l)
    33  		if f == true {
    34  			msg.Puts("(Recursion) %s   (%s)", found.Name, found.Path)
    35  		} else {
    36  			// Every branch in the tree is a copy to handle all the branches
    37  			cl := copyList(l)
    38  			cl.PushBack(found.Name)
    39  			msg.Puts("%s   (%s)", found.Name, found.Path)
    40  			Display(b, found.Path, found.Name, level+1, core, cl)
    41  		}
    42  	}
    43  }
    44  
    45  func walkDeps(b *util.BuildCtxt, base, myName string) []string {
    46  	externalDeps := []string{}
    47  	filepath.Walk(base, func(path string, fi os.FileInfo, err error) error {
    48  		if !dependency.IsSrcDir(fi) {
    49  			if fi.IsDir() {
    50  				return filepath.SkipDir
    51  			}
    52  			return nil
    53  		}
    54  
    55  		var imps []string
    56  		pkg, err := b.ImportDir(path, 0)
    57  		if err != nil && strings.HasPrefix(err.Error(), "found packages ") {
    58  			// If we got here it's because a package and multiple packages
    59  			// declared. This is often because of an example with a package
    60  			// or main but +build ignore as a build tag. In that case we
    61  			// try to brute force the packages with a slower scan.
    62  			imps, err = dependency.IterativeScan(path)
    63  			if err != nil {
    64  				msg.Err("Error walking dependencies for %s: %s", path, err)
    65  				return err
    66  			}
    67  		} else if err != nil {
    68  			if !strings.HasPrefix(err.Error(), "no buildable Go source") {
    69  				msg.Warn("Error: %s (%s)", err, path)
    70  				// Not sure if we should return here.
    71  				//return err
    72  			}
    73  		} else {
    74  			imps = pkg.Imports
    75  		}
    76  
    77  		if pkg.Goroot {
    78  			return nil
    79  		}
    80  
    81  		for _, imp := range imps {
    82  			//if strings.HasPrefix(imp, myName) {
    83  			////Info("Skipping %s because it is a subpackage of %s", imp, myName)
    84  			//continue
    85  			//}
    86  			if imp == myName {
    87  				continue
    88  			}
    89  			externalDeps = append(externalDeps, imp)
    90  		}
    91  
    92  		return nil
    93  	})
    94  	return externalDeps
    95  }
    96  
    97  func findPkg(b *util.BuildCtxt, name, cwd string) *dependency.PkgInfo {
    98  	var fi os.FileInfo
    99  	var err error
   100  	var p string
   101  
   102  	info := &dependency.PkgInfo{
   103  		Name: name,
   104  	}
   105  
   106  	if strings.HasPrefix(name, "./") || strings.HasPrefix(name, "../") {
   107  		info.Loc = dependency.LocRelative
   108  		return info
   109  	}
   110  
   111  	// Recurse backward to scan other vendor/ directories
   112  	// If the cwd isn't an absolute path walking upwards looking for vendor/
   113  	// folders can get into an infinate loop.
   114  	abs, err := filepath.Abs(cwd)
   115  	if err != nil {
   116  		abs = cwd
   117  	}
   118  	if abs != "." {
   119  		// Previously there was a check on the loop that wd := "/". The path
   120  		// "/" is a POSIX path so this fails on Windows. Now the check is to
   121  		// make sure the same wd isn't seen twice. When the same wd happens
   122  		// more than once it's the beginning of looping on the same location
   123  		// which is the top level.
   124  		pwd := ""
   125  		for wd := abs; wd != pwd; wd = filepath.Dir(wd) {
   126  			pwd = wd
   127  
   128  			// Don't look for packages outside the GOPATH
   129  			// Note, the GOPATH may or may not end with the path separator.
   130  			// The output of filepath.Dir does not the the path separator on the
   131  			// end so we need to test both.
   132  			if wd == b.GOPATH || wd+string(os.PathSeparator) == b.GOPATH {
   133  				break
   134  			}
   135  			p = filepath.Join(wd, "vendor", name)
   136  			if fi, err = os.Stat(p); err == nil && (fi.IsDir() || gpath.IsLink(fi)) {
   137  				info.Path = p
   138  				info.Loc = dependency.LocVendor
   139  				info.Vendored = true
   140  				return info
   141  			}
   142  		}
   143  	}
   144  	// Check $GOPATH
   145  	for _, r := range strings.Split(b.GOPATH, ":") {
   146  		p = filepath.Join(r, "src", name)
   147  		if fi, err = os.Stat(p); err == nil && (fi.IsDir() || gpath.IsLink(fi)) {
   148  			info.Path = p
   149  			info.Loc = dependency.LocGopath
   150  			return info
   151  		}
   152  	}
   153  
   154  	// Check $GOROOT
   155  	for _, r := range strings.Split(b.GOROOT, ":") {
   156  		p = filepath.Join(r, "src", name)
   157  		if fi, err = os.Stat(p); err == nil && (fi.IsDir() || gpath.IsLink(fi)) {
   158  			info.Path = p
   159  			info.Loc = dependency.LocGoroot
   160  			return info
   161  		}
   162  	}
   163  
   164  	// If this is "C", we're dealing with cgo
   165  	if name == "C" {
   166  		info.Loc = dependency.LocCgo
   167  	} else if name == "appengine" || name == "appengine_internal" ||
   168  		strings.HasPrefix(name, "appengine/") ||
   169  		strings.HasPrefix(name, "appengine_internal/") {
   170  		// Appengine is a special case when it comes to Go builds. It is a local
   171  		// looking package only available within appengine. It's a special case
   172  		// where Google products are playing with each other.
   173  		// https://blog.golang.org/the-app-engine-sdk-and-workspaces-gopath
   174  		info.Loc = dependency.LocAppengine
   175  	} else if name == "context" {
   176  		// context is a package being added to the Go 1.7 standard library. Some
   177  		// packages, such as golang.org/x/net are importing it with build flags
   178  		// in files for go1.7. Need to detect this and handle it.
   179  		info.Loc = dependency.LocGoroot
   180  	}
   181  
   182  	return info
   183  }
   184  
   185  // copyList copies an existing list to a new list.
   186  func copyList(l *list.List) *list.List {
   187  	n := list.New()
   188  	for e := l.Front(); e != nil; e = e.Next() {
   189  		n.PushBack(e.Value.(string))
   190  	}
   191  	return n
   192  }
   193  
   194  // findInList searches a list haystack for a string needle.
   195  func findInList(n string, l *list.List) bool {
   196  	for e := l.Front(); e != nil; e = e.Next() {
   197  		if e.Value.(string) == n {
   198  			return true
   199  		}
   200  	}
   201  
   202  	return false
   203  }