github.com/anthonymayer/glide@v0.0.0-20160224162501-bff8b50d232e/dependency/resolver.go (about)

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