| package dependency | 
 |  | 
 | import ( | 
 | 	"container/list" | 
 | 	"errors" | 
 | 	"runtime" | 
 | 	"sort" | 
 | 	//"go/build" | 
 | 	"os" | 
 | 	"path/filepath" | 
 | 	"strings" | 
 |  | 
 | 	"github.com/Masterminds/glide/cfg" | 
 | 	"github.com/Masterminds/glide/msg" | 
 | 	gpath "github.com/Masterminds/glide/path" | 
 | 	"github.com/Masterminds/glide/util" | 
 | ) | 
 |  | 
 | // MissingPackageHandler handles the case where a package is missing during scanning. | 
 | // | 
 | // It returns true if the package can be passed to the resolver, false otherwise. | 
 | // False may be returned even if error is nil. | 
 | type MissingPackageHandler interface { | 
 | 	// NotFound is called when the Resolver fails to find a package with the given name. | 
 | 	// | 
 | 	// NotFound returns true when the resolver should attempt to re-resole the | 
 | 	// dependency (e.g. when NotFound has gone and fetched the missing package). | 
 | 	// | 
 | 	// When NotFound returns false, the Resolver does not try to do any additional | 
 | 	// work on the missing package. | 
 | 	// | 
 | 	// NotFound only returns errors when it fails to perform its internal goals. | 
 | 	// When it returns false with no error, this indicates that the handler did | 
 | 	// its job, but the resolver should not do any additional work on the | 
 | 	// package. | 
 | 	NotFound(pkg string, addTest bool) (bool, error) | 
 |  | 
 | 	// OnGopath is called when the Resolver finds a dependency, but it's only on GOPATH. | 
 | 	// | 
 | 	// OnGopath provides an opportunity to copy, move, warn, or ignore cases like this. | 
 | 	// | 
 | 	// OnGopath returns true when the resolver should attempt to re-resolve the | 
 | 	// dependency (e.g. when the dependency is copied to a new location). | 
 | 	// | 
 | 	// When OnGopath returns false, the Resolver does not try to do any additional | 
 | 	// work on the package. | 
 | 	// | 
 | 	// An error indicates that OnGopath cannot complete its intended operation. | 
 | 	// Not all false results are errors. | 
 | 	OnGopath(pkg string, addTest bool) (bool, error) | 
 |  | 
 | 	// InVendor is called when the Resolver finds a dependency in the vendor/ directory. | 
 | 	// | 
 | 	// This can be used update a project found in the vendor/ folder. | 
 | 	InVendor(pkg string, addTest bool) error | 
 |  | 
 | 	// PkgPath is called to find the location locally to scan. This gives the | 
 | 	// handler to do things such as use a cached location. | 
 | 	PkgPath(pkg string) string | 
 | } | 
 |  | 
 | // DefaultMissingPackageHandler is the default handler for missing packages. | 
 | // | 
 | // When asked to handle a missing package, it will report the miss as a warning, | 
 | // and then store the package in the Missing slice for later access. | 
 | type DefaultMissingPackageHandler struct { | 
 | 	Missing []string | 
 | 	Gopath  []string | 
 | 	Prefix  string | 
 | } | 
 |  | 
 | // NotFound prints a warning and then stores the package name in Missing. | 
 | // | 
 | // It never returns an error, and it always returns false. | 
 | func (d *DefaultMissingPackageHandler) NotFound(pkg string, addTest bool) (bool, error) { | 
 | 	msg.Warn("Package %s is not installed", pkg) | 
 | 	d.Missing = append(d.Missing, pkg) | 
 | 	return false, nil | 
 | } | 
 |  | 
 | // OnGopath is run when a package is missing from vendor/ but found in the GOPATH | 
 | func (d *DefaultMissingPackageHandler) OnGopath(pkg string, addTest bool) (bool, error) { | 
 | 	msg.Warn("Package %s is only on GOPATH.", pkg) | 
 | 	d.Gopath = append(d.Gopath, pkg) | 
 | 	return false, nil | 
 | } | 
 |  | 
 | // InVendor is run when a package is found in the vendor/ folder | 
 | func (d *DefaultMissingPackageHandler) InVendor(pkg string, addTest bool) error { | 
 | 	msg.Info("Package %s found in vendor/ folder", pkg) | 
 | 	return nil | 
 | } | 
 |  | 
 | // PkgPath returns the path to the package | 
 | func (d *DefaultMissingPackageHandler) PkgPath(pkg string) string { | 
 | 	if d.Prefix != "" { | 
 | 		return filepath.Join(d.Prefix, pkg) | 
 | 	} | 
 | 	return pkg | 
 | } | 
 |  | 
 | // VersionHandler sets the version for a package when found while scanning. | 
 | // | 
 | // When a package if found it needs to be on the correct version before | 
 | // scanning its contents to be sure to pick up the right elements for that | 
 | // version. | 
 | type VersionHandler interface { | 
 |  | 
 | 	// Process provides an opportunity to process the codebase for version setting. | 
 | 	Process(pkg string) error | 
 |  | 
 | 	// SetVersion sets the version for a package. An error is returned if there | 
 | 	// was a problem setting the version. | 
 | 	SetVersion(pkg string, testDep bool) error | 
 | } | 
 |  | 
 | // DefaultVersionHandler is the default handler for setting the version. | 
 | // | 
 | // The default handler leaves the current version and skips setting a version. | 
 | // For a handler that alters the version see the handler included in the repo | 
 | // package as part of the installer. | 
 | type DefaultVersionHandler struct{} | 
 |  | 
 | // Process a package to aide in version setting. | 
 | func (d *DefaultVersionHandler) Process(pkg string) error { | 
 | 	return nil | 
 | } | 
 |  | 
 | // SetVersion here sends a message when a package is found noting that it | 
 | // did not set the version. | 
 | func (d *DefaultVersionHandler) SetVersion(pkg string, testDep bool) error { | 
 | 	msg.Warn("Version not set for package %s", pkg) | 
 | 	return nil | 
 | } | 
 |  | 
 | // Resolver resolves a dependency tree. | 
 | // | 
 | // It operates in two modes: | 
 | // - local resolution (ResolveLocal) determines the dependencies of the local project. | 
 | // - vendor resolving (Resolve, ResolveAll) determines the dependencies of vendored | 
 | //   projects. | 
 | // | 
 | // Local resolution is for guessing initial dependencies. Vendor resolution is | 
 | // for determining vendored dependencies. | 
 | type Resolver struct { | 
 | 	Handler        MissingPackageHandler | 
 | 	VersionHandler VersionHandler | 
 | 	VendorDir      string | 
 | 	BuildContext   *util.BuildCtxt | 
 | 	Config         *cfg.Config | 
 |  | 
 | 	// ResolveAllFiles toggles deep scanning. | 
 | 	// If this is true, resolve by scanning all files, not by walking the | 
 | 	// import tree. | 
 | 	ResolveAllFiles bool | 
 |  | 
 | 	// ResolveTest sets if test dependencies should be resolved. | 
 | 	ResolveTest bool | 
 |  | 
 | 	// Items already in the queue. | 
 | 	alreadyQ map[string]bool | 
 |  | 
 | 	// Attempts to scan that had unrecoverable error. | 
 | 	hadError map[string]bool | 
 |  | 
 | 	basedir string | 
 | 	seen    map[string]bool | 
 |  | 
 | 	// findCache caches hits from Find. This reduces the number of filesystem | 
 | 	// touches that have to be done for dependency resolution. | 
 | 	findCache map[string]*PkgInfo | 
 | } | 
 |  | 
 | // NewResolver returns a new Resolver initialized with the DefaultMissingPackageHandler. | 
 | // | 
 | // This will return an error if the given path does not meet the basic criteria | 
 | // for a Go source project. For example, basedir must have a vendor subdirectory. | 
 | // | 
 | // The BuildContext uses the "go/build".Default to resolve dependencies. | 
 | func NewResolver(basedir string) (*Resolver, error) { | 
 |  | 
 | 	var err error | 
 | 	basedir, err = filepath.Abs(basedir) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	basedir, err = checkForBasedirSymlink(basedir) | 
 |  | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	vdir := filepath.Join(basedir, "vendor") | 
 |  | 
 | 	buildContext, err := util.GetBuildContext() | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	r := &Resolver{ | 
 | 		Handler:        &DefaultMissingPackageHandler{Missing: []string{}, Gopath: []string{}}, | 
 | 		VersionHandler: &DefaultVersionHandler{}, | 
 | 		basedir:        basedir, | 
 | 		VendorDir:      vdir, | 
 | 		BuildContext:   buildContext, | 
 | 		seen:           map[string]bool{}, | 
 | 		alreadyQ:       map[string]bool{}, | 
 | 		hadError:       map[string]bool{}, | 
 | 		findCache:      map[string]*PkgInfo{}, | 
 |  | 
 | 		// The config instance here should really be replaced with a real one. | 
 | 		Config: &cfg.Config{}, | 
 | 	} | 
 |  | 
 | 	// TODO: Make sure the build context is correctly set up. Especially in | 
 | 	// regards to GOROOT, which is not always set. | 
 |  | 
 | 	return r, nil | 
 | } | 
 |  | 
 | // Resolve takes a package name and returns all of the imported package names. | 
 | // | 
 | // If a package is not found, this calls the Fetcher. If the Fetcher returns | 
 | // true, it will re-try traversing that package for dependencies. Otherwise it | 
 | // will add that package to the deps array and continue on without trying it. | 
 | // And if the Fetcher returns an error, this will stop resolution and return | 
 | // the error. | 
 | // | 
 | // If basepath is set to $GOPATH, this will start from that package's root there. | 
 | // If basepath is set to a project's vendor path, the scanning will begin from | 
 | // there. | 
 | func (r *Resolver) Resolve(pkg, basepath string) ([]string, error) { | 
 | 	target := filepath.Join(basepath, filepath.FromSlash(pkg)) | 
 | 	//msg.Debug("Scanning %s", target) | 
 | 	l := list.New() | 
 | 	l.PushBack(target) | 
 |  | 
 | 	// In this mode, walk the entire tree. | 
 | 	if r.ResolveAllFiles { | 
 | 		return r.resolveList(l, false, false) | 
 | 	} | 
 | 	return r.resolveImports(l, false, false) | 
 | } | 
 |  | 
 | // dirHasPrefix tests whether the directory dir begins with prefix. | 
 | func dirHasPrefix(dir, prefix string) bool { | 
 | 	if runtime.GOOS != "windows" { | 
 | 		return strings.HasPrefix(dir, prefix) | 
 | 	} | 
 | 	return len(dir) >= len(prefix) && strings.EqualFold(dir[:len(prefix)], prefix) | 
 | } | 
 |  | 
 | // ResolveLocal resolves dependencies for the current project. | 
 | // | 
 | // This begins with the project, builds up a list of external dependencies. | 
 | // | 
 | // If the deep flag is set to true, this will then resolve all of the dependencies | 
 | // of the dependencies it has found. If not, it will return just the packages that | 
 | // the base project relies upon. | 
 | func (r *Resolver) ResolveLocal(deep bool) ([]string, []string, error) { | 
 | 	// We build a list of local source to walk, then send this list | 
 | 	// to resolveList. | 
 | 	msg.Debug("Resolving local dependencies") | 
 | 	l := list.New() | 
 | 	tl := list.New() | 
 | 	alreadySeen := map[string]bool{} | 
 | 	talreadySeen := map[string]bool{} | 
 | 	err := filepath.Walk(r.basedir, func(path string, fi os.FileInfo, err error) error { | 
 | 		if err != nil && err != filepath.SkipDir { | 
 | 			return err | 
 | 		} | 
 | 		pt := strings.TrimPrefix(path, r.basedir+string(os.PathSeparator)) | 
 | 		pt = strings.TrimSuffix(pt, string(os.PathSeparator)) | 
 | 		if r.Config.HasExclude(pt) { | 
 | 			msg.Debug("Excluding %s", pt) | 
 | 			return filepath.SkipDir | 
 | 		} | 
 | 		if !fi.IsDir() { | 
 | 			return nil | 
 | 		} | 
 | 		if !srcDir(fi) { | 
 | 			return filepath.SkipDir | 
 | 		} | 
 |  | 
 | 		// Scan for dependencies, and anything that's not part of the local | 
 | 		// package gets added to the scan list. | 
 | 		var imps []string | 
 | 		var testImps []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, testImps, err = IterativeScan(path) | 
 | 				if err != nil { | 
 | 					return err | 
 | 				} | 
 | 			} else { | 
 | 				return err | 
 | 			} | 
 | 		} else { | 
 | 			imps = p.Imports | 
 | 			testImps = dedupeStrings(p.TestImports, p.XTestImports) | 
 | 		} | 
 |  | 
 | 		// We are only looking for dependencies in vendor. No root, cgo, etc. | 
 | 		for _, imp := range imps { | 
 | 			if r.Config.HasIgnore(imp) { | 
 | 				continue | 
 | 			} | 
 | 			if alreadySeen[imp] { | 
 | 				continue | 
 | 			} | 
 | 			alreadySeen[imp] = true | 
 | 			info := r.FindPkg(imp) | 
 | 			switch info.Loc { | 
 | 			case LocUnknown, LocVendor: | 
 | 				l.PushBack(filepath.Join(r.VendorDir, filepath.FromSlash(imp))) // Do we need a path on this? | 
 | 			case LocGopath: | 
 | 				if !dirHasPrefix(info.Path, r.basedir) { | 
 | 					// FIXME: This is a package outside of the project we're | 
 | 					// scanning. It should really be on vendor. But we don't | 
 | 					// want it to reference GOPATH. We want it to be detected | 
 | 					// and moved. | 
 | 					l.PushBack(filepath.Join(r.VendorDir, filepath.FromSlash(imp))) | 
 | 				} | 
 | 			case LocRelative: | 
 | 				if strings.HasPrefix(imp, "./"+gpath.VendorDir) { | 
 | 					msg.Warn("Go package resolving will resolve %s without the ./%s/ prefix", imp, gpath.VendorDir) | 
 | 				} | 
 | 			} | 
 | 		} | 
 |  | 
 | 		if r.ResolveTest { | 
 | 			for _, imp := range testImps { | 
 | 				if talreadySeen[imp] { | 
 | 					continue | 
 | 				} | 
 | 				talreadySeen[imp] = true | 
 | 				info := r.FindPkg(imp) | 
 | 				switch info.Loc { | 
 | 				case LocUnknown, LocVendor: | 
 | 					tl.PushBack(filepath.Join(r.VendorDir, filepath.FromSlash(imp))) // Do we need a path on this? | 
 | 				case LocGopath: | 
 | 					if !dirHasPrefix(info.Path, r.basedir) { | 
 | 						// FIXME: This is a package outside of the project we're | 
 | 						// scanning. It should really be on vendor. But we don't | 
 | 						// want it to reference GOPATH. We want it to be detected | 
 | 						// and moved. | 
 | 						tl.PushBack(filepath.Join(r.VendorDir, filepath.FromSlash(imp))) | 
 | 					} | 
 | 				case LocRelative: | 
 | 					if strings.HasPrefix(imp, "./"+gpath.VendorDir) { | 
 | 						msg.Warn("Go package resolving will resolve %s without the ./%s/ prefix", imp, gpath.VendorDir) | 
 | 					} | 
 | 				} | 
 | 			} | 
 | 		} | 
 |  | 
 | 		return nil | 
 | 	}) | 
 |  | 
 | 	if err != nil { | 
 | 		msg.Err("Failed to build an initial list of packages to scan: %s", err) | 
 | 		return []string{}, []string{}, err | 
 | 	} | 
 |  | 
 | 	if deep { | 
 | 		if r.ResolveAllFiles { | 
 | 			re, err := r.resolveList(l, false, false) | 
 | 			if err != nil { | 
 | 				return []string{}, []string{}, err | 
 | 			} | 
 | 			tre, err := r.resolveList(l, false, true) | 
 | 			return re, tre, err | 
 | 		} | 
 | 		re, err := r.resolveImports(l, false, false) | 
 | 		if err != nil { | 
 | 			return []string{}, []string{}, err | 
 | 		} | 
 | 		tre, err := r.resolveImports(tl, true, true) | 
 | 		return re, tre, err | 
 | 	} | 
 |  | 
 | 	// If we're not doing a deep scan, we just convert the list into an | 
 | 	// array and return. | 
 | 	res := make([]string, 0, l.Len()) | 
 | 	for e := l.Front(); e != nil; e = e.Next() { | 
 | 		res = append(res, e.Value.(string)) | 
 | 	} | 
 | 	tres := make([]string, 0, l.Len()) | 
 | 	if r.ResolveTest { | 
 | 		for e := tl.Front(); e != nil; e = e.Next() { | 
 | 			tres = append(tres, e.Value.(string)) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return res, tres, nil | 
 | } | 
 |  | 
 | // ResolveAll takes a list of packages and returns an inclusive list of all | 
 | // vendored dependencies. | 
 | // | 
 | // While this will scan all of the source code it can find, it will only return | 
 | // packages that were either explicitly passed in as deps, or were explicitly | 
 | // imported by the code. | 
 | // | 
 | // Packages that are either CGO or on GOROOT are ignored. Packages that are | 
 | // on GOPATH, but not vendored currently generate a warning. | 
 | // | 
 | // If one of the passed in packages does not exist in the vendor directory, | 
 | // an error is returned. | 
 | func (r *Resolver) ResolveAll(deps []*cfg.Dependency, addTest bool) ([]string, error) { | 
 |  | 
 | 	queue := sliceToQueue(deps, r.VendorDir) | 
 |  | 
 | 	if r.ResolveAllFiles { | 
 | 		return r.resolveList(queue, false, addTest) | 
 | 	} | 
 | 	return r.resolveImports(queue, false, addTest) | 
 | } | 
 |  | 
 | // Stripv strips the vendor/ prefix from vendored packages. | 
 | func (r *Resolver) Stripv(str string) string { | 
 | 	return strings.TrimPrefix(str, r.VendorDir+string(os.PathSeparator)) | 
 | } | 
 |  | 
 | // vpath adds an absolute vendor path. | 
 | func (r *Resolver) vpath(str string) string { | 
 | 	return filepath.Join(r.basedir, "vendor", str) | 
 | } | 
 |  | 
 | // resolveImports takes a list of existing packages and resolves their imports. | 
 | // | 
 | // It returns a list of all of the packages that it can determine are required | 
 | // for the given code to function. | 
 | // | 
 | // The expectation is that each item in the queue is an absolute path to a | 
 | // vendored package. This attempts to read that package, and then find | 
 | // its referenced packages. Those packages are then added to the list | 
 | // to be scanned next. | 
 | // | 
 | // The resolver's handler is used in the cases where a package cannot be | 
 | // located. | 
 | // | 
 | // testDeps specifies if the test dependencies should be resolved and addTest | 
 | // specifies if the dependencies should be added to the Config.DevImports. This | 
 | // is important because we may resolve normal dependencies of test deps and add | 
 | // them to the DevImports list. | 
 | func (r *Resolver) resolveImports(queue *list.List, testDeps, addTest bool) ([]string, error) { | 
 | 	msg.Debug("Resolving import path") | 
 |  | 
 | 	// When test deps passed in but not resolving return empty. | 
 | 	if (testDeps || addTest) && !r.ResolveTest { | 
 | 		return []string{}, nil | 
 | 	} | 
 |  | 
 | 	alreadySeen := make(map[string]bool, queue.Len()) | 
 |  | 
 | 	for e := queue.Front(); e != nil; e = e.Next() { | 
 | 		vdep := e.Value.(string) | 
 | 		dep := r.Stripv(vdep) | 
 | 		// Check if marked in the Q and then explicitly mark it. We want to know | 
 | 		// if it had previously been marked and ensure it for the future. | 
 |  | 
 | 		if alreadySeen[dep] { | 
 | 			continue | 
 | 		} | 
 | 		alreadySeen[dep] = true | 
 |  | 
 | 		_, foundQ := r.alreadyQ[dep] | 
 | 		r.alreadyQ[dep] = true | 
 |  | 
 | 		// If we've already encountered an error processing this dependency | 
 | 		// skip it. | 
 | 		_, foundErr := r.hadError[dep] | 
 | 		if foundErr { | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		// Skip ignored packages | 
 | 		if r.Config.HasIgnore(dep) { | 
 | 			msg.Debug("Ignoring: %s", dep) | 
 | 			continue | 
 | 		} | 
 | 		r.VersionHandler.Process(dep) | 
 | 		// Here, we want to import the package and see what imports it has. | 
 | 		msg.Debug("Trying to open %s (%s)", dep, r.Handler.PkgPath(dep)) | 
 | 		var imps []string | 
 | 		pkg, err := r.BuildContext.ImportDir(r.Handler.PkgPath(dep), 0) | 
 | 		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) | 
 | 			if testDeps { | 
 | 				_, imps, err = IterativeScan(r.Handler.PkgPath(dep)) | 
 | 			} else { | 
 | 				imps, _, err = IterativeScan(r.Handler.PkgPath(dep)) | 
 | 			} | 
 |  | 
 | 			if err != nil { | 
 | 				msg.Err("Iterative scanning error %s: %s", dep, err) | 
 | 				continue | 
 | 			} | 
 | 		} else if err != nil { | 
 | 			errStr := err.Error() | 
 | 			msg.Debug("ImportDir error on %s: %s", r.Handler.PkgPath(dep), err) | 
 | 			if strings.HasPrefix(errStr, "no buildable Go source") { | 
 | 				msg.Debug("No subpackages declared. Skipping %s.", dep) | 
 | 				continue | 
 | 			} else if os.IsNotExist(err) && !foundErr && !foundQ { | 
 | 				// If the location doesn't exist, there hasn't already been an | 
 | 				// error, it's not already been in the Q then try to fetch it. | 
 | 				// When there's an error or it's already in the Q (it should be | 
 | 				// fetched if it's marked in r.alreadyQ) we skip to make sure | 
 | 				// not to get stuck in a recursion. | 
 |  | 
 | 				// If the location doesn't exist try to fetch it. | 
 | 				if ok, err2 := r.Handler.NotFound(dep, addTest); ok { | 
 | 					r.alreadyQ[dep] = true | 
 | 					alreadySeen[dep] = false | 
 |  | 
 | 					// By adding to the queue it will get reprocessed now that | 
 | 					// it exists. | 
 | 					queue.PushBack(r.vpath(dep)) | 
 | 					r.VersionHandler.SetVersion(dep, addTest) | 
 | 				} else if err2 != nil { | 
 | 					r.hadError[dep] = true | 
 | 					msg.Err("Error looking for %s: %s", dep, err2) | 
 | 				} else { | 
 | 					r.hadError[dep] = true | 
 | 					// TODO (mpb): Should we toss this into a Handler to | 
 | 					// see if this is on GOPATH and copy it? | 
 | 					msg.Info("Not found in vendor/: %s (1)", dep) | 
 | 				} | 
 | 			} else if strings.Contains(errStr, "no such file or directory") { | 
 | 				r.hadError[dep] = true | 
 | 				msg.Err("Error scanning %s: %s", dep, err) | 
 | 				msg.Err("This error means the referenced package was not found.") | 
 | 				msg.Err("Missing file or directory errors usually occur when multiple packages") | 
 | 				msg.Err("share a common dependency and the first reference encountered by the scanner") | 
 | 				msg.Err("sets the version to one that does not contain a subpackage needed required") | 
 | 				msg.Err("by another package that uses the shared dependency. Try setting a") | 
 | 				msg.Err("version in your glide.yaml that works for all packages that share this") | 
 | 				msg.Err("dependency.") | 
 | 			} else { | 
 | 				r.hadError[dep] = true | 
 | 				msg.Err("Error scanning %s: %s", dep, err) | 
 | 			} | 
 | 			continue | 
 | 		} else { | 
 | 			if testDeps { | 
 | 				imps = dedupeStrings(pkg.TestImports, pkg.XTestImports) | 
 | 			} else { | 
 | 				imps = pkg.Imports | 
 | 			} | 
 |  | 
 | 		} | 
 |  | 
 | 		// Range over all of the identified imports and see which ones we | 
 | 		// can locate. | 
 | 		for _, imp := range imps { | 
 | 			if r.Config.HasIgnore(imp) { | 
 | 				msg.Debug("Ignoring: %s", imp) | 
 | 				continue | 
 | 			} | 
 | 			pi := r.FindPkg(imp) | 
 | 			if pi.Loc != LocCgo && pi.Loc != LocGoroot && pi.Loc != LocAppengine { | 
 | 				msg.Debug("Package %s imports %s", dep, imp) | 
 | 			} | 
 | 			switch pi.Loc { | 
 | 			case LocVendor: | 
 | 				msg.Debug("In vendor: %s", imp) | 
 | 				if _, ok := r.alreadyQ[imp]; !ok { | 
 | 					msg.Debug("Marking %s to be scanned.", imp) | 
 | 					r.alreadyQ[imp] = true | 
 | 					queue.PushBack(r.vpath(imp)) | 
 | 					if err := r.Handler.InVendor(imp, addTest); err == nil { | 
 | 						r.VersionHandler.SetVersion(imp, addTest) | 
 | 					} else { | 
 | 						msg.Warn("Error updating %s: %s", imp, err) | 
 | 					} | 
 | 				} | 
 | 			case LocUnknown: | 
 | 				msg.Debug("Missing %s. Trying to resolve.", imp) | 
 | 				if ok, err := r.Handler.NotFound(imp, addTest); ok { | 
 | 					r.alreadyQ[imp] = true | 
 | 					queue.PushBack(r.vpath(imp)) | 
 | 					r.VersionHandler.SetVersion(imp, addTest) | 
 | 				} else if err != nil { | 
 | 					r.hadError[imp] = true | 
 | 					msg.Err("Error looking for %s: %s", imp, err) | 
 | 				} else { | 
 | 					r.hadError[imp] = true | 
 | 					msg.Err("Not found: %s (2)", imp) | 
 | 				} | 
 | 			case LocGopath: | 
 | 				msg.Debug("Found on GOPATH, not vendor: %s", imp) | 
 | 				if _, ok := r.alreadyQ[imp]; !ok { | 
 | 					// Only scan it if it gets moved into vendor/ | 
 | 					if ok, _ := r.Handler.OnGopath(imp, addTest); ok { | 
 | 						r.alreadyQ[imp] = true | 
 | 						queue.PushBack(r.vpath(imp)) | 
 | 						r.VersionHandler.SetVersion(imp, addTest) | 
 | 					} | 
 | 				} | 
 | 			} | 
 | 		} | 
 |  | 
 | 	} | 
 |  | 
 | 	if len(r.hadError) > 0 { | 
 | 		// Errors occurred so we return. | 
 | 		return []string{}, errors.New("Error resolving imports") | 
 | 	} | 
 |  | 
 | 	// FIXME: From here to the end is a straight copy of the resolveList() func. | 
 | 	res := make([]string, 0, queue.Len()) | 
 |  | 
 | 	// In addition to generating a list | 
 | 	for e := queue.Front(); e != nil; e = e.Next() { | 
 | 		t := r.Stripv(e.Value.(string)) | 
 | 		root, sp := util.NormalizeName(t) | 
 |  | 
 | 		if root == r.Config.Name { | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		// Skip ignored packages | 
 | 		if r.Config.HasIgnore(e.Value.(string)) { | 
 | 			msg.Debug("Ignoring: %s", e.Value.(string)) | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		// TODO(mattfarina): Need to eventually support devImport | 
 | 		existing := r.Config.Imports.Get(root) | 
 | 		if existing == nil && addTest { | 
 | 			existing = r.Config.DevImports.Get(root) | 
 | 		} | 
 | 		if existing != nil { | 
 | 			if sp != "" && !existing.HasSubpackage(sp) { | 
 | 				existing.Subpackages = append(existing.Subpackages, sp) | 
 | 			} | 
 | 		} else { | 
 | 			newDep := &cfg.Dependency{ | 
 | 				Name: root, | 
 | 			} | 
 | 			if sp != "" { | 
 | 				newDep.Subpackages = []string{sp} | 
 | 			} | 
 |  | 
 | 			if addTest { | 
 | 				r.Config.DevImports = append(r.Config.DevImports, newDep) | 
 | 			} else { | 
 | 				r.Config.Imports = append(r.Config.Imports, newDep) | 
 | 			} | 
 | 		} | 
 | 		res = append(res, t) | 
 | 	} | 
 |  | 
 | 	return res, nil | 
 | } | 
 |  | 
 | // resolveList takes a list and resolves it. | 
 | // | 
 | // This walks the entire file tree for the given dependencies, not just the | 
 | // parts that are imported directly. Using this will discover dependencies | 
 | // regardless of OS, and arch. | 
 | func (r *Resolver) resolveList(queue *list.List, testDeps, addTest bool) ([]string, error) { | 
 | 	// When test deps passed in but not resolving return empty. | 
 | 	if testDeps && !r.ResolveTest { | 
 | 		return []string{}, nil | 
 | 	} | 
 |  | 
 | 	var failedDep string | 
 | 	var failedDepPath string | 
 | 	var pkgPath string | 
 | 	for e := queue.Front(); e != nil; e = e.Next() { | 
 | 		dep := e.Value.(string) | 
 | 		t := strings.TrimPrefix(dep, r.VendorDir+string(os.PathSeparator)) | 
 | 		if r.Config.HasIgnore(t) { | 
 | 			msg.Debug("Ignoring: %s", t) | 
 | 			continue | 
 | 		} | 
 | 		r.VersionHandler.Process(t) | 
 | 		//msg.Warn("#### %s ####", dep) | 
 | 		//msg.Info("Seen Count: %d", len(r.seen)) | 
 | 		// Catch the outtermost dependency. | 
 | 		pkgPath = r.Handler.PkgPath(t) | 
 | 		failedDep = t | 
 | 		failedDepPath = pkgPath | 
 | 		err := filepath.Walk(pkgPath, func(path string, fi os.FileInfo, err error) error { | 
 | 			if err != nil && err != filepath.SkipDir { | 
 | 				return err | 
 | 			} | 
 |  | 
 | 			// Skip files. | 
 | 			if !fi.IsDir() { | 
 | 				return nil | 
 | 			} | 
 | 			// Skip dirs that are not source. | 
 | 			if !srcDir(fi) { | 
 | 				//msg.Debug("Skip resource %s", fi.Name()) | 
 | 				return filepath.SkipDir | 
 | 			} | 
 |  | 
 | 			// Anything that comes through here has already been through | 
 | 			// the queue. | 
 | 			r.alreadyQ[path] = true | 
 | 			e := r.queueUnseen(path, queue, testDeps, addTest) | 
 | 			if e != nil { | 
 | 				failedDepPath = path | 
 | 				//msg.Err("Failed to fetch dependency %s: %s", path, err) | 
 | 			} | 
 | 			return e | 
 | 		}) | 
 | 		if err != nil && err != filepath.SkipDir { | 
 | 			msg.Err("Dependency %s (%s) failed to resolve: %s.", failedDep, failedDepPath, err) | 
 | 			return []string{}, err | 
 | 		} | 
 | 	} | 
 |  | 
 | 	res := make([]string, 0, queue.Len()) | 
 |  | 
 | 	// In addition to generating a list | 
 | 	for e := queue.Front(); e != nil; e = e.Next() { | 
 | 		t := strings.TrimPrefix(e.Value.(string), r.VendorDir+string(os.PathSeparator)) | 
 | 		root, sp := util.NormalizeName(t) | 
 |  | 
 | 		if root == r.Config.Name { | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		existing := r.Config.Imports.Get(root) | 
 | 		if existing == nil && addTest { | 
 | 			existing = r.Config.DevImports.Get(root) | 
 | 		} | 
 |  | 
 | 		if existing != nil { | 
 | 			if sp != "" && !existing.HasSubpackage(sp) { | 
 | 				existing.Subpackages = append(existing.Subpackages, sp) | 
 | 			} | 
 | 		} else { | 
 | 			newDep := &cfg.Dependency{ | 
 | 				Name: root, | 
 | 			} | 
 | 			if sp != "" { | 
 | 				newDep.Subpackages = []string{sp} | 
 | 			} | 
 |  | 
 | 			if addTest { | 
 | 				r.Config.DevImports = append(r.Config.DevImports, newDep) | 
 | 			} else { | 
 | 				r.Config.Imports = append(r.Config.Imports, newDep) | 
 | 			} | 
 | 		} | 
 | 		res = append(res, e.Value.(string)) | 
 | 	} | 
 |  | 
 | 	return res, nil | 
 | } | 
 |  | 
 | // queueUnseenImports scans a package's imports and adds any new ones to the | 
 | // processing queue. | 
 | func (r *Resolver) queueUnseen(pkg string, queue *list.List, testDeps, addTest bool) error { | 
 | 	// A pkg is marked "seen" as soon as we have inspected it the first time. | 
 | 	// Seen means that we have added all of its imports to the list. | 
 |  | 
 | 	// Already queued indicates that we've either already put it into the queue | 
 | 	// or intentionally not put it in the queue for fatal reasons (e.g. no | 
 | 	// buildable source). | 
 |  | 
 | 	deps, err := r.imports(pkg, testDeps, addTest) | 
 | 	if err != nil && !strings.HasPrefix(err.Error(), "no buildable Go source") { | 
 | 		msg.Err("Could not find %s: %s", pkg, err) | 
 | 		return err | 
 | 		// NOTE: If we uncomment this, we get lots of "no buildable Go source" errors, | 
 | 		// which don't ever seem to be helpful. They don't actually indicate an error | 
 | 		// condition, and it's perfectly okay to run into that condition. | 
 | 		//} else if err != nil { | 
 | 		//	msg.Warn(err.Error()) | 
 | 	} | 
 |  | 
 | 	for _, d := range deps { | 
 | 		if _, ok := r.alreadyQ[d]; !ok { | 
 | 			r.alreadyQ[d] = true | 
 | 			queue.PushBack(d) | 
 | 		} | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | // imports gets all of the imports for a given package. | 
 | // | 
 | // If the package is in GOROOT, this will return an empty list (but not | 
 | // an error). | 
 | // If it cannot resolve the pkg, it will return an error. | 
 | func (r *Resolver) imports(pkg string, testDeps, addTest bool) ([]string, error) { | 
 |  | 
 | 	if r.Config.HasIgnore(pkg) { | 
 | 		msg.Debug("Ignoring %s", pkg) | 
 | 		return []string{}, nil | 
 | 	} | 
 |  | 
 | 	// If this pkg is marked seen, we don't scan it again. | 
 | 	if _, ok := r.seen[pkg]; ok { | 
 | 		msg.Debug("Already saw %s", pkg) | 
 | 		return []string{}, nil | 
 | 	} | 
 |  | 
 | 	// 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 && 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. | 
 | 		if testDeps { | 
 | 			_, imps, err = IterativeScan(r.Handler.PkgPath(pkg)) | 
 | 		} else { | 
 | 			imps, _, err = IterativeScan(r.Handler.PkgPath(pkg)) | 
 | 		} | 
 |  | 
 | 		if err != nil { | 
 | 			return []string{}, err | 
 | 		} | 
 | 	} else if err != nil { | 
 | 		return []string{}, err | 
 | 	} else { | 
 | 		if testDeps { | 
 | 			imps = dedupeStrings(p.TestImports, p.XTestImports) | 
 | 		} else { | 
 | 			imps = p.Imports | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// It is okay to scan a package more than once. In some cases, this is | 
 | 	// desirable because the package can change between scans (e.g. as a result | 
 | 	// of a failed scan resolving the situation). | 
 | 	msg.Debug("=> Scanning %s (%s)", p.ImportPath, pkg) | 
 | 	r.seen[pkg] = true | 
 |  | 
 | 	// Optimization: If it's in GOROOT, it has no imports worth scanning. | 
 | 	if p.Goroot { | 
 | 		return []string{}, nil | 
 | 	} | 
 |  | 
 | 	// We are only looking for dependencies in vendor. No root, cgo, etc. | 
 | 	buf := []string{} | 
 | 	for _, imp := range imps { | 
 | 		if r.Config.HasIgnore(imp) { | 
 | 			msg.Debug("Ignoring %s", imp) | 
 | 			continue | 
 | 		} | 
 | 		info := r.FindPkg(imp) | 
 | 		switch info.Loc { | 
 | 		case LocUnknown: | 
 | 			// Do we resolve here? | 
 | 			found, err := r.Handler.NotFound(imp, addTest) | 
 | 			if err != nil { | 
 | 				msg.Err("Failed to fetch %s: %s", imp, err) | 
 | 			} | 
 | 			if found { | 
 | 				buf = append(buf, filepath.Join(r.VendorDir, filepath.FromSlash(imp))) | 
 | 				r.VersionHandler.SetVersion(imp, addTest) | 
 | 				continue | 
 | 			} | 
 | 			r.seen[info.Path] = true | 
 | 		case LocVendor: | 
 | 			//msg.Debug("Vendored: %s", imp) | 
 | 			buf = append(buf, info.Path) | 
 | 			if err := r.Handler.InVendor(imp, addTest); err == nil { | 
 | 				r.VersionHandler.SetVersion(imp, addTest) | 
 | 			} else { | 
 | 				msg.Warn("Error updating %s: %s", imp, err) | 
 | 			} | 
 | 		case LocGopath: | 
 | 			found, err := r.Handler.OnGopath(imp, addTest) | 
 | 			if err != nil { | 
 | 				msg.Err("Failed to fetch %s: %s", imp, err) | 
 | 			} | 
 | 			// If the Handler marks this as found, we drop it into the buffer | 
 | 			// for subsequent processing. Otherwise, we assume that we're | 
 | 			// in a less-than-perfect, but functional, situation. | 
 | 			if found { | 
 | 				buf = append(buf, filepath.Join(r.VendorDir, filepath.FromSlash(imp))) | 
 | 				r.VersionHandler.SetVersion(imp, addTest) | 
 | 				continue | 
 | 			} | 
 | 			msg.Warn("Package %s is on GOPATH, but not vendored. Ignoring.", imp) | 
 | 			r.seen[info.Path] = true | 
 | 		default: | 
 | 			// Local packages are an odd case. CGO cannot be scanned. | 
 | 			msg.Debug("===> Skipping %s", imp) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return buf, nil | 
 | } | 
 |  | 
 | // sliceToQueue is a special-purpose function for unwrapping a slice of | 
 | // dependencies into a queue of fully qualified paths. | 
 | func sliceToQueue(deps []*cfg.Dependency, basepath string) *list.List { | 
 | 	l := list.New() | 
 | 	for _, e := range deps { | 
 | 		if len(e.Subpackages) > 0 { | 
 | 			for _, v := range e.Subpackages { | 
 | 				ip := e.Name | 
 | 				if v != "." && v != "" { | 
 | 					ip = ip + "/" + v | 
 | 				} | 
 | 				msg.Debug("Adding local Import %s to queue", ip) | 
 | 				l.PushBack(filepath.Join(basepath, filepath.FromSlash(ip))) | 
 | 			} | 
 | 		} else { | 
 | 			msg.Debug("Adding local Import %s to queue", e.Name) | 
 | 			l.PushBack(filepath.Join(basepath, filepath.FromSlash(e.Name))) | 
 | 		} | 
 |  | 
 | 	} | 
 | 	return l | 
 | } | 
 |  | 
 | // PkgLoc describes the location of the package. | 
 | type PkgLoc uint8 | 
 |  | 
 | const ( | 
 | 	// LocUnknown indicates the package location is unknown (probably not present) | 
 | 	LocUnknown PkgLoc = iota | 
 | 	// LocLocal inidcates that the package is in a local dir, not GOPATH or GOROOT. | 
 | 	LocLocal | 
 | 	// LocVendor indicates that the package is in a vendor/ dir | 
 | 	LocVendor | 
 | 	// LocGopath inidcates that the package is in GOPATH | 
 | 	LocGopath | 
 | 	// LocGoroot indicates that the package is in GOROOT | 
 | 	LocGoroot | 
 | 	// LocCgo indicates that the package is a a CGO package | 
 | 	LocCgo | 
 | 	// LocAppengine indicates the package is part of the appengine SDK. It's a | 
 | 	// special build mode. https://blog.golang.org/the-app-engine-sdk-and-workspaces-gopath | 
 | 	// Why does a Google product get a special case build mode with a local | 
 | 	// package? | 
 | 	LocAppengine | 
 | 	// LocRelative indicates the package is a relative directory | 
 | 	LocRelative | 
 | ) | 
 |  | 
 | // PkgInfo represents metadata about a package found by the resolver. | 
 | type PkgInfo struct { | 
 | 	Name, Path string | 
 | 	Vendored   bool | 
 | 	Loc        PkgLoc | 
 | } | 
 |  | 
 | // FindPkg takes a package name and attempts to find it on the filesystem | 
 | // | 
 | // The resulting PkgInfo will indicate where it was found. | 
 | func (r *Resolver) FindPkg(name string) *PkgInfo { | 
 | 	// We cachae results for FindPkg to reduce the number of filesystem ops | 
 | 	// that we have to do. This is a little risky because certain directories, | 
 | 	// like GOPATH, can be modified while we're running an operation, and | 
 | 	// render the cache inaccurate. | 
 | 	// | 
 | 	// Unfound items (LocUnknown) are never cached because we assume that as | 
 | 	// part of the response, the Resolver may fetch that dependency. | 
 | 	if i, ok := r.findCache[name]; ok { | 
 | 		//msg.Info("Cache hit on %s", name) | 
 | 		return i | 
 | 	} | 
 |  | 
 | 	// 502 individual packages scanned. | 
 | 	// No cache: | 
 | 	// glide -y etcd.yaml list  0.27s user 0.19s system 85% cpu 0.534 total | 
 | 	// With cache: | 
 | 	// glide -y etcd.yaml list  0.22s user 0.15s system 85% cpu 0.438 total | 
 |  | 
 | 	var p string | 
 | 	info := &PkgInfo{ | 
 | 		Name: name, | 
 | 	} | 
 |  | 
 | 	if strings.HasPrefix(name, "./") || strings.HasPrefix(name, "../") { | 
 | 		info.Loc = LocRelative | 
 | 		r.findCache[name] = info | 
 | 		return info | 
 | 	} | 
 |  | 
 | 	// Check _only_ if this dep is in the current vendor directory. | 
 | 	p = filepath.Join(r.VendorDir, filepath.FromSlash(name)) | 
 | 	if pkgExists(p) { | 
 | 		info.Path = p | 
 | 		info.Loc = LocVendor | 
 | 		info.Vendored = true | 
 | 		r.findCache[name] = info | 
 | 		return info | 
 | 	} | 
 |  | 
 | 	// TODO: Do we need this if we always flatten? | 
 | 	// Recurse backward to scan other vendor/ directories | 
 | 	//for wd := cwd; wd != "/"; wd = filepath.Dir(wd) { | 
 | 	//p = filepath.Join(wd, "vendor", filepath.FromSlash(name)) | 
 | 	//if fi, err = os.Stat(p); err == nil && (fi.IsDir() || isLink(fi)) { | 
 | 	//info.Path = p | 
 | 	//info.PType = ptypeVendor | 
 | 	//info.Vendored = true | 
 | 	//return info | 
 | 	//} | 
 | 	//} | 
 |  | 
 | 	// Check $GOPATH | 
 | 	for _, rr := range filepath.SplitList(r.BuildContext.GOPATH) { | 
 | 		p = filepath.Join(rr, "src", filepath.FromSlash(name)) | 
 | 		if pkgExists(p) { | 
 | 			info.Path = p | 
 | 			info.Loc = LocGopath | 
 | 			r.findCache[name] = info | 
 | 			return info | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// Check $GOROOT | 
 | 	for _, rr := range filepath.SplitList(r.BuildContext.GOROOT) { | 
 | 		p = filepath.Join(rr, "src", filepath.FromSlash(name)) | 
 | 		if pkgExists(p) { | 
 | 			info.Path = p | 
 | 			info.Loc = LocGoroot | 
 | 			r.findCache[name] = info | 
 | 			return info | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// If this is "C", we're dealing with cgo | 
 | 	if name == "C" { | 
 | 		info.Loc = LocCgo | 
 | 		r.findCache[name] = info | 
 | 	} else if name == "appengine" || name == "appengine_internal" || | 
 | 		strings.HasPrefix(name, "appengine/") || | 
 | 		strings.HasPrefix(name, "appengine_internal/") { | 
 | 		// Appengine is a special case when it comes to Go builds. It is a local | 
 | 		// looking package only available within appengine. It's a special case | 
 | 		// where Google products are playing with each other. | 
 | 		// https://blog.golang.org/the-app-engine-sdk-and-workspaces-gopath | 
 | 		info.Loc = LocAppengine | 
 | 		r.findCache[name] = info | 
 | 	} else if name == "context" || name == "net/http/httptrace" { | 
 | 		// context and net/http/httptrace are packages being added to | 
 | 		// the Go 1.7 standard library. Some packages, such as golang.org/x/net | 
 | 		// are importing it with build flags in files for go1.7. Need to detect | 
 | 		// this and handle it. | 
 | 		info.Loc = LocGoroot | 
 | 		r.findCache[name] = info | 
 | 	} | 
 |  | 
 | 	return info | 
 | } | 
 |  | 
 | func pkgExists(path string) bool { | 
 | 	fi, err := os.Stat(path) | 
 | 	return err == nil && (fi.IsDir() || isLink(fi)) | 
 | } | 
 |  | 
 | // isLink returns true if the given FileInfo is a symbolic link. | 
 | func isLink(fi os.FileInfo) bool { | 
 | 	return fi.Mode()&os.ModeSymlink == os.ModeSymlink | 
 | } | 
 |  | 
 | // IsSrcDir returns true if this is a directory that could have source code, | 
 | // false otherwise. | 
 | // | 
 | // Directories with _ or . prefixes are skipped, as are testdata and vendor. | 
 | func IsSrcDir(fi os.FileInfo) bool { | 
 | 	return srcDir(fi) | 
 | } | 
 |  | 
 | func srcDir(fi os.FileInfo) bool { | 
 | 	if !fi.IsDir() { | 
 | 		return false | 
 | 	} | 
 |  | 
 | 	// Ignore _foo and .foo | 
 | 	if strings.HasPrefix(fi.Name(), "_") || strings.HasPrefix(fi.Name(), ".") { | 
 | 		return false | 
 | 	} | 
 |  | 
 | 	// Ignore testdata. For now, ignore vendor. | 
 | 	if fi.Name() == "testdata" || fi.Name() == "vendor" { | 
 | 		return false | 
 | 	} | 
 |  | 
 | 	return true | 
 | } | 
 |  | 
 | // checkForBasedirSymlink checks to see if the given basedir is actually a | 
 | // symlink. In the case that it is a symlink, the symlink is read and returned. | 
 | // If the basedir is not a symlink, the provided basedir argument is simply | 
 | // returned back to the caller. | 
 | func checkForBasedirSymlink(basedir string) (string, error) { | 
 | 	fi, err := os.Lstat(basedir) | 
 | 	if err != nil { | 
 | 		return "", err | 
 | 	} | 
 |  | 
 | 	if fi.Mode()&os.ModeSymlink != 0 { | 
 | 		return os.Readlink(basedir) | 
 | 	} | 
 |  | 
 | 	return basedir, nil | 
 | } | 
 |  | 
 | // helper func to merge, dedupe, and sort strings | 
 | func dedupeStrings(s1, s2 []string) (r []string) { | 
 | 	dedupe := make(map[string]bool) | 
 |  | 
 | 	if len(s1) > 0 && len(s2) > 0 { | 
 | 		for _, i := range s1 { | 
 | 			dedupe[i] = true | 
 | 		} | 
 | 		for _, i := range s2 { | 
 | 			dedupe[i] = true | 
 | 		} | 
 |  | 
 | 		for i := range dedupe { | 
 | 			r = append(r, i) | 
 | 		} | 
 | 		// And then re-sort them | 
 | 		sort.Strings(r) | 
 | 	} else if len(s1) > 0 { | 
 | 		r = s1 | 
 | 	} else if len(s2) > 0 { | 
 | 		r = s2 | 
 | 	} | 
 |  | 
 | 	return | 
 | } |