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

View as plain text