Merge pull request #240 from Masterminds/feat/resolve-imports

Resolve the imports being used
diff --git a/action/list.go b/action/list.go
index 067e3ca..a2d35c7 100644
--- a/action/list.go
+++ b/action/list.go
@@ -32,13 +32,14 @@
 		msg.Die("Error listing dependencies: %s", err)
 	}
 
+	msg.Info("Sorting...")
 	sort.Strings(sortable)
 
 	msg.Puts("INSTALLED packages:")
 	for _, k := range sortable {
 		v, err := filepath.Rel(basedir, k)
 		if err != nil {
-			msg.Warn("Failed to Rel path: %s", err)
+			//msg.Warn("Failed to Rel path: %s", err)
 			v = k
 		}
 		msg.Puts("\t%s", v)
diff --git a/dependency/resolver.go b/dependency/resolver.go
index e08f738..e76f358 100644
--- a/dependency/resolver.go
+++ b/dependency/resolver.go
@@ -2,6 +2,7 @@
 
 import (
 	"container/list"
+	//"go/build"
 	"os"
 	"path/filepath"
 	"strings"
@@ -115,15 +116,21 @@
 type Resolver struct {
 	Handler        MissingPackageHandler
 	VersionHandler VersionHandler
-	basedir        string
 	VendorDir      string
 	BuildContext   *util.BuildCtxt
-	seen           map[string]bool
 	Config         *cfg.Config
 
+	// ResolveAllFiles toggles deep scanning.
+	// If this is true, resolve by scanning all files, not by walking the
+	// import tree.
+	ResolveAllFiles bool
+
 	// Items already in the queue.
 	alreadyQ map[string]bool
 
+	basedir string
+	seen    map[string]bool
+
 	// findCache caches hits from Find. This reduces the number of filesystem
 	// touches that have to be done for dependency resolution.
 	findCache map[string]*PkgInfo
@@ -185,7 +192,12 @@
 	//msg.Debug("Scanning %s", target)
 	l := list.New()
 	l.PushBack(target)
-	return r.resolveList(l)
+
+	// In this mode, walk the entire tree.
+	if r.ResolveAllFiles {
+		return r.resolveList(l)
+	}
+	return r.resolveImports(l)
 }
 
 // ResolveLocal resolves dependencies for the current project.
@@ -251,7 +263,10 @@
 	}
 
 	if deep {
-		return r.resolveList(l)
+		if r.ResolveAllFiles {
+			return r.resolveList(l)
+		}
+		return r.resolveImports(l)
 	}
 
 	// If we're not doing a deep scan, we just convert the list into an
@@ -277,16 +292,164 @@
 // an error is returned.
 func (r *Resolver) ResolveAll(deps []*cfg.Dependency) ([]string, error) {
 	queue := sliceToQueue(deps, r.VendorDir)
-	return r.resolveList(queue)
+
+	loc, err := r.ResolveLocal(false)
+	if err != nil {
+		return []string{}, err
+	}
+	for _, l := range loc {
+		msg.Debug("Adding local mport %s to queue", l)
+		queue.PushBack(l)
+	}
+
+	if r.ResolveAllFiles {
+		return r.resolveList(queue)
+	}
+	return r.resolveImports(queue)
+}
+
+// stripv strips the vendor/ prefix from vendored packages.
+func (r *Resolver) stripv(str string) string {
+	return strings.TrimPrefix(str, r.VendorDir+string(os.PathSeparator))
+}
+
+// vpath adds an absolute vendor path.
+func (r *Resolver) vpath(str string) string {
+	return filepath.Join(r.basedir, "vendor", str)
+}
+
+// resolveImports takes a list of existing packages and resolves their imports.
+//
+// It returns a list of all of the packages that it can determine are required
+// for the given code to function.
+//
+// The expectation is that each item in the queue is an absolute path to a
+// vendored package. This attempts to read that package, and then find
+// its referenced packages. Those packages are then added to the list
+// to be scanned next.
+//
+// The resolver's handler is used in the cases where a package cannot be
+// located.
+func (r *Resolver) resolveImports(queue *list.List) ([]string, error) {
+	for e := queue.Front(); e != nil; e = e.Next() {
+		vdep := e.Value.(string)
+		dep := r.stripv(vdep)
+
+		r.alreadyQ[dep] = true
+
+		// Skip ignored packages
+		if r.Config.HasIgnore(dep) {
+			msg.Info("Ignoring: %s", dep)
+			continue
+		}
+		r.VersionHandler.Process(dep)
+
+		// Here, we want to import the package and see what imports it has.
+		msg.Debug("Trying to open %s", vdep)
+		pkg, err := r.BuildContext.ImportDir(vdep, 0)
+		if err != nil {
+			msg.Warn("ImportDir error on %s: %s", vdep, err)
+			if strings.HasPrefix(err.Error(), "no buildable Go source") {
+				msg.Info("No subpackages declared. Skipping %s.", dep)
+				continue
+			}
+			if ok, err := r.Handler.NotFound(dep); ok {
+				r.alreadyQ[dep] = true
+				queue.PushBack(r.vpath(dep))
+				r.VersionHandler.SetVersion(dep)
+			} else if err != nil {
+				msg.Warn("Error looking for %s: %s", dep, err)
+			} else {
+				// TODO (mpb): Should we toss this into a Handler to
+				// see if this is on GOPATH and copy it?
+				msg.Info("Not found in vendor/: %s (1)", dep)
+			}
+			continue
+		}
+
+		// Range over all of the identified imports and see which ones we
+		// can locate.
+		for _, imp := range pkg.Imports {
+			pi := r.FindPkg(imp)
+			if pi.Loc != LocCgo && pi.Loc != LocGoroot {
+				msg.Debug("Package %s imports %s", dep, imp)
+			}
+			switch pi.Loc {
+			case LocVendor:
+				msg.Debug("In vendor: %s", imp)
+				if _, ok := r.alreadyQ[imp]; !ok {
+					msg.Debug("Marking %s to be scanned.", imp)
+					r.alreadyQ[imp] = true
+					queue.PushBack(r.vpath(imp))
+					r.VersionHandler.SetVersion(imp)
+				}
+			case LocUnknown:
+				msg.Debug("Missing %s. Trying to resolve.", imp)
+				if ok, err := r.Handler.NotFound(imp); ok {
+					r.alreadyQ[imp] = true
+					queue.PushBack(r.vpath(imp))
+					r.VersionHandler.SetVersion(imp)
+				} else if err != nil {
+					msg.Warn("Error looking for %s: %s", imp, err)
+				} else {
+					msg.Info("Not found: %s (2)", imp)
+				}
+			case LocGopath:
+				msg.Info("Found on GOPATH, not vendor: %s", imp)
+				if _, ok := r.alreadyQ[imp]; !ok {
+					// Only scan it if it gets moved into vendor/
+					if ok, _ := r.Handler.OnGopath(imp); ok {
+						r.alreadyQ[imp] = true
+						queue.PushBack(r.vpath(imp))
+						r.VersionHandler.SetVersion(imp)
+					}
+				}
+			}
+		}
+
+	}
+
+	// FIXME: From here to the end is a straight copy of the resolveList() func.
+	res := make([]string, 0, queue.Len())
+
+	// In addition to generating a list
+	for e := queue.Front(); e != nil; e = e.Next() {
+		t := r.stripv(e.Value.(string))
+		root, sp := util.NormalizeName(t)
+
+		// TODO(mattfarina): Need to eventually support devImport
+		existing := r.Config.Imports.Get(root)
+		if existing != nil {
+			if sp != "" && !existing.HasSubpackage(sp) {
+				existing.Subpackages = append(existing.Subpackages, sp)
+			}
+		} else {
+			newDep := &cfg.Dependency{
+				Name: root,
+			}
+			if sp != "" {
+				newDep.Subpackages = []string{sp}
+			}
+
+			r.Config.Imports = append(r.Config.Imports, newDep)
+		}
+		res = append(res, t)
+	}
+
+	return res, nil
 }
 
 // resolveList takes a list and resolves it.
+//
+// This walks the entire file tree for the given dependencies, not just the
+// parts that are imported directly. Using this will discover dependencies
+// regardless of OS, and arch.
 func (r *Resolver) resolveList(queue *list.List) ([]string, error) {
 
 	var failedDep string
 	for e := queue.Front(); e != nil; e = e.Next() {
 		dep := e.Value.(string)
-		t := strings.TrimPrefix(e.Value.(string), r.VendorDir+string(os.PathSeparator))
+		t := strings.TrimPrefix(dep, r.VendorDir+string(os.PathSeparator))
 		if r.Config.HasIgnore(t) {
 			msg.Info("Ignoring: %s", t)
 			continue
diff --git a/dependency/resolver_test.go b/dependency/resolver_test.go
index 0edb764..fa4ab6e 100644
--- a/dependency/resolver_test.go
+++ b/dependency/resolver_test.go
@@ -72,6 +72,10 @@
 	if len(l) != 1 {
 		t.Errorf("Expected 1 dep, got %d: %s", len(l), l[0])
 	}
+
+	if !strings.HasSuffix("github.com/codegangsta/cli", l[0]) {
+		t.Errorf("Unexpected package name: %s", l[0])
+	}
 }
 
 func TestResolveAll(t *testing.T) {
diff --git a/glide.go b/glide.go
index eb3b61b..714da14 100644
--- a/glide.go
+++ b/glide.go
@@ -183,6 +183,10 @@
 					Usage: "If there was a change in the repo or VCS switch to new one. Warning, changes will be lost.",
 				},
 				cli.BoolFlag{
+					Name:  "all-dependencies",
+					Usage: "This will resolve all dependencies for all packages, not just those directly used.",
+				},
+				cli.BoolFlag{
 					Name:  "update-vendored, u",
 					Usage: "Update vendored packages (without local VCS repo). Warning, changes will be lost.",
 				},
@@ -206,11 +210,12 @@
 				}
 
 				inst := &repo.Installer{
-					Force:          c.Bool("force"),
-					UseCache:       c.Bool("cache"),
-					UseGopath:      c.Bool("use-gopath"),
-					UseCacheGopath: c.Bool("cache-gopath"),
-					UpdateVendored: c.Bool("update-vendored"),
+					Force:           c.Bool("force"),
+					UseCache:        c.Bool("cache"),
+					UseGopath:       c.Bool("use-gopath"),
+					UseCacheGopath:  c.Bool("cache-gopath"),
+					UpdateVendored:  c.Bool("update-vendored"),
+					ResolveAllFiles: c.Bool("all-dependencies"),
 				}
 				packages := []string(c.Args())
 				insecure := c.Bool("insecure")
@@ -437,6 +442,10 @@
 					Usage: "If there was a change in the repo or VCS switch to new one. Warning, changes will be lost.",
 				},
 				cli.BoolFlag{
+					Name:  "all-dependencies",
+					Usage: "This will resolve all dependencies for all packages, not just those directly used.",
+				},
+				cli.BoolFlag{
 					Name:  "update-vendored, u",
 					Usage: "Update vendored packages (without local VCS repo). Warning, changes will be lost.",
 				},
@@ -459,13 +468,14 @@
 			},
 			Action: func(c *cli.Context) {
 				installer := &repo.Installer{
-					DeleteUnused:   c.Bool("deleteOptIn"),
-					UpdateVendored: c.Bool("update-vendored"),
-					Force:          c.Bool("force"),
-					UseCache:       c.Bool("cache"),
-					UseCacheGopath: c.Bool("cache-gopath"),
-					UseGopath:      c.Bool("use-gopath"),
-					Home:           gpath.Home(),
+					DeleteUnused:    c.Bool("deleteOptIn"),
+					UpdateVendored:  c.Bool("update-vendored"),
+					ResolveAllFiles: c.Bool("all-dependencies"),
+					Force:           c.Bool("force"),
+					UseCache:        c.Bool("cache"),
+					UseCacheGopath:  c.Bool("cache-gopath"),
+					UseGopath:       c.Bool("use-gopath"),
+					Home:            gpath.Home(),
 				}
 
 				action.Update(installer, c.Bool("no-recursive"))
@@ -486,7 +496,7 @@
 		},
 		{
 			Name:  "list",
-			Usage: "List prints all dependencies that Glide could discover.",
+			Usage: "List prints all dependencies that the present code references.",
 			Description: `List scans your code and lists all of the packages that are used.
 
 			It does not use the glide.yaml. Instead, it inspects the code to determine what packages are
diff --git a/repo/installer.go b/repo/installer.go
index 645d490..305f870 100644
--- a/repo/installer.go
+++ b/repo/installer.go
@@ -47,6 +47,11 @@
 	// imported pacakgage references this pacakage it does not need to be
 	// downloaded and searched out again.
 	RootPackage string
+
+	// ResolveAllFiles enables a resolver that will examine the dependencies
+	// of every file of every package, rather than only following imported
+	// packages.
+	ResolveAllFiles bool
 }
 
 // VendorPath returns the path to the location to put vendor packages
@@ -173,13 +178,16 @@
 	res.Config = conf
 	res.Handler = m
 	res.VersionHandler = v
+	res.ResolveAllFiles = i.ResolveAllFiles
 	msg.Info("Resolving imports")
 	_, err = allPackages(conf.Imports, res)
 	if err != nil {
 		msg.Die("Failed to retrieve a list of dependencies: %s", err)
 	}
 
-	msg.Warn("devImports not resolved.")
+	if len(conf.DevImports) > 0 {
+		msg.Warn("dev imports not resolved.")
+	}
 
 	err = ConcurrentUpdate(conf.Imports, vpath, i)
 
@@ -207,6 +215,7 @@
 	}
 	res.Config = conf
 	res.VersionHandler = v
+	res.ResolveAllFiles = i.ResolveAllFiles
 
 	msg.Info("Resolving imports")
 	_, err = allPackages(conf.Imports, res)
@@ -214,7 +223,9 @@
 		msg.Die("Failed to retrieve a list of dependencies: %s", err)
 	}
 
-	msg.Warn("devImports not resolved.")
+	if len(conf.DevImports) > 0 {
+		msg.Warn("dev imports not resolved.")
+	}
 
 	return conf.Imports
 }
@@ -227,6 +238,8 @@
 	var lock sync.Mutex
 	var returnErr error
 
+	msg.Info("Downloading dependencies. Please wait...")
+
 	for ii := 0; ii < concurrentWorkers; ii++ {
 		go func(ch <-chan *cfg.Dependency) {
 			for {
@@ -316,10 +329,11 @@
 	// This package may have been placed on the list to look for when it wasn't
 	// downloaded but it has since been downloaded before coming to this entry.
 	if _, err := os.Stat(dest); err == nil {
+		msg.Debug("Found %s", dest)
 		return true, nil
 	}
 
-	msg.Info("Fetching %s into %s", pkg, m.destination)
+	msg.Info("- Fetching %s into %s", pkg, m.destination)
 
 	d := m.Config.Imports.Get(root)
 	// If the dependency is nil it means the Config doesn't yet know about it.
diff --git a/repo/vcs.go b/repo/vcs.go
index 091b91c..6a3421c 100644
--- a/repo/vcs.go
+++ b/repo/vcs.go
@@ -30,7 +30,7 @@
 		return nil
 	}
 
-	msg.Info("Fetching updates for %s.\n", dep.Name)
+	msg.Info("- Fetching updates for %s.\n", dep.Name)
 
 	if filterArchOs(dep) {
 		msg.Info("%s is not used for %s/%s.\n", dep.Name, runtime.GOOS, runtime.GOARCH)