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