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 }