github.com/please-build/go-rules/tools/please_go@v0.0.0-20240319165128-ea27d6f5caba/cover/cover.go (about)

     1  // Package cover instruments source files for coverage.
     2  // It orchestrates some of the functionality of `go tool cover`.
     3  package cover
     4  
     5  import (
     6  	"bytes"
     7  	"encoding/json"
     8  	"go/parser"
     9  	"go/token"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	"github.com/please-build/go-rules/tools/please_go/install/toolchain"
    16  )
    17  
    18  // WriteCoverage writes the necessary Go coverage information for a set of sources.
    19  func WriteCoverage(goTool, coverTool, covercfg, output, pkg string, srcs []string) error {
    20  	pkgName, err := packageName(srcs[0])
    21  	if err != nil {
    22  		return err
    23  	}
    24  	const pkgConfigFile = "pkgcfg"
    25  	b, _ := json.Marshal(coverConfig{
    26  		OutConfig:   covercfg,
    27  		PkgPath:     pkg,
    28  		PkgName:     pkgName,
    29  		Granularity: "perblock",
    30  	})
    31  	if err := os.WriteFile(pkgConfigFile, b, 0644); err != nil {
    32  		return err
    33  	}
    34  	var buf bytes.Buffer
    35  	// 1.21 requires a cover vars file to be written into the output file list
    36  	if coverTool != "" || needs121CoverVars(goTool) {
    37  		buf.WriteString(filepath.Join(filepath.Dir(srcs[0]), "_covervars.cover.go\n"))
    38  	}
    39  	for _, src := range srcs {
    40  		buf.WriteString(strings.TrimSuffix(src, ".go") + ".cover.go\n")
    41  	}
    42  	if err := os.WriteFile(output, buf.Bytes(), 0644); err != nil {
    43  		return err
    44  	}
    45  	var cmd *exec.Cmd
    46  	if coverTool != "" {
    47  		cmd = exec.Command(coverTool, append([]string{"-mode=set", "-var=goCover", "-pkgcfg", pkgConfigFile, "-outfilelist", output}, srcs...)...)
    48  	} else {
    49  		cmd = exec.Command(goTool, append([]string{"tool", "cover", "-mode=set", "-var=goCover", "-pkgcfg", pkgConfigFile, "-outfilelist", output}, srcs...)...)
    50  	}
    51  	cmd.Stdout = os.Stdout
    52  	cmd.Stderr = os.Stderr
    53  	return cmd.Run()
    54  }
    55  
    56  func needs121CoverVars(goTool string) bool {
    57  	tc := toolchain.Toolchain{GoTool: goTool}
    58  	version, err := tc.GoMinorVersion()
    59  	return err == nil && version >= 21
    60  }
    61  
    62  // This is a copy of the one from internal/coverage (why does that need to be internal??)
    63  type coverConfig struct {
    64  	OutConfig   string
    65  	PkgPath     string
    66  	PkgName     string
    67  	Granularity string
    68  	ModulePath  string
    69  }
    70  
    71  // packageName returns the Go package for a file.
    72  func packageName(filename string) (string, error) {
    73  	fset := token.NewFileSet()
    74  	f, err := parser.ParseFile(fset, filename, nil, parser.PackageClauseOnly)
    75  	if err != nil {
    76  		return "", err
    77  	}
    78  	return f.Name.Name, nil
    79  }