You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
5.2 KiB

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