package utils
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
"golang.org/x/tools/cover"
)
// Pkg describes a single package, compatible with the JSON output from 'go list'; see 'go help list'.
type Pkg struct {
ImportPath string
Dir string
Error *struct {
Err string
}
}
func FindPkgs(profiles []*cover.Profile) (map[string]*Pkg, error) {
// Run go list to find the location of every package we care about.
pkgs := make(map[string]*Pkg)
var list []string
for _, profile := range profiles {
if strings.HasPrefix(profile.FileName, ".") || filepath.IsAbs(profile.FileName) {
// Relative or absolute path.
continue
}
pkg := path.Dir(profile.FileName)
if _, ok := pkgs[pkg]; !ok {
pkgs[pkg] = nil
list = append(list, pkg)
}
}
if len(list) == 0 {
return pkgs, nil
}
// Note: usually run as "go tool cover" in which case $GOROOT is set,
// in which case runtime.GOROOT() does exactly what we want.
goTool := filepath.Join(runtime.GOROOT(), "bin/go")
cmd := exec.Command(goTool, append([]string{"list", "-e", "-json"}, list...)...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
stdout, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("cannot run go list: %w\n%s", err, stderr.Bytes())
}
dec := json.NewDecoder(bytes.NewReader(stdout))
for {
var pkg Pkg
decodeErr := dec.Decode(&pkg)
if decodeErr == io.EOF {
break
}
if decodeErr != nil {
return nil, fmt.Errorf("decoding go list json: %w", decodeErr)
}
pkgs[pkg.ImportPath] = &pkg
}
return pkgs, nil
}