1// Copyright 2014 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package main
6
7import (
8	"errors"
9	"flag"
10	"fmt"
11	"go/build"
12	"go/types"
13	"io/ioutil"
14	"os"
15	"path/filepath"
16	"strings"
17)
18
19var (
20	source  = flag.String("s", "", "only consider packages from src, where src is one of the supported compilers")
21	verbose = flag.Bool("v", false, "verbose mode")
22)
23
24// lists of registered sources and corresponding importers
25var (
26	sources         []string
27	importers       []types.Importer
28	errImportFailed = errors.New("import failed")
29)
30
31func usage() {
32	fmt.Fprintln(os.Stderr, "usage: godex [flags] {path|qualifiedIdent}")
33	flag.PrintDefaults()
34	os.Exit(2)
35}
36
37func report(msg string) {
38	fmt.Fprintln(os.Stderr, "error: "+msg)
39	os.Exit(2)
40}
41
42func main() {
43	flag.Usage = usage
44	flag.Parse()
45
46	if flag.NArg() == 0 {
47		report("no package name, path, or file provided")
48	}
49
50	var imp types.Importer = new(tryImporters)
51	if *source != "" {
52		imp = lookup(*source)
53		if imp == nil {
54			report("source (-s argument) must be one of: " + strings.Join(sources, ", "))
55		}
56	}
57
58	for _, arg := range flag.Args() {
59		path, name := splitPathIdent(arg)
60		logf("\tprocessing %q: path = %q, name = %s\n", arg, path, name)
61
62		// generate possible package path prefixes
63		// (at the moment we do this for each argument - should probably cache the generated prefixes)
64		prefixes := make(chan string)
65		go genPrefixes(prefixes, !filepath.IsAbs(path) && !build.IsLocalImport(path))
66
67		// import package
68		pkg, err := tryPrefixes(prefixes, path, imp)
69		if err != nil {
70			logf("\t=> ignoring %q: %s\n", path, err)
71			continue
72		}
73
74		// filter objects if needed
75		var filter func(types.Object) bool
76		if name != "" {
77			filter = func(obj types.Object) bool {
78				// TODO(gri) perhaps use regular expression matching here?
79				return obj.Name() == name
80			}
81		}
82
83		// print contents
84		print(os.Stdout, pkg, filter)
85	}
86}
87
88func logf(format string, args ...interface{}) {
89	if *verbose {
90		fmt.Fprintf(os.Stderr, format, args...)
91	}
92}
93
94// splitPathIdent splits a path.name argument into its components.
95// All but the last path element may contain dots.
96func splitPathIdent(arg string) (path, name string) {
97	if i := strings.LastIndex(arg, "."); i >= 0 {
98		if j := strings.LastIndex(arg, "/"); j < i {
99			// '.' is not part of path
100			path = arg[:i]
101			name = arg[i+1:]
102			return
103		}
104	}
105	path = arg
106	return
107}
108
109// tryPrefixes tries to import the package given by (the possibly partial) path using the given importer imp
110// by prepending all possible prefixes to path. It returns with the first package that it could import, or
111// with an error.
112func tryPrefixes(prefixes chan string, path string, imp types.Importer) (pkg *types.Package, err error) {
113	for prefix := range prefixes {
114		actual := path
115		if prefix == "" {
116			// don't use filepath.Join as it will sanitize the path and remove
117			// a leading dot and then the path is not recognized as a relative
118			// package path by the importers anymore
119			logf("\ttrying no prefix\n")
120		} else {
121			actual = filepath.Join(prefix, path)
122			logf("\ttrying prefix %q\n", prefix)
123		}
124		pkg, err = imp.Import(actual)
125		if err == nil {
126			break
127		}
128		logf("\t=> importing %q failed: %s\n", actual, err)
129	}
130	return
131}
132
133// tryImporters is an importer that tries all registered importers
134// successively until one of them succeeds or all of them failed.
135type tryImporters struct{}
136
137func (t *tryImporters) Import(path string) (pkg *types.Package, err error) {
138	for i, imp := range importers {
139		logf("\t\ttrying %s import\n", sources[i])
140		pkg, err = imp.Import(path)
141		if err == nil {
142			break
143		}
144		logf("\t\t=> %s import failed: %s\n", sources[i], err)
145	}
146	return
147}
148
149type protector struct {
150	imp types.Importer
151}
152
153func (p *protector) Import(path string) (pkg *types.Package, err error) {
154	defer func() {
155		if recover() != nil {
156			pkg = nil
157			err = errImportFailed
158		}
159	}()
160	return p.imp.Import(path)
161}
162
163// protect protects an importer imp from panics and returns the protected importer.
164func protect(imp types.Importer) types.Importer {
165	return &protector{imp}
166}
167
168// register registers an importer imp for a given source src.
169func register(src string, imp types.Importer) {
170	if lookup(src) != nil {
171		panic(src + " importer already registered")
172	}
173	sources = append(sources, src)
174	importers = append(importers, protect(imp))
175}
176
177// lookup returns the importer imp for a given source src.
178func lookup(src string) types.Importer {
179	for i, s := range sources {
180		if s == src {
181			return importers[i]
182		}
183	}
184	return nil
185}
186
187func genPrefixes(out chan string, all bool) {
188	out <- ""
189	if all {
190		platform := build.Default.GOOS + "_" + build.Default.GOARCH
191		dirnames := append([]string{build.Default.GOROOT}, filepath.SplitList(build.Default.GOPATH)...)
192		for _, dirname := range dirnames {
193			walkDir(filepath.Join(dirname, "pkg", platform), "", out)
194		}
195	}
196	close(out)
197}
198
199func walkDir(dirname, prefix string, out chan string) {
200	fiList, err := ioutil.ReadDir(dirname)
201	if err != nil {
202		return
203	}
204	for _, fi := range fiList {
205		if fi.IsDir() && !strings.HasPrefix(fi.Name(), ".") {
206			prefix := filepath.Join(prefix, fi.Name())
207			out <- prefix
208			walkDir(filepath.Join(dirname, fi.Name()), prefix, out)
209		}
210	}
211}
212