| // 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 |
| } |