|  | // Copyright 2014 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "errors" | 
|  | "flag" | 
|  | "fmt" | 
|  | "go/build" | 
|  | "go/types" | 
|  | "io/ioutil" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "strings" | 
|  | ) | 
|  |  | 
|  | var ( | 
|  | source  = flag.String("s", "", "only consider packages from src, where src is one of the supported compilers") | 
|  | verbose = flag.Bool("v", false, "verbose mode") | 
|  | ) | 
|  |  | 
|  | // lists of registered sources and corresponding importers | 
|  | var ( | 
|  | sources         []string | 
|  | importers       []types.Importer | 
|  | errImportFailed = errors.New("import failed") | 
|  | ) | 
|  |  | 
|  | func usage() { | 
|  | fmt.Fprintln(os.Stderr, "usage: godex [flags] {path|qualifiedIdent}") | 
|  | flag.PrintDefaults() | 
|  | os.Exit(2) | 
|  | } | 
|  |  | 
|  | func report(msg string) { | 
|  | fmt.Fprintln(os.Stderr, "error: "+msg) | 
|  | os.Exit(2) | 
|  | } | 
|  |  | 
|  | func main() { | 
|  | flag.Usage = usage | 
|  | flag.Parse() | 
|  |  | 
|  | if flag.NArg() == 0 { | 
|  | report("no package name, path, or file provided") | 
|  | } | 
|  |  | 
|  | var imp types.Importer = new(tryImporters) | 
|  | if *source != "" { | 
|  | imp = lookup(*source) | 
|  | if imp == nil { | 
|  | report("source (-s argument) must be one of: " + strings.Join(sources, ", ")) | 
|  | } | 
|  | } | 
|  |  | 
|  | for _, arg := range flag.Args() { | 
|  | path, name := splitPathIdent(arg) | 
|  | logf("\tprocessing %q: path = %q, name = %s\n", arg, path, name) | 
|  |  | 
|  | // generate possible package path prefixes | 
|  | // (at the moment we do this for each argument - should probably cache the generated prefixes) | 
|  | prefixes := make(chan string) | 
|  | go genPrefixes(prefixes, !filepath.IsAbs(path) && !build.IsLocalImport(path)) | 
|  |  | 
|  | // import package | 
|  | pkg, err := tryPrefixes(prefixes, path, imp) | 
|  | if err != nil { | 
|  | logf("\t=> ignoring %q: %s\n", path, err) | 
|  | continue | 
|  | } | 
|  |  | 
|  | // filter objects if needed | 
|  | var filter func(types.Object) bool | 
|  | if name != "" { | 
|  | filter = func(obj types.Object) bool { | 
|  | // TODO(gri) perhaps use regular expression matching here? | 
|  | return obj.Name() == name | 
|  | } | 
|  | } | 
|  |  | 
|  | // print contents | 
|  | print(os.Stdout, pkg, filter) | 
|  | } | 
|  | } | 
|  |  | 
|  | func logf(format string, args ...interface{}) { | 
|  | if *verbose { | 
|  | fmt.Fprintf(os.Stderr, format, args...) | 
|  | } | 
|  | } | 
|  |  | 
|  | // splitPathIdent splits a path.name argument into its components. | 
|  | // All but the last path element may contain dots. | 
|  | func splitPathIdent(arg string) (path, name string) { | 
|  | if i := strings.LastIndex(arg, "."); i >= 0 { | 
|  | if j := strings.LastIndex(arg, "/"); j < i { | 
|  | // '.' is not part of path | 
|  | path = arg[:i] | 
|  | name = arg[i+1:] | 
|  | return | 
|  | } | 
|  | } | 
|  | path = arg | 
|  | return | 
|  | } | 
|  |  | 
|  | // tryPrefixes tries to import the package given by (the possibly partial) path using the given importer imp | 
|  | // by prepending all possible prefixes to path. It returns with the first package that it could import, or | 
|  | // with an error. | 
|  | func tryPrefixes(prefixes chan string, path string, imp types.Importer) (pkg *types.Package, err error) { | 
|  | for prefix := range prefixes { | 
|  | actual := path | 
|  | if prefix == "" { | 
|  | // don't use filepath.Join as it will sanitize the path and remove | 
|  | // a leading dot and then the path is not recognized as a relative | 
|  | // package path by the importers anymore | 
|  | logf("\ttrying no prefix\n") | 
|  | } else { | 
|  | actual = filepath.Join(prefix, path) | 
|  | logf("\ttrying prefix %q\n", prefix) | 
|  | } | 
|  | pkg, err = imp.Import(actual) | 
|  | if err == nil { | 
|  | break | 
|  | } | 
|  | logf("\t=> importing %q failed: %s\n", actual, err) | 
|  | } | 
|  | return | 
|  | } | 
|  |  | 
|  | // tryImporters is an importer that tries all registered importers | 
|  | // successively until one of them succeeds or all of them failed. | 
|  | type tryImporters struct{} | 
|  |  | 
|  | func (t *tryImporters) Import(path string) (pkg *types.Package, err error) { | 
|  | for i, imp := range importers { | 
|  | logf("\t\ttrying %s import\n", sources[i]) | 
|  | pkg, err = imp.Import(path) | 
|  | if err == nil { | 
|  | break | 
|  | } | 
|  | logf("\t\t=> %s import failed: %s\n", sources[i], err) | 
|  | } | 
|  | return | 
|  | } | 
|  |  | 
|  | type protector struct { | 
|  | imp types.Importer | 
|  | } | 
|  |  | 
|  | func (p *protector) Import(path string) (pkg *types.Package, err error) { | 
|  | defer func() { | 
|  | if recover() != nil { | 
|  | pkg = nil | 
|  | err = errImportFailed | 
|  | } | 
|  | }() | 
|  | return p.imp.Import(path) | 
|  | } | 
|  |  | 
|  | // protect protects an importer imp from panics and returns the protected importer. | 
|  | func protect(imp types.Importer) types.Importer { | 
|  | return &protector{imp} | 
|  | } | 
|  |  | 
|  | // register registers an importer imp for a given source src. | 
|  | func register(src string, imp types.Importer) { | 
|  | if lookup(src) != nil { | 
|  | panic(src + " importer already registered") | 
|  | } | 
|  | sources = append(sources, src) | 
|  | importers = append(importers, protect(imp)) | 
|  | } | 
|  |  | 
|  | // lookup returns the importer imp for a given source src. | 
|  | func lookup(src string) types.Importer { | 
|  | for i, s := range sources { | 
|  | if s == src { | 
|  | return importers[i] | 
|  | } | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func genPrefixes(out chan string, all bool) { | 
|  | out <- "" | 
|  | if all { | 
|  | platform := build.Default.GOOS + "_" + build.Default.GOARCH | 
|  | dirnames := append([]string{build.Default.GOROOT}, filepath.SplitList(build.Default.GOPATH)...) | 
|  | for _, dirname := range dirnames { | 
|  | walkDir(filepath.Join(dirname, "pkg", platform), "", out) | 
|  | } | 
|  | } | 
|  | close(out) | 
|  | } | 
|  |  | 
|  | func walkDir(dirname, prefix string, out chan string) { | 
|  | fiList, err := ioutil.ReadDir(dirname) | 
|  | if err != nil { | 
|  | return | 
|  | } | 
|  | for _, fi := range fiList { | 
|  | if fi.IsDir() && !strings.HasPrefix(fi.Name(), ".") { | 
|  | prefix := filepath.Join(prefix, fi.Name()) | 
|  | out <- prefix | 
|  | walkDir(filepath.Join(dirname, fi.Name()), prefix, out) | 
|  | } | 
|  | } | 
|  | } |