blob: 339480c90fedbf7d77d30c9b98f5af0e1049f7a3 [file] [edit]
// Copyright 2026 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 testimporter
import (
"bufio"
"fmt"
"go/build"
"internal/exportdata"
"internal/pkgbits"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"cmd/compile/internal/base"
"cmd/compile/internal/importer"
"cmd/compile/internal/types2"
)
// Importer implements a types2 importer for use in testing by calling "go
// build". It is safe for concurrent use; sharing importers can yield better
// performance. It understands the compiler-internal unified export formats.
type Importer struct {
dir string // work directory
mu sync.Mutex // guards the fields below
readPkgs map[string]*types2.Package // package path -> package
bldOnces map[string]*sync.Once // package path -> build function
bldCache map[string]*bldResult // package path -> build result
}
type bldResult struct {
out string // path to built archive
err error // nil if compilation succeeded
}
// NewImporter returns a new Importer.
func NewImporter() *Importer {
dir, err := os.MkdirTemp("", "")
if err != nil {
panic("could not create temp directory")
}
return &Importer{
dir: dir,
mu: sync.Mutex{},
readPkgs: make(map[string]*types2.Package),
bldOnces: make(map[string]*sync.Once),
bldCache: make(map[string]*bldResult),
}
}
// Import implements types2.Importer.
func (imp *Importer) Import(path string) (*types2.Package, error) {
return imp.ImportFrom(path, "", 0)
}
// ImportFrom implements types2.ImportFrom.
func (imp *Importer) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) {
base.Assert(mode == 0)
if path == "unsafe" {
return types2.Unsafe, nil
}
bld, err := build.Import(path, srcDir, build.FindOnly)
if err != nil {
return nil, err
}
// srcDir is only relevant if the package is not in GOROOT.
if !bld.Goroot {
base.Assert(filepath.IsAbs(srcDir)) // see #14282
}
path = bld.ImportPath
// If the package was already read (fully), avoid reading it again.
// Note pkg.Complete must be observed with the lock since packages are modified concurrently.
imp.mu.Lock()
if pkg, ok := imp.readPkgs[path]; ok && pkg.Complete() {
imp.mu.Unlock()
return pkg, nil
}
imp.mu.Unlock()
return imp.readArchive(path, bld.Dir)
}
func (imp *Importer) readArchive(path, dir string) (*types2.Package, error) {
out, err := imp.compile(path, dir)
if err != nil {
return nil, err
}
// Open and decode the output.
f, err := os.Open(out)
if err != nil {
return nil, err
}
defer f.Close()
buf := bufio.NewReader(f)
data, err := exportdata.ReadUnified(buf)
if err != nil {
return nil, err
}
// Guard writes to imp.readPkgs in ReadPackages.
imp.mu.Lock()
defer imp.mu.Unlock()
// While ReadPackage might populate imp.readPkgs with an incomplete package,
// we check for completeness before returning from ImportFrom.
return importer.ReadPackage(nil, imp.readPkgs, pkgbits.NewPkgDecoder(path, string(data))), nil
}
func (imp *Importer) compile(path, dir string) (string, error) {
imp.mu.Lock()
once, ok := imp.bldOnces[path]
if !ok {
once = &sync.Once{}
imp.bldOnces[path] = once
}
imp.mu.Unlock()
once.Do(func() {
// We're first, do the build.
out := filepath.Join(imp.dir, strings.ReplaceAll(path, "/", "_")+".a")
cmd := exec.Command(filepath.Join(build.Default.GOROOT, "bin", "go"), "build", "-o", out, dir)
var res *bldResult
if bytes, err := cmd.CombinedOutput(); err != nil {
res = &bldResult{err: fmt.Errorf("building %s failed: %s", path, bytes)}
} else {
res = &bldResult{out: out}
}
imp.mu.Lock()
imp.bldCache[path] = res
imp.mu.Unlock()
})
imp.mu.Lock()
res := imp.bldCache[path]
imp.mu.Unlock()
return res.out, res.err
}