1// Copyright 2011 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 gcimporter implements Import for gc-generated object files.
6package gcimporter // import "go/internal/gcimporter"
7
8import (
9	"bufio"
10	"fmt"
11	"go/build"
12	"go/token"
13	"go/types"
14	"io"
15	"io/ioutil"
16	"os"
17	"path/filepath"
18	"strings"
19)
20
21// debugging/development support
22const debug = false
23
24var pkgExts = [...]string{".a", ".o"}
25
26// FindPkg returns the filename and unique package id for an import
27// path based on package information provided by build.Import (using
28// the build.Default build.Context). A relative srcDir is interpreted
29// relative to the current working directory.
30// If no file was found, an empty filename is returned.
31//
32func FindPkg(path, srcDir string) (filename, id string) {
33	if path == "" {
34		return
35	}
36
37	var noext string
38	switch {
39	default:
40		// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
41		// Don't require the source files to be present.
42		if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282
43			srcDir = abs
44		}
45		bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary)
46		if bp.PkgObj == "" {
47			id = path // make sure we have an id to print in error message
48			return
49		}
50		noext = strings.TrimSuffix(bp.PkgObj, ".a")
51		id = bp.ImportPath
52
53	case build.IsLocalImport(path):
54		// "./x" -> "/this/directory/x.ext", "/this/directory/x"
55		noext = filepath.Join(srcDir, path)
56		id = noext
57
58	case filepath.IsAbs(path):
59		// for completeness only - go/build.Import
60		// does not support absolute imports
61		// "/x" -> "/x.ext", "/x"
62		noext = path
63		id = path
64	}
65
66	if false { // for debugging
67		if path != id {
68			fmt.Printf("%s -> %s\n", path, id)
69		}
70	}
71
72	// try extensions
73	for _, ext := range pkgExts {
74		filename = noext + ext
75		if f, err := os.Stat(filename); err == nil && !f.IsDir() {
76			return
77		}
78	}
79
80	filename = "" // not found
81	return
82}
83
84// Import imports a gc-generated package given its import path and srcDir, adds
85// the corresponding package object to the packages map, and returns the object.
86// The packages map must contain all packages already imported.
87//
88func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
89	var rc io.ReadCloser
90	var id string
91	if lookup != nil {
92		// With custom lookup specified, assume that caller has
93		// converted path to a canonical import path for use in the map.
94		if path == "unsafe" {
95			return types.Unsafe, nil
96		}
97		id = path
98
99		// No need to re-import if the package was imported completely before.
100		if pkg = packages[id]; pkg != nil && pkg.Complete() {
101			return
102		}
103		f, err := lookup(path)
104		if err != nil {
105			return nil, err
106		}
107		rc = f
108	} else {
109		var filename string
110		filename, id = FindPkg(path, srcDir)
111		if filename == "" {
112			if path == "unsafe" {
113				return types.Unsafe, nil
114			}
115			return nil, fmt.Errorf("can't find import: %q", id)
116		}
117
118		// no need to re-import if the package was imported completely before
119		if pkg = packages[id]; pkg != nil && pkg.Complete() {
120			return
121		}
122
123		// open file
124		f, err := os.Open(filename)
125		if err != nil {
126			return nil, err
127		}
128		defer func() {
129			if err != nil {
130				// add file name to error
131				err = fmt.Errorf("%s: %v", filename, err)
132			}
133		}()
134		rc = f
135	}
136	defer rc.Close()
137
138	var hdr string
139	buf := bufio.NewReader(rc)
140	if hdr, err = FindExportData(buf); err != nil {
141		return
142	}
143
144	switch hdr {
145	case "$$\n":
146		err = fmt.Errorf("import %q: old export format no longer supported (recompile library)", path)
147
148	case "$$B\n":
149		var data []byte
150		data, err = ioutil.ReadAll(buf)
151		if err != nil {
152			break
153		}
154
155		// The indexed export format starts with an 'i'; the older
156		// binary export format starts with a 'c', 'd', or 'v'
157		// (from "version"). Select appropriate importer.
158		if len(data) > 0 && data[0] == 'i' {
159			_, pkg, err = iImportData(fset, packages, data[1:], id)
160		} else {
161			_, pkg, err = BImportData(fset, packages, data, id)
162		}
163
164	default:
165		err = fmt.Errorf("unknown export data header: %q", hdr)
166	}
167
168	return
169}
170
171func deref(typ types.Type) types.Type {
172	if p, _ := typ.(*types.Pointer); p != nil {
173		return p.Elem()
174	}
175	return typ
176}
177
178type byPath []*types.Package
179
180func (a byPath) Len() int           { return len(a) }
181func (a byPath) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
182func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() }
183