github.com/heyitsanthony/glide@v0.12.3/dependency/resolver.go (about)

     1  package dependency
     2  
     3  import (
     4  	"container/list"
     5  	"errors"
     6  	"runtime"
     7  	"sort"
     8  	//"go/build"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/Masterminds/glide/cfg"
    14  	"github.com/Masterminds/glide/msg"
    15  	gpath "github.com/Masterminds/glide/path"
    16  	"github.com/Masterminds/glide/util"
    17  )
    18  
    19  // MissingPackageHandler handles the case where a package is missing during scanning.
    20  //
    21  // It returns true if the package can be passed to the resolver, false otherwise.
    22  // False may be returned even if error is nil.
    23  type MissingPackageHandler interface {
    24  	// NotFound is called when the Resolver fails to find a package with the given name.
    25  	//
    26  	// NotFound returns true when the resolver should attempt to re-resole the
    27  	// dependency (e.g. when NotFound has gone and fetched the missing package).
    28  	//
    29  	// When NotFound returns false, the Resolver does not try to do any additional
    30  	// work on the missing package.
    31  	//
    32  	// NotFound only returns errors when it fails to perform its internal goals.
    33  	// When it returns false with no error, this indicates that the handler did
    34  	// its job, but the resolver should not do any additional work on the
    35  	// package.
    36  	NotFound(pkg string, addTest bool) (bool, error)
    37  
    38  	// OnGopath is called when the Resolver finds a dependency, but it's only on GOPATH.
    39  	//
    40  	// OnGopath provides an opportunity to copy, move, warn, or ignore cases like this.
    41  	//
    42  	// OnGopath returns true when the resolver should attempt to re-resolve the
    43  	// dependency (e.g. when the dependency is copied to a new location).
    44  	//
    45  	// When OnGopath returns false, the Resolver does not try to do any additional
    46  	// work on the package.
    47  	//
    48  	// An error indicates that OnGopath cannot complete its intended operation.
    49  	// Not all false results are errors.
    50  	OnGopath(pkg string, addTest bool) (bool, error)
    51  
    52  	// InVendor is called when the Resolver finds a dependency in the vendor/ directory.
    53  	//
    54  	// This can be used update a project found in the vendor/ folder.
    55  	InVendor(pkg string, addTest bool) error
    56  
    57  	// PkgPath is called to find the location locally to scan. This gives the
    58  	// handler to do things such as use a cached location.
    59  	PkgPath(pkg string) string
    60  }
    61  
    62  // DefaultMissingPackageHandler is the default handler for missing packages.
    63  //
    64  // When asked to handle a missing package, it will report the miss as a warning,
    65  // and then store the package in the Missing slice for later access.
    66  type DefaultMissingPackageHandler struct {
    67  	Missing []string
    68  	Gopath  []string
    69  	Prefix  string
    70  }
    71  
    72  // NotFound prints a warning and then stores the package name in Missing.
    73  //
    74  // It never returns an error, and it always returns false.
    75  func (d *DefaultMissingPackageHandler) NotFound(pkg string, addTest bool) (bool, error) {
    76  	msg.Warn("Package %s is not installed", pkg)
    77  	d.Missing = append(d.Missing, pkg)
    78  	return false, nil
    79  }
    80  
    81  // OnGopath is run when a package is missing from vendor/ but found in the GOPATH
    82  func (d *DefaultMissingPackageHandler) OnGopath(pkg string, addTest bool) (bool, error) {
    83  	msg.Warn("Package %s is only on GOPATH.", pkg)
    84  	d.Gopath = append(d.Gopath, pkg)
    85  	return false, nil
    86  }
    87  
    88  // InVendor is run when a package is found in the vendor/ folder
    89  func (d *DefaultMissingPackageHandler) InVendor(pkg string, addTest bool) error {
    90  	msg.Info("Package %s found in vendor/ folder", pkg)
    91  	return nil
    92  }
    93  
    94  // PkgPath returns the path to the package
    95  func (d *DefaultMissingPackageHandler) PkgPath(pkg string) string {
    96  	if d.Prefix != "" {
    97  		return filepath.Join(d.Prefix, pkg)
    98  	}
    99  	return pkg
   100  }
   101  
   102  // VersionHandler sets the version for a package when found while scanning.
   103  //
   104  // When a package if found it needs to be on the correct version before
   105  // scanning its contents to be sure to pick up the right elements for that
   106  // version.
   107  type VersionHandler interface {
   108  
   109  	// Process provides an opportunity to process the codebase for version setting.
   110  	Process(pkg string) error
   111  
   112  	// SetVersion sets the version for a package. An error is returned if there
   113  	// was a problem setting the version.
   114  	SetVersion(pkg string, testDep bool) error
   115  }
   116  
   117  // DefaultVersionHandler is the default handler for setting the version.
   118  //
   119  // The default handler leaves the current version and skips setting a version.
   120  // For a handler that alters the version see the handler included in the repo
   121  // package as part of the installer.
   122  type DefaultVersionHandler struct{}
   123  
   124  // Process a package to aide in version setting.
   125  func (d *DefaultVersionHandler) Process(pkg string) error {
   126  	return nil
   127  }
   128  
   129  // SetVersion here sends a message when a package is found noting that it
   130  // did not set the version.
   131  func (d *DefaultVersionHandler) SetVersion(pkg string, testDep bool) error {
   132  	msg.Warn("Version not set for package %s", pkg)
   133  	return nil
   134  }
   135  
   136  // Resolver resolves a dependency tree.
   137  //
   138  // It operates in two modes:
   139  // - local resolution (ResolveLocal) determines the dependencies of the local project.
   140  // - vendor resolving (Resolve, ResolveAll) determines the dependencies of vendored
   141  //   projects.
   142  //
   143  // Local resolution is for guessing initial dependencies. Vendor resolution is
   144  // for determining vendored dependencies.
   145  type Resolver struct {
   146  	Handler        MissingPackageHandler
   147  	VersionHandler VersionHandler
   148  	VendorDir      string
   149  	BuildContext   *util.BuildCtxt
   150  	Config         *cfg.Config
   151  
   152  	// ResolveAllFiles toggles deep scanning.
   153  	// If this is true, resolve by scanning all files, not by walking the
   154  	// import tree.
   155  	ResolveAllFiles bool
   156  
   157  	// ResolveTest sets if test dependencies should be resolved.
   158  	ResolveTest bool
   159  
   160  	// Items already in the queue.
   161  	alreadyQ map[string]bool
   162  
   163  	// Attempts to scan that had unrecoverable error.
   164  	hadError map[string]bool
   165  
   166  	basedir string
   167  	seen    map[string]bool
   168  
   169  	// findCache caches hits from Find. This reduces the number of filesystem
   170  	// touches that have to be done for dependency resolution.
   171  	findCache map[string]*PkgInfo
   172  }
   173  
   174  // NewResolver returns a new Resolver initialized with the DefaultMissingPackageHandler.
   175  //
   176  // This will return an error if the given path does not meet the basic criteria
   177  // for a Go source project. For example, basedir must have a vendor subdirectory.
   178  //
   179  // The BuildContext uses the "go/build".Default to resolve dependencies.
   180  func NewResolver(basedir string) (*Resolver, error) {
   181  
   182  	var err error
   183  	basedir, err = filepath.Abs(basedir)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	basedir, err = checkForBasedirSymlink(basedir)
   189  
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	vdir := filepath.Join(basedir, "vendor")
   195  
   196  	buildContext, err := util.GetBuildContext()
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	r := &Resolver{
   202  		Handler:        &DefaultMissingPackageHandler{Missing: []string{}, Gopath: []string{}},
   203  		VersionHandler: &DefaultVersionHandler{},
   204  		basedir:        basedir,
   205  		VendorDir:      vdir,
   206  		BuildContext:   buildContext,
   207  		seen:           map[string]bool{},
   208  		alreadyQ:       map[string]bool{},
   209  		hadError:       map[string]bool{},
   210  		findCache:      map[string]*PkgInfo{},
   211  
   212  		// The config instance here should really be replaced with a real one.
   213  		Config: &cfg.Config{},
   214  	}
   215  
   216  	// TODO: Make sure the build context is correctly set up. Especially in
   217  	// regards to GOROOT, which is not always set.
   218  
   219  	return r, nil
   220  }
   221  
   222  // Resolve takes a package name and returns all of the imported package names.
   223  //
   224  // If a package is not found, this calls the Fetcher. If the Fetcher returns
   225  // true, it will re-try traversing that package for dependencies. Otherwise it
   226  // will add that package to the deps array and continue on without trying it.
   227  // And if the Fetcher returns an error, this will stop resolution and return
   228  // the error.
   229  //
   230  // If basepath is set to $GOPATH, this will start from that package's root there.
   231  // If basepath is set to a project's vendor path, the scanning will begin from
   232  // there.
   233  func (r *Resolver) Resolve(pkg, basepath string) ([]string, error) {
   234  	target := filepath.Join(basepath, filepath.FromSlash(pkg))
   235  	//msg.Debug("Scanning %s", target)
   236  	l := list.New()
   237  	l.PushBack(target)
   238  
   239  	// In this mode, walk the entire tree.
   240  	if r.ResolveAllFiles {
   241  		return r.resolveList(l, false, false)
   242  	}
   243  	return r.resolveImports(l, false, false)
   244  }
   245  
   246  // dirHasPrefix tests whether the directory dir begins with prefix.
   247  func dirHasPrefix(dir, prefix string) bool {
   248  	if runtime.GOOS != "windows" {
   249  		return strings.HasPrefix(dir, prefix)
   250  	}
   251  	return len(dir) >= len(prefix) && strings.EqualFold(dir[:len(prefix)], prefix)
   252  }
   253  
   254  // ResolveLocal resolves dependencies for the current project.
   255  //
   256  // This begins with the project, builds up a list of external dependencies.
   257  //
   258  // If the deep flag is set to true, this will then resolve all of the dependencies
   259  // of the dependencies it has found. If not, it will return just the packages that
   260  // the base project relies upon.
   261  func (r *Resolver) ResolveLocal(deep bool) ([]string, []string, error) {
   262  	// We build a list of local source to walk, then send this list
   263  	// to resolveList.
   264  	msg.Debug("Resolving local dependencies")
   265  	l := list.New()
   266  	tl := list.New()
   267  	alreadySeen := map[string]bool{}
   268  	talreadySeen := map[string]bool{}
   269  	err := filepath.Walk(r.basedir, func(path string, fi os.FileInfo, err error) error {
   270  		if err != nil && err != filepath.SkipDir {
   271  			return err
   272  		}
   273  		pt := strings.TrimPrefix(path, r.basedir+string(os.PathSeparator))
   274  		pt = strings.TrimSuffix(pt, string(os.PathSeparator))
   275  		if r.Config.HasExclude(pt) {
   276  			msg.Debug("Excluding %s", pt)
   277  			return filepath.SkipDir
   278  		}
   279  		if !fi.IsDir() {
   280  			return nil
   281  		}
   282  		if !srcDir(fi) {
   283  			return filepath.SkipDir
   284  		}
   285  
   286  		// Scan for dependencies, and anything that's not part of the local
   287  		// package gets added to the scan list.
   288  		var imps []string
   289  		var testImps []string
   290  		p, err := r.BuildContext.ImportDir(path, 0)
   291  		if err != nil {
   292  			if strings.HasPrefix(err.Error(), "no buildable Go source") {
   293  				return nil
   294  			} else if strings.HasPrefix(err.Error(), "found packages ") {
   295  				// If we got here it's because a package and multiple packages
   296  				// declared. This is often because of an example with a package
   297  				// or main but +build ignore as a build tag. In that case we
   298  				// try to brute force the packages with a slower scan.
   299  				imps, testImps, err = IterativeScan(path)
   300  				if err != nil {
   301  					return err
   302  				}
   303  			} else {
   304  				return err
   305  			}
   306  		} else {
   307  			imps = p.Imports
   308  			testImps = dedupeStrings(p.TestImports, p.XTestImports)
   309  		}
   310  
   311  		// We are only looking for dependencies in vendor. No root, cgo, etc.
   312  		for _, imp := range imps {
   313  			if r.Config.HasIgnore(imp) {
   314  				continue
   315  			}
   316  			if alreadySeen[imp] {
   317  				continue
   318  			}
   319  			alreadySeen[imp] = true
   320  			info := r.FindPkg(imp)
   321  			switch info.Loc {
   322  			case LocUnknown, LocVendor:
   323  				l.PushBack(filepath.Join(r.VendorDir, filepath.FromSlash(imp))) // Do we need a path on this?
   324  			case LocGopath:
   325  				if !dirHasPrefix(info.Path, r.basedir) {
   326  					// FIXME: This is a package outside of the project we're
   327  					// scanning. It should really be on vendor. But we don't
   328  					// want it to reference GOPATH. We want it to be detected
   329  					// and moved.
   330  					l.PushBack(filepath.Join(r.VendorDir, filepath.FromSlash(imp)))
   331  				}
   332  			case LocRelative:
   333  				if strings.HasPrefix(imp, "./"+gpath.VendorDir) {
   334  					msg.Warn("Go package resolving will resolve %s without the ./%s/ prefix", imp, gpath.VendorDir)
   335  				}
   336  			}
   337  		}
   338  
   339  		if r.ResolveTest {
   340  			for _, imp := range testImps {
   341  				if talreadySeen[imp] {
   342  					continue
   343  				}
   344  				talreadySeen[imp] = true
   345  				info := r.FindPkg(imp)
   346  				switch info.Loc {
   347  				case LocUnknown, LocVendor:
   348  					tl.PushBack(filepath.Join(r.VendorDir, filepath.FromSlash(imp))) // Do we need a path on this?
   349  				case LocGopath:
   350  					if !dirHasPrefix(info.Path, r.basedir) {
   351  						// FIXME: This is a package outside of the project we're
   352  						// scanning. It should really be on vendor. But we don't
   353  						// want it to reference GOPATH. We want it to be detected
   354  						// and moved.
   355  						tl.PushBack(filepath.Join(r.VendorDir, filepath.FromSlash(imp)))
   356  					}
   357  				case LocRelative:
   358  					if strings.HasPrefix(imp, "./"+gpath.VendorDir) {
   359  						msg.Warn("Go package resolving will resolve %s without the ./%s/ prefix", imp, gpath.VendorDir)
   360  					}
   361  				}
   362  			}
   363  		}
   364  
   365  		return nil
   366  	})
   367  
   368  	if err != nil {
   369  		msg.Err("Failed to build an initial list of packages to scan: %s", err)
   370  		return []string{}, []string{}, err
   371  	}
   372  
   373  	if deep {
   374  		if r.ResolveAllFiles {
   375  			re, err := r.resolveList(l, false, false)
   376  			if err != nil {
   377  				return []string{}, []string{}, err
   378  			}
   379  			tre, err := r.resolveList(l, false, true)
   380  			return re, tre, err
   381  		}
   382  		re, err := r.resolveImports(l, false, false)
   383  		if err != nil {
   384  			return []string{}, []string{}, err
   385  		}
   386  		tre, err := r.resolveImports(tl, true, true)
   387  		return re, tre, err
   388  	}
   389  
   390  	// If we're not doing a deep scan, we just convert the list into an
   391  	// array and return.
   392  	res := make([]string, 0, l.Len())
   393  	for e := l.Front(); e != nil; e = e.Next() {
   394  		res = append(res, e.Value.(string))
   395  	}
   396  	tres := make([]string, 0, l.Len())
   397  	if r.ResolveTest {
   398  		for e := tl.Front(); e != nil; e = e.Next() {
   399  			tres = append(tres, e.Value.(string))
   400  		}
   401  	}
   402  
   403  	return res, tres, nil
   404  }
   405  
   406  // ResolveAll takes a list of packages and returns an inclusive list of all
   407  // vendored dependencies.
   408  //
   409  // While this will scan all of the source code it can find, it will only return
   410  // packages that were either explicitly passed in as deps, or were explicitly
   411  // imported by the code.
   412  //
   413  // Packages that are either CGO or on GOROOT are ignored. Packages that are
   414  // on GOPATH, but not vendored currently generate a warning.
   415  //
   416  // If one of the passed in packages does not exist in the vendor directory,
   417  // an error is returned.
   418  func (r *Resolver) ResolveAll(deps []*cfg.Dependency, addTest bool) ([]string, error) {
   419  
   420  	queue := sliceToQueue(deps, r.VendorDir)
   421  
   422  	if r.ResolveAllFiles {
   423  		return r.resolveList(queue, false, addTest)
   424  	}
   425  	return r.resolveImports(queue, false, addTest)
   426  }
   427  
   428  // Stripv strips the vendor/ prefix from vendored packages.
   429  func (r *Resolver) Stripv(str string) string {
   430  	return strings.TrimPrefix(str, r.VendorDir+string(os.PathSeparator))
   431  }
   432  
   433  // vpath adds an absolute vendor path.
   434  func (r *Resolver) vpath(str string) string {
   435  	return filepath.Join(r.basedir, "vendor", str)
   436  }
   437  
   438  // resolveImports takes a list of existing packages and resolves their imports.
   439  //
   440  // It returns a list of all of the packages that it can determine are required
   441  // for the given code to function.
   442  //
   443  // The expectation is that each item in the queue is an absolute path to a
   444  // vendored package. This attempts to read that package, and then find
   445  // its referenced packages. Those packages are then added to the list
   446  // to be scanned next.
   447  //
   448  // The resolver's handler is used in the cases where a package cannot be
   449  // located.
   450  //
   451  // testDeps specifies if the test dependencies should be resolved and addTest
   452  // specifies if the dependencies should be added to the Config.DevImports. This
   453  // is important because we may resolve normal dependencies of test deps and add
   454  // them to the DevImports list.
   455  func (r *Resolver) resolveImports(queue *list.List, testDeps, addTest bool) ([]string, error) {
   456  	msg.Debug("Resolving import path")
   457  
   458  	// When test deps passed in but not resolving return empty.
   459  	if (testDeps || addTest) && !r.ResolveTest {
   460  		return []string{}, nil
   461  	}
   462  
   463  	alreadySeen := make(map[string]bool, queue.Len())
   464  
   465  	for e := queue.Front(); e != nil; e = e.Next() {
   466  		vdep := e.Value.(string)
   467  		dep := r.Stripv(vdep)
   468  		// Check if marked in the Q and then explicitly mark it. We want to know
   469  		// if it had previously been marked and ensure it for the future.
   470  
   471  		if alreadySeen[dep] {
   472  			continue
   473  		}
   474  		alreadySeen[dep] = true
   475  
   476  		_, foundQ := r.alreadyQ[dep]
   477  		r.alreadyQ[dep] = true
   478  
   479  		// If we've already encountered an error processing this dependency
   480  		// skip it.
   481  		_, foundErr := r.hadError[dep]
   482  		if foundErr {
   483  			continue
   484  		}
   485  
   486  		// Skip ignored packages
   487  		if r.Config.HasIgnore(dep) {
   488  			msg.Debug("Ignoring: %s", dep)
   489  			continue
   490  		}
   491  		r.VersionHandler.Process(dep)
   492  		// Here, we want to import the package and see what imports it has.
   493  		msg.Debug("Trying to open %s (%s)", dep, r.Handler.PkgPath(dep))
   494  		var imps []string
   495  		pkg, err := r.BuildContext.ImportDir(r.Handler.PkgPath(dep), 0)
   496  		if err != nil && strings.HasPrefix(err.Error(), "found packages ") {
   497  			// If we got here it's because a package and multiple packages
   498  			// declared. This is often because of an example with a package
   499  			// or main but +build ignore as a build tag. In that case we
   500  			// try to brute force the packages with a slower scan.
   501  			msg.Debug("Using Iterative Scanning for %s", dep)
   502  			if testDeps {
   503  				_, imps, err = IterativeScan(r.Handler.PkgPath(dep))
   504  			} else {
   505  				imps, _, err = IterativeScan(r.Handler.PkgPath(dep))
   506  			}
   507  
   508  			if err != nil {
   509  				msg.Err("Iterative scanning error %s: %s", dep, err)
   510  				continue
   511  			}
   512  		} else if err != nil {
   513  			errStr := err.Error()
   514  			msg.Debug("ImportDir error on %s: %s", r.Handler.PkgPath(dep), err)
   515  			if strings.HasPrefix(errStr, "no buildable Go source") {
   516  				msg.Debug("No subpackages declared. Skipping %s.", dep)
   517  				continue
   518  			} else if os.IsNotExist(err) && !foundErr && !foundQ {
   519  				// If the location doesn't exist, there hasn't already been an
   520  				// error, it's not already been in the Q then try to fetch it.
   521  				// When there's an error or it's already in the Q (it should be
   522  				// fetched if it's marked in r.alreadyQ) we skip to make sure
   523  				// not to get stuck in a recursion.
   524  
   525  				// If the location doesn't exist try to fetch it.
   526  				if ok, err2 := r.Handler.NotFound(dep, addTest); ok {
   527  					r.alreadyQ[dep] = true
   528  					alreadySeen[dep] = false
   529  
   530  					// By adding to the queue it will get reprocessed now that
   531  					// it exists.
   532  					queue.PushBack(r.vpath(dep))
   533  					r.VersionHandler.SetVersion(dep, addTest)
   534  				} else if err2 != nil {
   535  					r.hadError[dep] = true
   536  					msg.Err("Error looking for %s: %s", dep, err2)
   537  				} else {
   538  					r.hadError[dep] = true
   539  					// TODO (mpb): Should we toss this into a Handler to
   540  					// see if this is on GOPATH and copy it?
   541  					msg.Info("Not found in vendor/: %s (1)", dep)
   542  				}
   543  			} else if strings.Contains(errStr, "no such file or directory") {
   544  				r.hadError[dep] = true
   545  				msg.Err("Error scanning %s: %s", dep, err)
   546  				msg.Err("This error means the referenced package was not found.")
   547  				msg.Err("Missing file or directory errors usually occur when multiple packages")
   548  				msg.Err("share a common dependency and the first reference encountered by the scanner")
   549  				msg.Err("sets the version to one that does not contain a subpackage needed required")
   550  				msg.Err("by another package that uses the shared dependency. Try setting a")
   551  				msg.Err("version in your glide.yaml that works for all packages that share this")
   552  				msg.Err("dependency.")
   553  			} else {
   554  				r.hadError[dep] = true
   555  				msg.Err("Error scanning %s: %s", dep, err)
   556  			}
   557  			continue
   558  		} else {
   559  			if testDeps {
   560  				imps = dedupeStrings(pkg.TestImports, pkg.XTestImports)
   561  			} else {
   562  				imps = pkg.Imports
   563  			}
   564  
   565  		}
   566  
   567  		// Range over all of the identified imports and see which ones we
   568  		// can locate.
   569  		for _, imp := range imps {
   570  			if r.Config.HasIgnore(imp) {
   571  				msg.Debug("Ignoring: %s", imp)
   572  				continue
   573  			}
   574  			pi := r.FindPkg(imp)
   575  			if pi.Loc != LocCgo && pi.Loc != LocGoroot && pi.Loc != LocAppengine {
   576  				msg.Debug("Package %s imports %s", dep, imp)
   577  			}
   578  			switch pi.Loc {
   579  			case LocVendor:
   580  				msg.Debug("In vendor: %s", imp)
   581  				if _, ok := r.alreadyQ[imp]; !ok {
   582  					msg.Debug("Marking %s to be scanned.", imp)
   583  					r.alreadyQ[imp] = true
   584  					queue.PushBack(r.vpath(imp))
   585  					if err := r.Handler.InVendor(imp, addTest); err == nil {
   586  						r.VersionHandler.SetVersion(imp, addTest)
   587  					} else {
   588  						msg.Warn("Error updating %s: %s", imp, err)
   589  					}
   590  				}
   591  			case LocUnknown:
   592  				msg.Debug("Missing %s. Trying to resolve.", imp)
   593  				if ok, err := r.Handler.NotFound(imp, addTest); ok {
   594  					r.alreadyQ[imp] = true
   595  					queue.PushBack(r.vpath(imp))
   596  					r.VersionHandler.SetVersion(imp, addTest)
   597  				} else if err != nil {
   598  					r.hadError[imp] = true
   599  					msg.Err("Error looking for %s: %s", imp, err)
   600  				} else {
   601  					r.hadError[imp] = true
   602  					msg.Err("Not found: %s (2)", imp)
   603  				}
   604  			case LocGopath:
   605  				msg.Debug("Found on GOPATH, not vendor: %s", imp)
   606  				if _, ok := r.alreadyQ[imp]; !ok {
   607  					// Only scan it if it gets moved into vendor/
   608  					if ok, _ := r.Handler.OnGopath(imp, addTest); ok {
   609  						r.alreadyQ[imp] = true
   610  						queue.PushBack(r.vpath(imp))
   611  						r.VersionHandler.SetVersion(imp, addTest)
   612  					}
   613  				}
   614  			}
   615  		}
   616  
   617  	}
   618  
   619  	if len(r.hadError) > 0 {
   620  		// Errors occurred so we return.
   621  		return []string{}, errors.New("Error resolving imports")
   622  	}
   623  
   624  	// FIXME: From here to the end is a straight copy of the resolveList() func.
   625  	res := make([]string, 0, queue.Len())
   626  
   627  	// In addition to generating a list
   628  	for e := queue.Front(); e != nil; e = e.Next() {
   629  		t := r.Stripv(e.Value.(string))
   630  		root, sp := util.NormalizeName(t)
   631  
   632  		if root == r.Config.Name {
   633  			continue
   634  		}
   635  
   636  		// Skip ignored packages
   637  		if r.Config.HasIgnore(e.Value.(string)) {
   638  			msg.Debug("Ignoring: %s", e.Value.(string))
   639  			continue
   640  		}
   641  
   642  		// TODO(mattfarina): Need to eventually support devImport
   643  		existing := r.Config.Imports.Get(root)
   644  		if existing == nil && addTest {
   645  			existing = r.Config.DevImports.Get(root)
   646  		}
   647  		if existing != nil {
   648  			if sp != "" && !existing.HasSubpackage(sp) {
   649  				existing.Subpackages = append(existing.Subpackages, sp)
   650  			}
   651  		} else {
   652  			newDep := &cfg.Dependency{
   653  				Name: root,
   654  			}
   655  			if sp != "" {
   656  				newDep.Subpackages = []string{sp}
   657  			}
   658  
   659  			if addTest {
   660  				r.Config.DevImports = append(r.Config.DevImports, newDep)
   661  			} else {
   662  				r.Config.Imports = append(r.Config.Imports, newDep)
   663  			}
   664  		}
   665  		res = append(res, t)
   666  	}
   667  
   668  	return res, nil
   669  }
   670  
   671  // resolveList takes a list and resolves it.
   672  //
   673  // This walks the entire file tree for the given dependencies, not just the
   674  // parts that are imported directly. Using this will discover dependencies
   675  // regardless of OS, and arch.
   676  func (r *Resolver) resolveList(queue *list.List, testDeps, addTest bool) ([]string, error) {
   677  	// When test deps passed in but not resolving return empty.
   678  	if testDeps && !r.ResolveTest {
   679  		return []string{}, nil
   680  	}
   681  
   682  	var failedDep string
   683  	var failedDepPath string
   684  	var pkgPath string
   685  	for e := queue.Front(); e != nil; e = e.Next() {
   686  		dep := e.Value.(string)
   687  		t := strings.TrimPrefix(dep, r.VendorDir+string(os.PathSeparator))
   688  		if r.Config.HasIgnore(t) {
   689  			msg.Debug("Ignoring: %s", t)
   690  			continue
   691  		}
   692  		r.VersionHandler.Process(t)
   693  		//msg.Warn("#### %s ####", dep)
   694  		//msg.Info("Seen Count: %d", len(r.seen))
   695  		// Catch the outtermost dependency.
   696  		pkgPath = r.Handler.PkgPath(t)
   697  		failedDep = t
   698  		failedDepPath = pkgPath
   699  		err := filepath.Walk(pkgPath, func(path string, fi os.FileInfo, err error) error {
   700  			if err != nil && err != filepath.SkipDir {
   701  				return err
   702  			}
   703  
   704  			// Skip files.
   705  			if !fi.IsDir() {
   706  				return nil
   707  			}
   708  			// Skip dirs that are not source.
   709  			if !srcDir(fi) {
   710  				//msg.Debug("Skip resource %s", fi.Name())
   711  				return filepath.SkipDir
   712  			}
   713  
   714  			// Anything that comes through here has already been through
   715  			// the queue.
   716  			r.alreadyQ[path] = true
   717  			e := r.queueUnseen(path, queue, testDeps, addTest)
   718  			if e != nil {
   719  				failedDepPath = path
   720  				//msg.Err("Failed to fetch dependency %s: %s", path, err)
   721  			}
   722  			return e
   723  		})
   724  		if err != nil && err != filepath.SkipDir {
   725  			msg.Err("Dependency %s (%s) failed to resolve: %s.", failedDep, failedDepPath, err)
   726  			return []string{}, err
   727  		}
   728  	}
   729  
   730  	res := make([]string, 0, queue.Len())
   731  
   732  	// In addition to generating a list
   733  	for e := queue.Front(); e != nil; e = e.Next() {
   734  		t := strings.TrimPrefix(e.Value.(string), r.VendorDir+string(os.PathSeparator))
   735  		root, sp := util.NormalizeName(t)
   736  
   737  		if root == r.Config.Name {
   738  			continue
   739  		}
   740  
   741  		existing := r.Config.Imports.Get(root)
   742  		if existing == nil && addTest {
   743  			existing = r.Config.DevImports.Get(root)
   744  		}
   745  
   746  		if existing != nil {
   747  			if sp != "" && !existing.HasSubpackage(sp) {
   748  				existing.Subpackages = append(existing.Subpackages, sp)
   749  			}
   750  		} else {
   751  			newDep := &cfg.Dependency{
   752  				Name: root,
   753  			}
   754  			if sp != "" {
   755  				newDep.Subpackages = []string{sp}
   756  			}
   757  
   758  			if addTest {
   759  				r.Config.DevImports = append(r.Config.DevImports, newDep)
   760  			} else {
   761  				r.Config.Imports = append(r.Config.Imports, newDep)
   762  			}
   763  		}
   764  		res = append(res, e.Value.(string))
   765  	}
   766  
   767  	return res, nil
   768  }
   769  
   770  // queueUnseenImports scans a package's imports and adds any new ones to the
   771  // processing queue.
   772  func (r *Resolver) queueUnseen(pkg string, queue *list.List, testDeps, addTest bool) error {
   773  	// A pkg is marked "seen" as soon as we have inspected it the first time.
   774  	// Seen means that we have added all of its imports to the list.
   775  
   776  	// Already queued indicates that we've either already put it into the queue
   777  	// or intentionally not put it in the queue for fatal reasons (e.g. no
   778  	// buildable source).
   779  
   780  	deps, err := r.imports(pkg, testDeps, addTest)
   781  	if err != nil && !strings.HasPrefix(err.Error(), "no buildable Go source") {
   782  		msg.Err("Could not find %s: %s", pkg, err)
   783  		return err
   784  		// NOTE: If we uncomment this, we get lots of "no buildable Go source" errors,
   785  		// which don't ever seem to be helpful. They don't actually indicate an error
   786  		// condition, and it's perfectly okay to run into that condition.
   787  		//} else if err != nil {
   788  		//	msg.Warn(err.Error())
   789  	}
   790  
   791  	for _, d := range deps {
   792  		if _, ok := r.alreadyQ[d]; !ok {
   793  			r.alreadyQ[d] = true
   794  			queue.PushBack(d)
   795  		}
   796  	}
   797  	return nil
   798  }
   799  
   800  // imports gets all of the imports for a given package.
   801  //
   802  // If the package is in GOROOT, this will return an empty list (but not
   803  // an error).
   804  // If it cannot resolve the pkg, it will return an error.
   805  func (r *Resolver) imports(pkg string, testDeps, addTest bool) ([]string, error) {
   806  
   807  	if r.Config.HasIgnore(pkg) {
   808  		msg.Debug("Ignoring %s", pkg)
   809  		return []string{}, nil
   810  	}
   811  
   812  	// If this pkg is marked seen, we don't scan it again.
   813  	if _, ok := r.seen[pkg]; ok {
   814  		msg.Debug("Already saw %s", pkg)
   815  		return []string{}, nil
   816  	}
   817  
   818  	// FIXME: On error this should try to NotFound to the dependency, and then import
   819  	// it again.
   820  	var imps []string
   821  	p, err := r.BuildContext.ImportDir(pkg, 0)
   822  	if err != nil && strings.HasPrefix(err.Error(), "found packages ") {
   823  		// If we got here it's because a package and multiple packages
   824  		// declared. This is often because of an example with a package
   825  		// or main but +build ignore as a build tag. In that case we
   826  		// try to brute force the packages with a slower scan.
   827  		if testDeps {
   828  			_, imps, err = IterativeScan(r.Handler.PkgPath(pkg))
   829  		} else {
   830  			imps, _, err = IterativeScan(r.Handler.PkgPath(pkg))
   831  		}
   832  
   833  		if err != nil {
   834  			return []string{}, err
   835  		}
   836  	} else if err != nil {
   837  		return []string{}, err
   838  	} else {
   839  		if testDeps {
   840  			imps = dedupeStrings(p.TestImports, p.XTestImports)
   841  		} else {
   842  			imps = p.Imports
   843  		}
   844  	}
   845  
   846  	// It is okay to scan a package more than once. In some cases, this is
   847  	// desirable because the package can change between scans (e.g. as a result
   848  	// of a failed scan resolving the situation).
   849  	msg.Debug("=> Scanning %s (%s)", p.ImportPath, pkg)
   850  	r.seen[pkg] = true
   851  
   852  	// Optimization: If it's in GOROOT, it has no imports worth scanning.
   853  	if p.Goroot {
   854  		return []string{}, nil
   855  	}
   856  
   857  	// We are only looking for dependencies in vendor. No root, cgo, etc.
   858  	buf := []string{}
   859  	for _, imp := range imps {
   860  		if r.Config.HasIgnore(imp) {
   861  			msg.Debug("Ignoring %s", imp)
   862  			continue
   863  		}
   864  		info := r.FindPkg(imp)
   865  		switch info.Loc {
   866  		case LocUnknown:
   867  			// Do we resolve here?
   868  			found, err := r.Handler.NotFound(imp, addTest)
   869  			if err != nil {
   870  				msg.Err("Failed to fetch %s: %s", imp, err)
   871  			}
   872  			if found {
   873  				buf = append(buf, filepath.Join(r.VendorDir, filepath.FromSlash(imp)))
   874  				r.VersionHandler.SetVersion(imp, addTest)
   875  				continue
   876  			}
   877  			r.seen[info.Path] = true
   878  		case LocVendor:
   879  			//msg.Debug("Vendored: %s", imp)
   880  			buf = append(buf, info.Path)
   881  			if err := r.Handler.InVendor(imp, addTest); err == nil {
   882  				r.VersionHandler.SetVersion(imp, addTest)
   883  			} else {
   884  				msg.Warn("Error updating %s: %s", imp, err)
   885  			}
   886  		case LocGopath:
   887  			found, err := r.Handler.OnGopath(imp, addTest)
   888  			if err != nil {
   889  				msg.Err("Failed to fetch %s: %s", imp, err)
   890  			}
   891  			// If the Handler marks this as found, we drop it into the buffer
   892  			// for subsequent processing. Otherwise, we assume that we're
   893  			// in a less-than-perfect, but functional, situation.
   894  			if found {
   895  				buf = append(buf, filepath.Join(r.VendorDir, filepath.FromSlash(imp)))
   896  				r.VersionHandler.SetVersion(imp, addTest)
   897  				continue
   898  			}
   899  			msg.Warn("Package %s is on GOPATH, but not vendored. Ignoring.", imp)
   900  			r.seen[info.Path] = true
   901  		default:
   902  			// Local packages are an odd case. CGO cannot be scanned.
   903  			msg.Debug("===> Skipping %s", imp)
   904  		}
   905  	}
   906  
   907  	return buf, nil
   908  }
   909  
   910  // sliceToQueue is a special-purpose function for unwrapping a slice of
   911  // dependencies into a queue of fully qualified paths.
   912  func sliceToQueue(deps []*cfg.Dependency, basepath string) *list.List {
   913  	l := list.New()
   914  	for _, e := range deps {
   915  		if len(e.Subpackages) > 0 {
   916  			for _, v := range e.Subpackages {
   917  				ip := e.Name
   918  				if v != "." && v != "" {
   919  					ip = ip + "/" + v
   920  				}
   921  				msg.Debug("Adding local Import %s to queue", ip)
   922  				l.PushBack(filepath.Join(basepath, filepath.FromSlash(ip)))
   923  			}
   924  		} else {
   925  			msg.Debug("Adding local Import %s to queue", e.Name)
   926  			l.PushBack(filepath.Join(basepath, filepath.FromSlash(e.Name)))
   927  		}
   928  
   929  	}
   930  	return l
   931  }
   932  
   933  // PkgLoc describes the location of the package.
   934  type PkgLoc uint8
   935  
   936  const (
   937  	// LocUnknown indicates the package location is unknown (probably not present)
   938  	LocUnknown PkgLoc = iota
   939  	// LocLocal inidcates that the package is in a local dir, not GOPATH or GOROOT.
   940  	LocLocal
   941  	// LocVendor indicates that the package is in a vendor/ dir
   942  	LocVendor
   943  	// LocGopath inidcates that the package is in GOPATH
   944  	LocGopath
   945  	// LocGoroot indicates that the package is in GOROOT
   946  	LocGoroot
   947  	// LocCgo indicates that the package is a a CGO package
   948  	LocCgo
   949  	// LocAppengine indicates the package is part of the appengine SDK. It's a
   950  	// special build mode. https://blog.golang.org/the-app-engine-sdk-and-workspaces-gopath
   951  	// Why does a Google product get a special case build mode with a local
   952  	// package?
   953  	LocAppengine
   954  	// LocRelative indicates the package is a relative directory
   955  	LocRelative
   956  )
   957  
   958  // PkgInfo represents metadata about a package found by the resolver.
   959  type PkgInfo struct {
   960  	Name, Path string
   961  	Vendored   bool
   962  	Loc        PkgLoc
   963  }
   964  
   965  // FindPkg takes a package name and attempts to find it on the filesystem
   966  //
   967  // The resulting PkgInfo will indicate where it was found.
   968  func (r *Resolver) FindPkg(name string) *PkgInfo {
   969  	// We cachae results for FindPkg to reduce the number of filesystem ops
   970  	// that we have to do. This is a little risky because certain directories,
   971  	// like GOPATH, can be modified while we're running an operation, and
   972  	// render the cache inaccurate.
   973  	//
   974  	// Unfound items (LocUnknown) are never cached because we assume that as
   975  	// part of the response, the Resolver may fetch that dependency.
   976  	if i, ok := r.findCache[name]; ok {
   977  		//msg.Info("Cache hit on %s", name)
   978  		return i
   979  	}
   980  
   981  	// 502 individual packages scanned.
   982  	// No cache:
   983  	// glide -y etcd.yaml list  0.27s user 0.19s system 85% cpu 0.534 total
   984  	// With cache:
   985  	// glide -y etcd.yaml list  0.22s user 0.15s system 85% cpu 0.438 total
   986  
   987  	var p string
   988  	info := &PkgInfo{
   989  		Name: name,
   990  	}
   991  
   992  	if strings.HasPrefix(name, "./") || strings.HasPrefix(name, "../") {
   993  		info.Loc = LocRelative
   994  		r.findCache[name] = info
   995  		return info
   996  	}
   997  
   998  	// Check _only_ if this dep is in the current vendor directory.
   999  	p = filepath.Join(r.VendorDir, filepath.FromSlash(name))
  1000  	if pkgExists(p) {
  1001  		info.Path = p
  1002  		info.Loc = LocVendor
  1003  		info.Vendored = true
  1004  		r.findCache[name] = info
  1005  		return info
  1006  	}
  1007  
  1008  	// TODO: Do we need this if we always flatten?
  1009  	// Recurse backward to scan other vendor/ directories
  1010  	//for wd := cwd; wd != "/"; wd = filepath.Dir(wd) {
  1011  	//p = filepath.Join(wd, "vendor", filepath.FromSlash(name))
  1012  	//if fi, err = os.Stat(p); err == nil && (fi.IsDir() || isLink(fi)) {
  1013  	//info.Path = p
  1014  	//info.PType = ptypeVendor
  1015  	//info.Vendored = true
  1016  	//return info
  1017  	//}
  1018  	//}
  1019  
  1020  	// Check $GOPATH
  1021  	for _, rr := range filepath.SplitList(r.BuildContext.GOPATH) {
  1022  		p = filepath.Join(rr, "src", filepath.FromSlash(name))
  1023  		if pkgExists(p) {
  1024  			info.Path = p
  1025  			info.Loc = LocGopath
  1026  			r.findCache[name] = info
  1027  			return info
  1028  		}
  1029  	}
  1030  
  1031  	// Check $GOROOT
  1032  	for _, rr := range filepath.SplitList(r.BuildContext.GOROOT) {
  1033  		p = filepath.Join(rr, "src", filepath.FromSlash(name))
  1034  		if pkgExists(p) {
  1035  			info.Path = p
  1036  			info.Loc = LocGoroot
  1037  			r.findCache[name] = info
  1038  			return info
  1039  		}
  1040  	}
  1041  
  1042  	// If this is "C", we're dealing with cgo
  1043  	if name == "C" {
  1044  		info.Loc = LocCgo
  1045  		r.findCache[name] = info
  1046  	} else if name == "appengine" || name == "appengine_internal" ||
  1047  		strings.HasPrefix(name, "appengine/") ||
  1048  		strings.HasPrefix(name, "appengine_internal/") {
  1049  		// Appengine is a special case when it comes to Go builds. It is a local
  1050  		// looking package only available within appengine. It's a special case
  1051  		// where Google products are playing with each other.
  1052  		// https://blog.golang.org/the-app-engine-sdk-and-workspaces-gopath
  1053  		info.Loc = LocAppengine
  1054  		r.findCache[name] = info
  1055  	} else if name == "context" || name == "net/http/httptrace" {
  1056  		// context and net/http/httptrace are packages being added to
  1057  		// the Go 1.7 standard library. Some packages, such as golang.org/x/net
  1058  		// are importing it with build flags in files for go1.7. Need to detect
  1059  		// this and handle it.
  1060  		info.Loc = LocGoroot
  1061  		r.findCache[name] = info
  1062  	}
  1063  
  1064  	return info
  1065  }
  1066  
  1067  func pkgExists(path string) bool {
  1068  	fi, err := os.Stat(path)
  1069  	return err == nil && (fi.IsDir() || isLink(fi))
  1070  }
  1071  
  1072  // isLink returns true if the given FileInfo is a symbolic link.
  1073  func isLink(fi os.FileInfo) bool {
  1074  	return fi.Mode()&os.ModeSymlink == os.ModeSymlink
  1075  }
  1076  
  1077  // IsSrcDir returns true if this is a directory that could have source code,
  1078  // false otherwise.
  1079  //
  1080  // Directories with _ or . prefixes are skipped, as are testdata and vendor.
  1081  func IsSrcDir(fi os.FileInfo) bool {
  1082  	return srcDir(fi)
  1083  }
  1084  
  1085  func srcDir(fi os.FileInfo) bool {
  1086  	if !fi.IsDir() {
  1087  		return false
  1088  	}
  1089  
  1090  	// Ignore _foo and .foo
  1091  	if strings.HasPrefix(fi.Name(), "_") || strings.HasPrefix(fi.Name(), ".") {
  1092  		return false
  1093  	}
  1094  
  1095  	// Ignore testdata. For now, ignore vendor.
  1096  	if fi.Name() == "testdata" || fi.Name() == "vendor" {
  1097  		return false
  1098  	}
  1099  
  1100  	return true
  1101  }
  1102  
  1103  // checkForBasedirSymlink checks to see if the given basedir is actually a
  1104  // symlink. In the case that it is a symlink, the symlink is read and returned.
  1105  // If the basedir is not a symlink, the provided basedir argument is simply
  1106  // returned back to the caller.
  1107  func checkForBasedirSymlink(basedir string) (string, error) {
  1108  	fi, err := os.Lstat(basedir)
  1109  	if err != nil {
  1110  		return "", err
  1111  	}
  1112  
  1113  	if fi.Mode()&os.ModeSymlink != 0 {
  1114  		return os.Readlink(basedir)
  1115  	}
  1116  
  1117  	return basedir, nil
  1118  }
  1119  
  1120  // helper func to merge, dedupe, and sort strings
  1121  func dedupeStrings(s1, s2 []string) (r []string) {
  1122  	dedupe := make(map[string]bool)
  1123  
  1124  	if len(s1) > 0 && len(s2) > 0 {
  1125  		for _, i := range s1 {
  1126  			dedupe[i] = true
  1127  		}
  1128  		for _, i := range s2 {
  1129  			dedupe[i] = true
  1130  		}
  1131  
  1132  		for i := range dedupe {
  1133  			r = append(r, i)
  1134  		}
  1135  		// And then re-sort them
  1136  		sort.Strings(r)
  1137  	} else if len(s1) > 0 {
  1138  		r = s1
  1139  	} else if len(s2) > 0 {
  1140  		r = s2
  1141  	}
  1142  
  1143  	return
  1144  }