Source file src/cmd/compile/internal/testimporter/importer.go

     1  // Copyright 2026 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 testimporter
     6  
     7  import (
     8  	"bufio"
     9  	"fmt"
    10  	"go/build"
    11  	"internal/exportdata"
    12  	"internal/pkgbits"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"strings"
    17  	"sync"
    18  
    19  	"cmd/compile/internal/base"
    20  	"cmd/compile/internal/importer"
    21  	"cmd/compile/internal/types2"
    22  )
    23  
    24  // Importer implements a types2 importer for use in testing by calling "go
    25  // build". It is safe for concurrent use; sharing importers can yield better
    26  // performance. It understands the compiler-internal unified export formats.
    27  type Importer struct {
    28  	dir      string                     // work directory
    29  	mu       sync.Mutex                 // guards the fields below
    30  	readPkgs map[string]*types2.Package // package path -> package
    31  	bldOnces map[string]*sync.Once      // package path -> build function
    32  	bldCache map[string]*bldResult      // package path -> build result
    33  }
    34  
    35  type bldResult struct {
    36  	out string // path to built archive
    37  	err error  // nil if compilation succeeded
    38  }
    39  
    40  // NewImporter returns a new Importer.
    41  func NewImporter() *Importer {
    42  	dir, err := os.MkdirTemp("", "")
    43  	if err != nil {
    44  		panic("could not create temp directory")
    45  	}
    46  	return &Importer{
    47  		dir:      dir,
    48  		mu:       sync.Mutex{},
    49  		readPkgs: make(map[string]*types2.Package),
    50  		bldOnces: make(map[string]*sync.Once),
    51  		bldCache: make(map[string]*bldResult),
    52  	}
    53  }
    54  
    55  // Import implements types2.Importer.
    56  func (imp *Importer) Import(path string) (*types2.Package, error) {
    57  	return imp.ImportFrom(path, "", 0)
    58  }
    59  
    60  // ImportFrom implements types2.ImportFrom.
    61  func (imp *Importer) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) {
    62  	base.Assert(mode == 0)
    63  	if path == "unsafe" {
    64  		return types2.Unsafe, nil
    65  	}
    66  	bld, err := build.Import(path, srcDir, build.FindOnly)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	// srcDir is only relevant if the package is not in GOROOT.
    71  	if !bld.Goroot {
    72  		base.Assert(filepath.IsAbs(srcDir)) // see #14282
    73  	}
    74  	path = bld.ImportPath
    75  	// If the package was already read (fully), avoid reading it again.
    76  	// Note pkg.Complete must be observed with the lock since packages are modified concurrently.
    77  	imp.mu.Lock()
    78  	if pkg, ok := imp.readPkgs[path]; ok && pkg.Complete() {
    79  		imp.mu.Unlock()
    80  		return pkg, nil
    81  	}
    82  	imp.mu.Unlock()
    83  	return imp.readArchive(path, bld.Dir)
    84  }
    85  
    86  func (imp *Importer) readArchive(path, dir string) (*types2.Package, error) {
    87  	out, err := imp.compile(path, dir)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	// Open and decode the output.
    92  	f, err := os.Open(out)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	defer f.Close()
    97  	buf := bufio.NewReader(f)
    98  	data, err := exportdata.ReadUnified(buf)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	// Guard writes to imp.readPkgs in ReadPackages.
   103  	imp.mu.Lock()
   104  	defer imp.mu.Unlock()
   105  	// While ReadPackage might populate imp.readPkgs with an incomplete package,
   106  	// we check for completeness before returning from ImportFrom.
   107  	return importer.ReadPackage(nil, imp.readPkgs, pkgbits.NewPkgDecoder(path, string(data))), nil
   108  }
   109  
   110  func (imp *Importer) compile(path, dir string) (string, error) {
   111  	imp.mu.Lock()
   112  	once, ok := imp.bldOnces[path]
   113  	if !ok {
   114  		once = &sync.Once{}
   115  		imp.bldOnces[path] = once
   116  	}
   117  	imp.mu.Unlock()
   118  	once.Do(func() {
   119  		// We're first, do the build.
   120  		out := filepath.Join(imp.dir, strings.ReplaceAll(path, "/", "_")+".a")
   121  		cmd := exec.Command(filepath.Join(build.Default.GOROOT, "bin", "go"), "build", "-o", out, dir)
   122  		var res *bldResult
   123  		if bytes, err := cmd.CombinedOutput(); err != nil {
   124  			res = &bldResult{err: fmt.Errorf("building %s failed: %s", path, bytes)}
   125  		} else {
   126  			res = &bldResult{out: out}
   127  		}
   128  		imp.mu.Lock()
   129  		imp.bldCache[path] = res
   130  		imp.mu.Unlock()
   131  	})
   132  	imp.mu.Lock()
   133  	res := imp.bldCache[path]
   134  	imp.mu.Unlock()
   135  	return res.out, res.err
   136  }
   137  

View as plain text