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

View as plain text