120 lines
2.7 KiB
Go
120 lines
2.7 KiB
Go
package testtool
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sync"
|
|
)
|
|
|
|
const BinariesEnv = "TESTTOOL_BINARIES"
|
|
|
|
type BinCache interface {
|
|
// GetBinary returns filesystem path to the compiled binary corresponding to given import path.
|
|
GetBinary(importPath string) (string, error)
|
|
}
|
|
|
|
type CloseFunc func()
|
|
|
|
func NewBinCache() (BinCache, CloseFunc) {
|
|
if _, ok := os.LookupEnv(BinariesEnv); ok {
|
|
return newCIBuildCache(), func() {}
|
|
}
|
|
|
|
dir, err := os.MkdirTemp("", "bincache-")
|
|
if err != nil {
|
|
log.Fatalf("unable to create temp dir: %s", err)
|
|
}
|
|
|
|
return newLocalBinCache(dir), func() { _ = os.RemoveAll(dir) }
|
|
}
|
|
|
|
// localBinCache is a BinCache implementation that compiles queried binaries lazily.
|
|
type localBinCache struct {
|
|
// dir is a directory that stores compiled binaries.
|
|
dir string
|
|
|
|
binaries sync.Map
|
|
}
|
|
|
|
// newLocalBinCache creates localBinCache that uses given directory to store binaries.
|
|
func newLocalBinCache(dir string) *localBinCache {
|
|
return &localBinCache{dir: dir}
|
|
}
|
|
|
|
func (c *localBinCache) GetBinary(importPath string) (string, error) {
|
|
v, ok := c.binaries.Load(importPath)
|
|
if ok {
|
|
return v.(string), nil
|
|
}
|
|
|
|
binPath := filepath.Join(c.dir, RandomBinaryName())
|
|
if buildTags == "" {
|
|
runGo("build", "-mod", "readonly", "-o", binPath, importPath)
|
|
} else {
|
|
runGo("build", "-mod", "readonly", "-tags", buildTags, "-o", binPath, importPath)
|
|
}
|
|
|
|
c.binaries.Store(importPath, binPath)
|
|
|
|
return binPath, nil
|
|
}
|
|
|
|
func runGo(arg ...string) {
|
|
cmd := exec.Command("go", arg...)
|
|
cmd.Env = append(os.Environ(), "GOFLAGS=")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// ciBuildCache is a BinCache implementation that uses precompiled binaries.
|
|
type ciBuildCache struct {
|
|
binaries map[string]string
|
|
}
|
|
|
|
// newCIBuildCache creates ciBuildCache that loads locations of precompiled binaries from env variable.
|
|
func newCIBuildCache() *ciBuildCache {
|
|
binariesJSON, ok := os.LookupEnv(BinariesEnv)
|
|
if !ok {
|
|
log.Fatalf("%s env variable not set", BinariesEnv)
|
|
}
|
|
|
|
binaries := make(map[string]string)
|
|
if err := json.Unmarshal([]byte(binariesJSON), &binaries); err != nil {
|
|
log.Fatalf("unexpected %s format: %s", binaries, err)
|
|
}
|
|
|
|
return &ciBuildCache{binaries: binaries}
|
|
}
|
|
|
|
func (c *ciBuildCache) GetBinary(importPath string) (string, error) {
|
|
binary, ok := c.binaries[importPath]
|
|
if !ok {
|
|
return "", fmt.Errorf("%s not found", importPath)
|
|
}
|
|
return binary, nil
|
|
}
|
|
|
|
func RandomBinaryName() string {
|
|
name := RandomName()
|
|
if runtime.GOOS == "windows" {
|
|
name += ".exe"
|
|
}
|
|
return name
|
|
}
|
|
|
|
func RandomName() string {
|
|
var raw [8]byte
|
|
_, _ = rand.Read(raw[:])
|
|
name := hex.EncodeToString(raw[:])
|
|
return name
|
|
}
|