--- /dev/null
+// 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)
+ }
+ }
+}