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