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  }