Merge pull request #277 from Masterminds/fix-273

Fix 273
diff --git a/dependency/resolver.go b/dependency/resolver.go
index 9f7e59e..d12a50c 100644
--- a/dependency/resolver.go
+++ b/dependency/resolver.go
@@ -241,16 +241,29 @@
 
 		// Scan for dependencies, and anything that's not part of the local
 		// package gets added to the scan list.
+		var imps []string
 		p, err := r.BuildContext.ImportDir(path, 0)
 		if err != nil {
 			if strings.HasPrefix(err.Error(), "no buildable Go source") {
 				return nil
+			} else if strings.HasPrefix(err.Error(), "found packages ") {
+				// If we got here it's because a package and multiple packages
+				// declared. This is often because of an example with a package
+				// or main but +build ignore as a build tag. In that case we
+				// try to brute force the packages with a slower scan.
+				imps, err = IterativeScan(path)
+				if err != nil {
+					return err
+				}
+			} else {
+				return err
 			}
-			return err
+		} else {
+			imps = p.Imports
 		}
 
 		// We are only looking for dependencies in vendor. No root, cgo, etc.
-		for _, imp := range p.Imports {
+		for _, imp := range imps {
 			if alreadySeen[imp] {
 				continue
 			}
@@ -377,8 +390,20 @@
 
 		// Here, we want to import the package and see what imports it has.
 		msg.Debug("Trying to open %s", vdep)
+		var imps []string
 		pkg, err := r.BuildContext.ImportDir(vdep, 0)
-		if err != nil {
+		if err != nil && strings.HasPrefix(err.Error(), "found packages ") {
+			// If we got here it's because a package and multiple packages
+			// declared. This is often because of an example with a package
+			// or main but +build ignore as a build tag. In that case we
+			// try to brute force the packages with a slower scan.
+			msg.Debug("Using Iterative Scanning for %s", dep)
+			imps, err = IterativeScan(vdep)
+			if err != nil {
+				msg.Err("Error scanning %s: %s", dep, err)
+				continue
+			}
+		} else if err != nil {
 			msg.Debug("ImportDir error on %s: %s", vdep, err)
 			if strings.HasPrefix(err.Error(), "no buildable Go source") {
 				msg.Debug("No subpackages declared. Skipping %s.", dep)
@@ -412,11 +437,13 @@
 				msg.Err("Error scanning %s: %s", dep, err)
 			}
 			continue
+		} else {
+			imps = pkg.Imports
 		}
 
 		// Range over all of the identified imports and see which ones we
 		// can locate.
-		for _, imp := range pkg.Imports {
+		for _, imp := range imps {
 			pi := r.FindPkg(imp)
 			if pi.Loc != LocCgo && pi.Loc != LocGoroot && pi.Loc != LocAppengine {
 				msg.Debug("Package %s imports %s", dep, imp)
@@ -623,9 +650,21 @@
 
 	// FIXME: On error this should try to NotFound to the dependency, and then import
 	// it again.
+	var imps []string
 	p, err := r.BuildContext.ImportDir(pkg, 0)
-	if err != nil {
+	if err != nil && strings.HasPrefix(err.Error(), "found packages ") {
+		// If we got here it's because a package and multiple packages
+		// declared. This is often because of an example with a package
+		// or main but +build ignore as a build tag. In that case we
+		// try to brute force the packages with a slower scan.
+		imps, err = IterativeScan(pkg)
+		if err != nil {
+			return []string{}, err
+		}
+	} else if err != nil {
 		return []string{}, err
+	} else {
+		imps = p.Imports
 	}
 
 	// It is okay to scan a package more than once. In some cases, this is
@@ -641,7 +680,7 @@
 
 	// We are only looking for dependencies in vendor. No root, cgo, etc.
 	buf := []string{}
-	for _, imp := range p.Imports {
+	for _, imp := range imps {
 		if r.Config.HasIgnore(imp) {
 			msg.Debug("Ignoring %s", imp)
 			continue
diff --git a/dependency/scan.go b/dependency/scan.go
new file mode 100644
index 0000000..73452f2
--- /dev/null
+++ b/dependency/scan.go
@@ -0,0 +1,287 @@
+package dependency
+
+import (
+	"bytes"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+	"text/scanner"
+
+	"github.com/Masterminds/glide/msg"
+	"github.com/Masterminds/glide/util"
+)
+
+var osList []string
+var archList []string
+
+func init() {
+	// The supported systems are listed in
+	// https://github.com/golang/go/blob/master/src/go/build/syslist.go
+	// The lists are not exported so we need to duplicate them here.
+	osListString := "android darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris windows"
+	osList = strings.Split(osListString, " ")
+
+	archListString := "386 amd64 amd64p32 arm armbe arm64 arm64be ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32le ppc s390 s390x sparc sparc64"
+	archList = strings.Split(archListString, " ")
+}
+
+// IterativeScan attempts to obtain a list of imported dependencies from a
+// package. This scanning is different from ImportDir as part of the go/build
+// package. It looks over different permutations of the supported OS/Arch to
+// try and find all imports. This is different from setting UseAllFiles to
+// true on the build Context. It scopes down to just the supported OS/Arch.
+//
+// Note, there are cases where multiple packages are in the same directory. This
+// usually happens with an example that has a main package and a +build tag
+// of ignore. This is a bit of a hack. It causes UseAllFiles to have errors.
+func IterativeScan(path string) ([]string, error) {
+
+	// TODO(mattfarina): Add support for release tags.
+
+	tgs, _ := readBuildTags(path)
+	// Handle the case of scanning with no tags
+	tgs = append(tgs, "")
+
+	var pkgs []string
+	for _, tt := range tgs {
+
+		// split the tag combination to look at permutations.
+		ts := strings.Split(tt, ",")
+		var ttgs []string
+		var arch string
+		var ops string
+		for _, ttt := range ts {
+			dirty := false
+			if strings.HasPrefix(ttt, "!") {
+				dirty = true
+				ttt = strings.TrimPrefix(ttt, "!")
+			}
+			if isSupportedOs(ttt) {
+				if dirty {
+					ops = getOsValue(ttt)
+				} else {
+					ops = ttt
+				}
+			} else if isSupportedArch(ttt) {
+				if dirty {
+					arch = getArchValue(ttt)
+				} else {
+					arch = ttt
+				}
+			} else {
+				if !dirty {
+					ttgs = append(ttgs, ttt)
+				}
+			}
+		}
+
+		// Handle the case where there are no tags but we need to iterate
+		// on something.
+		if len(ttgs) == 0 {
+			ttgs = append(ttgs, "")
+		}
+
+		b, err := util.GetBuildContext()
+		if err != nil {
+			return []string{}, err
+		}
+
+		// Make sure use all files is off
+		b.UseAllFiles = false
+
+		// Set the OS and Arch for this pass
+		b.GOARCH = arch
+		b.GOOS = ops
+		b.BuildTags = ttgs
+		msg.Debug("Scanning with Arch(%s), OS(%s), and Build Tags(%v)", arch, ops, ttgs)
+
+		pk, err := b.ImportDir(path, 0)
+
+		// If there are no buildable souce with this permutation we skip it.
+		if err != nil && strings.HasPrefix(err.Error(), "no buildable Go source files in") {
+			continue
+		} else if err != nil && strings.HasPrefix(err.Error(), "found packages archive ") {
+			// A permutation may cause multiple packages to appear. For example,
+			// an example file with an ignore build tag. If this happens we
+			// ignore it.
+			// TODO(mattfarina): Find a better way.
+			msg.Debug("Found multiple packages while scanning %s: %s", path, err)
+			continue
+		} else if err != nil {
+			msg.Debug("Problem parsing package at %s for %s %s", path, ops, arch)
+			return []string{}, err
+		}
+
+		for _, dep := range pk.Imports {
+			found := false
+			for _, p := range pkgs {
+				if p == dep {
+					found = true
+				}
+			}
+			if !found {
+				pkgs = append(pkgs, dep)
+			}
+		}
+	}
+
+	return pkgs, nil
+}
+
+func readBuildTags(p string) ([]string, error) {
+	_, err := os.Stat(p)
+	if err != nil {
+		return []string{}, err
+	}
+
+	d, err := os.Open(p)
+	if err != nil {
+		return []string{}, err
+	}
+
+	objects, err := d.Readdir(-1)
+	if err != nil {
+		return []string{}, err
+	}
+
+	var tags []string
+	for _, obj := range objects {
+
+		// only process Go files
+		if strings.HasSuffix(obj.Name(), ".go") {
+			fp := filepath.Join(p, obj.Name())
+
+			co, err := readGoContents(fp)
+			if err != nil {
+				return []string{}, err
+			}
+
+			// Only look at places where we had a code comment.
+			if len(co) > 0 {
+				t := findTags(co)
+				for _, tg := range t {
+					found := false
+					for _, tt := range tags {
+						if tt == tg {
+							found = true
+						}
+					}
+					if !found {
+						tags = append(tags, tg)
+					}
+				}
+			}
+		}
+	}
+
+	return tags, nil
+}
+
+// Read contents of a Go file up to the package declaration. This can be used
+// to find the the build tags.
+func readGoContents(fp string) ([]byte, error) {
+	f, err := os.Open(fp)
+	defer f.Close()
+	if err != nil {
+		return []byte{}, err
+	}
+
+	var s scanner.Scanner
+	s.Init(f)
+	var tok rune
+	var pos scanner.Position
+	for tok != scanner.EOF {
+		tok = s.Scan()
+
+		// Getting the token text will skip comments by default.
+		tt := s.TokenText()
+		// build tags will not be after the package declaration.
+		if tt == "package" {
+			pos = s.Position
+			break
+		}
+	}
+
+	buf := bytes.NewBufferString("")
+	f.Seek(0, 0)
+	_, err = io.CopyN(buf, f, int64(pos.Offset))
+	if err != nil {
+		return []byte{}, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+// From a byte slice of a Go file find the tags.
+func findTags(co []byte) []string {
+	p := co
+	var tgs []string
+	for len(p) > 0 {
+		line := p
+		if i := bytes.IndexByte(line, '\n'); i >= 0 {
+			line, p = line[:i], p[i+1:]
+		} else {
+			p = p[len(p):]
+		}
+		line = bytes.TrimSpace(line)
+		// Only look at comment lines that are well formed in the Go style
+		if bytes.HasPrefix(line, []byte("//")) {
+			line = bytes.TrimSpace(line[len([]byte("//")):])
+			if len(line) > 0 && line[0] == '+' {
+				f := strings.Fields(string(line))
+
+				// We've found a +build tag line.
+				if f[0] == "+build" {
+					for _, tg := range f[1:] {
+						tgs = append(tgs, tg)
+					}
+				}
+			}
+		}
+	}
+
+	return tgs
+}
+
+// Get an OS value that's not the one passed in.
+func getOsValue(n string) string {
+	for _, o := range osList {
+		if o != n {
+			return o
+		}
+	}
+
+	return n
+}
+
+func isSupportedOs(n string) bool {
+	for _, o := range osList {
+		if o == n {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Get an Arch value that's not the one passed in.
+func getArchValue(n string) string {
+	for _, o := range archList {
+		if o != n {
+			return o
+		}
+	}
+
+	return n
+}
+
+func isSupportedArch(n string) bool {
+	for _, o := range archList {
+		if o == n {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/tree/tree.go b/tree/tree.go
index 1be49a5..8f0a0b5 100644
--- a/tree/tree.go
+++ b/tree/tree.go
@@ -52,20 +52,33 @@
 			return nil
 		}
 
+		var imps []string
 		pkg, err := b.ImportDir(path, 0)
-		if err != nil {
+		if err != nil && strings.HasPrefix(err.Error(), "found packages ") {
+			// If we got here it's because a package and multiple packages
+			// declared. This is often because of an example with a package
+			// or main but +build ignore as a build tag. In that case we
+			// try to brute force the packages with a slower scan.
+			imps, err = dependency.IterativeScan(path)
+			if err != nil {
+				msg.Err("Error walking dependencies for %s: %s", path, err)
+				return err
+			}
+		} else if err != nil {
 			if !strings.HasPrefix(err.Error(), "no buildable Go source") {
 				msg.Warn("Error: %s (%s)", err, path)
 				// Not sure if we should return here.
 				//return err
 			}
+		} else {
+			imps = pkg.Imports
 		}
 
 		if pkg.Goroot {
 			return nil
 		}
 
-		for _, imp := range pkg.Imports {
+		for _, imp := range imps {
 			//if strings.HasPrefix(imp, myName) {
 			////Info("Skipping %s because it is a subpackage of %s", imp, myName)
 			//continue