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 | |
5 | package main |
6 | |
7 | import ( |
8 | "errors" |
9 | "flag" |
10 | "fmt" |
11 | "go/build" |
12 | "go/types" |
13 | "io/ioutil" |
14 | "os" |
15 | "path/filepath" |
16 | "strings" |
17 | ) |
18 | |
19 | var ( |
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 |
25 | var ( |
26 | sources []string |
27 | importers []types.Importer |
28 | errImportFailed = errors.New("import failed") |
29 | ) |
30 | |
31 | func usage() { |
32 | fmt.Fprintln(os.Stderr, "usage: godex [flags] {path|qualifiedIdent}") |
33 | flag.PrintDefaults() |
34 | os.Exit(2) |
35 | } |
36 | |
37 | func report(msg string) { |
38 | fmt.Fprintln(os.Stderr, "error: "+msg) |
39 | os.Exit(2) |
40 | } |
41 | |
42 | func 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 | |
88 | func 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. |
96 | func 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. |
112 | func 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. |
135 | type tryImporters struct{} |
136 | |
137 | func (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 | |
149 | type protector struct { |
150 | imp types.Importer |
151 | } |
152 | |
153 | func (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. |
164 | func protect(imp types.Importer) types.Importer { |
165 | return &protector{imp} |
166 | } |
167 | |
168 | // register registers an importer imp for a given source src. |
169 | func 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. |
178 | func 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 | |
187 | func 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 | |
199 | func 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 |
Members