Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions src/cmd/compile/internal/testimporter/importer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// 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/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) {
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 {
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 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
}
28 changes: 28 additions & 0 deletions src/cmd/compile/internal/testimporter/support.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.

// TODO(mark): Remove fork in cmd/compile/internal/importer.

package testimporter

import (
"cmd/compile/internal/base"
"internal/pkgbits"
)

func assert(p bool) {
base.Assert(p)
}

// See cmd/compile/internal/noder.derivedInfo.
type derivedInfo struct {
idx pkgbits.Index
needed bool
}

// See cmd/compile/internal/noder.typeInfo.
type typeInfo struct {
idx pkgbits.Index
derived bool
}
Loading
Loading