• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 importer implements Import for gc-generated object files.
6package importer
7
8import (
9	"bufio"
10	"bytes"
11	"errors"
12	"fmt"
13	"go/build"
14	"internal/pkgbits"
15	"io"
16	"os"
17	"os/exec"
18	"path/filepath"
19	"strings"
20	"sync"
21
22	"cmd/compile/internal/types2"
23)
24
25var exportMap sync.Map // package dir → func() (string, error)
26
27// lookupGorootExport returns the location of the export data
28// (normally found in the build cache, but located in GOROOT/pkg
29// in prior Go releases) for the package located in pkgDir.
30//
31// (We use the package's directory instead of its import path
32// mainly to simplify handling of the packages in src/vendor
33// and cmd/vendor.)
34func lookupGorootExport(pkgDir string) (string, error) {
35	f, ok := exportMap.Load(pkgDir)
36	if !ok {
37		var (
38			listOnce   sync.Once
39			exportPath string
40			err        error
41		)
42		f, _ = exportMap.LoadOrStore(pkgDir, func() (string, error) {
43			listOnce.Do(func() {
44				cmd := exec.Command(filepath.Join(build.Default.GOROOT, "bin", "go"), "list", "-export", "-f", "{{.Export}}", pkgDir)
45				cmd.Dir = build.Default.GOROOT
46				cmd.Env = append(os.Environ(), "PWD="+cmd.Dir, "GOROOT="+build.Default.GOROOT)
47				var output []byte
48				output, err = cmd.Output()
49				if err != nil {
50					if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
51						err = errors.New(string(ee.Stderr))
52					}
53					return
54				}
55
56				exports := strings.Split(string(bytes.TrimSpace(output)), "\n")
57				if len(exports) != 1 {
58					err = fmt.Errorf("go list reported %d exports; expected 1", len(exports))
59					return
60				}
61
62				exportPath = exports[0]
63			})
64
65			return exportPath, err
66		})
67	}
68
69	return f.(func() (string, error))()
70}
71
72var pkgExts = [...]string{".a", ".o"} // a file from the build cache will have no extension
73
74// FindPkg returns the filename and unique package id for an import
75// path based on package information provided by build.Import (using
76// the build.Default build.Context). A relative srcDir is interpreted
77// relative to the current working directory.
78func FindPkg(path, srcDir string) (filename, id string, err error) {
79	if path == "" {
80		return "", "", errors.New("path is empty")
81	}
82
83	var noext string
84	switch {
85	default:
86		// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
87		// Don't require the source files to be present.
88		if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282
89			srcDir = abs
90		}
91		var bp *build.Package
92		bp, err = build.Import(path, srcDir, build.FindOnly|build.AllowBinary)
93		if bp.PkgObj == "" {
94			if bp.Goroot && bp.Dir != "" {
95				filename, err = lookupGorootExport(bp.Dir)
96				if err == nil {
97					_, err = os.Stat(filename)
98				}
99				if err == nil {
100					return filename, bp.ImportPath, nil
101				}
102			}
103			goto notfound
104		} else {
105			noext = strings.TrimSuffix(bp.PkgObj, ".a")
106		}
107		id = bp.ImportPath
108
109	case build.IsLocalImport(path):
110		// "./x" -> "/this/directory/x.ext", "/this/directory/x"
111		noext = filepath.Join(srcDir, path)
112		id = noext
113
114	case filepath.IsAbs(path):
115		// for completeness only - go/build.Import
116		// does not support absolute imports
117		// "/x" -> "/x.ext", "/x"
118		noext = path
119		id = path
120	}
121
122	if false { // for debugging
123		if path != id {
124			fmt.Printf("%s -> %s\n", path, id)
125		}
126	}
127
128	// try extensions
129	for _, ext := range pkgExts {
130		filename = noext + ext
131		f, statErr := os.Stat(filename)
132		if statErr == nil && !f.IsDir() {
133			return filename, id, nil
134		}
135		if err == nil {
136			err = statErr
137		}
138	}
139
140notfound:
141	if err == nil {
142		return "", path, fmt.Errorf("can't find import: %q", path)
143	}
144	return "", path, fmt.Errorf("can't find import: %q: %w", path, err)
145}
146
147// Import imports a gc-generated package given its import path and srcDir, adds
148// the corresponding package object to the packages map, and returns the object.
149// The packages map must contain all packages already imported.
150func Import(packages map[string]*types2.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types2.Package, err error) {
151	var rc io.ReadCloser
152	var id string
153	if lookup != nil {
154		// With custom lookup specified, assume that caller has
155		// converted path to a canonical import path for use in the map.
156		if path == "unsafe" {
157			return types2.Unsafe, nil
158		}
159		id = path
160
161		// No need to re-import if the package was imported completely before.
162		if pkg = packages[id]; pkg != nil && pkg.Complete() {
163			return
164		}
165		f, err := lookup(path)
166		if err != nil {
167			return nil, err
168		}
169		rc = f
170	} else {
171		var filename string
172		filename, id, err = FindPkg(path, srcDir)
173		if filename == "" {
174			if path == "unsafe" {
175				return types2.Unsafe, nil
176			}
177			return nil, err
178		}
179
180		// no need to re-import if the package was imported completely before
181		if pkg = packages[id]; pkg != nil && pkg.Complete() {
182			return
183		}
184
185		// open file
186		f, err := os.Open(filename)
187		if err != nil {
188			return nil, err
189		}
190		defer func() {
191			if err != nil {
192				// add file name to error
193				err = fmt.Errorf("%s: %v", filename, err)
194			}
195		}()
196		rc = f
197	}
198	defer rc.Close()
199
200	buf := bufio.NewReader(rc)
201	hdr, size, err := FindExportData(buf)
202	if err != nil {
203		return
204	}
205
206	switch hdr {
207	case "$$\n":
208		err = fmt.Errorf("import %q: old textual export format no longer supported (recompile library)", path)
209
210	case "$$B\n":
211		var data []byte
212		var r io.Reader = buf
213		if size >= 0 {
214			r = io.LimitReader(r, int64(size))
215		}
216		data, err = io.ReadAll(r)
217		if err != nil {
218			break
219		}
220
221		if len(data) == 0 {
222			err = fmt.Errorf("import %q: missing export data", path)
223			break
224		}
225		exportFormat := data[0]
226		s := string(data[1:])
227
228		// The indexed export format starts with an 'i'; the older
229		// binary export format starts with a 'c', 'd', or 'v'
230		// (from "version"). Select appropriate importer.
231		switch exportFormat {
232		case 'u':
233			s = s[:strings.Index(s, "\n$$\n")]
234			input := pkgbits.NewPkgDecoder(id, s)
235			pkg = ReadPackage(nil, packages, input)
236		case 'i':
237			pkg, err = ImportData(packages, s, id)
238		default:
239			err = fmt.Errorf("import %q: old binary export format no longer supported (recompile library)", path)
240		}
241
242	default:
243		err = fmt.Errorf("import %q: unknown export data header: %q", path, hdr)
244	}
245
246	return
247}
248
249type byPath []*types2.Package
250
251func (a byPath) Len() int           { return len(a) }
252func (a byPath) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
253func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() }
254