212 lines
5.2 KiB
Go
212 lines
5.2 KiB
Go
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"flag"
|
||
|
"fmt"
|
||
|
"go/build"
|
||
|
"go/types"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
source = flag.String("s", "", "only consider packages from src, where src is one of the supported compilers")
|
||
|
verbose = flag.Bool("v", false, "verbose mode")
|
||
|
)
|
||
|
|
||
|
// lists of registered sources and corresponding importers
|
||
|
var (
|
||
|
sources []string
|
||
|
importers []types.Importer
|
||
|
importFailed = errors.New("import failed")
|
||
|
)
|
||
|
|
||
|
func usage() {
|
||
|
fmt.Fprintln(os.Stderr, "usage: godex [flags] {path|qualifiedIdent}")
|
||
|
flag.PrintDefaults()
|
||
|
os.Exit(2)
|
||
|
}
|
||
|
|
||
|
func report(msg string) {
|
||
|
fmt.Fprintln(os.Stderr, "error: "+msg)
|
||
|
os.Exit(2)
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
flag.Usage = usage
|
||
|
flag.Parse()
|
||
|
|
||
|
if flag.NArg() == 0 {
|
||
|
report("no package name, path, or file provided")
|
||
|
}
|
||
|
|
||
|
var imp types.Importer = new(tryImporters)
|
||
|
if *source != "" {
|
||
|
imp = lookup(*source)
|
||
|
if imp == nil {
|
||
|
report("source (-s argument) must be one of: " + strings.Join(sources, ", "))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, arg := range flag.Args() {
|
||
|
path, name := splitPathIdent(arg)
|
||
|
logf("\tprocessing %q: path = %q, name = %s\n", arg, path, name)
|
||
|
|
||
|
// generate possible package path prefixes
|
||
|
// (at the moment we do this for each argument - should probably cache the generated prefixes)
|
||
|
prefixes := make(chan string)
|
||
|
go genPrefixes(prefixes, !filepath.IsAbs(path) && !build.IsLocalImport(path))
|
||
|
|
||
|
// import package
|
||
|
pkg, err := tryPrefixes(prefixes, path, imp)
|
||
|
if err != nil {
|
||
|
logf("\t=> ignoring %q: %s\n", path, err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// filter objects if needed
|
||
|
var filter func(types.Object) bool
|
||
|
if name != "" {
|
||
|
filter = func(obj types.Object) bool {
|
||
|
// TODO(gri) perhaps use regular expression matching here?
|
||
|
return obj.Name() == name
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// print contents
|
||
|
print(os.Stdout, pkg, filter)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func logf(format string, args ...interface{}) {
|
||
|
if *verbose {
|
||
|
fmt.Fprintf(os.Stderr, format, args...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// splitPathIdent splits a path.name argument into its components.
|
||
|
// All but the last path element may contain dots.
|
||
|
func splitPathIdent(arg string) (path, name string) {
|
||
|
if i := strings.LastIndex(arg, "."); i >= 0 {
|
||
|
if j := strings.LastIndex(arg, "/"); j < i {
|
||
|
// '.' is not part of path
|
||
|
path = arg[:i]
|
||
|
name = arg[i+1:]
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
path = arg
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// tryPrefixes tries to import the package given by (the possibly partial) path using the given importer imp
|
||
|
// by prepending all possible prefixes to path. It returns with the first package that it could import, or
|
||
|
// with an error.
|
||
|
func tryPrefixes(prefixes chan string, path string, imp types.Importer) (pkg *types.Package, err error) {
|
||
|
for prefix := range prefixes {
|
||
|
actual := path
|
||
|
if prefix == "" {
|
||
|
// don't use filepath.Join as it will sanitize the path and remove
|
||
|
// a leading dot and then the path is not recognized as a relative
|
||
|
// package path by the importers anymore
|
||
|
logf("\ttrying no prefix\n")
|
||
|
} else {
|
||
|
actual = filepath.Join(prefix, path)
|
||
|
logf("\ttrying prefix %q\n", prefix)
|
||
|
}
|
||
|
pkg, err = imp.Import(actual)
|
||
|
if err == nil {
|
||
|
break
|
||
|
}
|
||
|
logf("\t=> importing %q failed: %s\n", actual, err)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// tryImporters is an importer that tries all registered importers
|
||
|
// successively until one of them succeeds or all of them failed.
|
||
|
type tryImporters struct{}
|
||
|
|
||
|
func (t *tryImporters) Import(path string) (pkg *types.Package, err error) {
|
||
|
for i, imp := range importers {
|
||
|
logf("\t\ttrying %s import\n", sources[i])
|
||
|
pkg, err = imp.Import(path)
|
||
|
if err == nil {
|
||
|
break
|
||
|
}
|
||
|
logf("\t\t=> %s import failed: %s\n", sources[i], err)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
type protector struct {
|
||
|
imp types.Importer
|
||
|
}
|
||
|
|
||
|
func (p *protector) Import(path string) (pkg *types.Package, err error) {
|
||
|
defer func() {
|
||
|
if recover() != nil {
|
||
|
pkg = nil
|
||
|
err = importFailed
|
||
|
}
|
||
|
}()
|
||
|
return p.imp.Import(path)
|
||
|
}
|
||
|
|
||
|
// protect protects an importer imp from panics and returns the protected importer.
|
||
|
func protect(imp types.Importer) types.Importer {
|
||
|
return &protector{imp}
|
||
|
}
|
||
|
|
||
|
// register registers an importer imp for a given source src.
|
||
|
func register(src string, imp types.Importer) {
|
||
|
if lookup(src) != nil {
|
||
|
panic(src + " importer already registered")
|
||
|
}
|
||
|
sources = append(sources, src)
|
||
|
importers = append(importers, protect(imp))
|
||
|
}
|
||
|
|
||
|
// lookup returns the importer imp for a given source src.
|
||
|
func lookup(src string) types.Importer {
|
||
|
for i, s := range sources {
|
||
|
if s == src {
|
||
|
return importers[i]
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func genPrefixes(out chan string, all bool) {
|
||
|
out <- ""
|
||
|
if all {
|
||
|
platform := build.Default.GOOS + "_" + build.Default.GOARCH
|
||
|
dirnames := append([]string{build.Default.GOROOT}, filepath.SplitList(build.Default.GOPATH)...)
|
||
|
for _, dirname := range dirnames {
|
||
|
walkDir(filepath.Join(dirname, "pkg", platform), "", out)
|
||
|
}
|
||
|
}
|
||
|
close(out)
|
||
|
}
|
||
|
|
||
|
func walkDir(dirname, prefix string, out chan string) {
|
||
|
fiList, err := ioutil.ReadDir(dirname)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
for _, fi := range fiList {
|
||
|
if fi.IsDir() && !strings.HasPrefix(fi.Name(), ".") {
|
||
|
prefix := filepath.Join(prefix, fi.Name())
|
||
|
out <- prefix
|
||
|
walkDir(filepath.Join(dirname, fi.Name()), prefix, out)
|
||
|
}
|
||
|
}
|
||
|
}
|