2021-09-02 09:48:11 +00:00
|
|
|
|
/*
|
|
|
|
|
Copyright 2021 Qiniu Cloud (qiniu.com)
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
2021-09-02 06:36:41 +00:00
|
|
|
|
package build
|
2021-06-09 07:40:02 +00:00
|
|
|
|
|
|
|
|
|
import (
|
2021-08-10 14:50:30 +00:00
|
|
|
|
"fmt"
|
2021-06-14 07:18:29 +00:00
|
|
|
|
"os"
|
2023-09-07 11:05:02 +00:00
|
|
|
|
"os/exec"
|
2021-06-17 11:53:00 +00:00
|
|
|
|
"path"
|
2021-06-14 07:18:29 +00:00
|
|
|
|
"path/filepath"
|
2023-09-08 03:31:19 +00:00
|
|
|
|
"strings"
|
2021-06-14 07:18:29 +00:00
|
|
|
|
|
2023-08-28 04:34:44 +00:00
|
|
|
|
"github.com/RickLeee/goc-v2/pkg/build/internal/tool"
|
|
|
|
|
"github.com/RickLeee/goc-v2/pkg/build/internal/websocket"
|
|
|
|
|
"github.com/RickLeee/goc-v2/pkg/log"
|
2021-06-09 07:40:02 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Inject injects cover variables for all the .go files in the target directory
|
2021-09-02 06:36:41 +00:00
|
|
|
|
func (b *Build) Inject() {
|
2021-06-09 07:40:02 +00:00
|
|
|
|
log.StartWait("injecting cover variables")
|
|
|
|
|
|
2021-09-02 06:36:41 +00:00
|
|
|
|
var seen = make(map[string]*PackageCover)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
|
|
|
|
|
// 所有插桩变量定义声明
|
|
|
|
|
allDecl := ""
|
2021-06-09 07:40:02 +00:00
|
|
|
|
|
2021-09-02 06:36:41 +00:00
|
|
|
|
pkgs := b.Pkgs
|
2021-06-14 07:18:29 +00:00
|
|
|
|
for _, pkg := range pkgs {
|
2021-06-09 07:40:02 +00:00
|
|
|
|
if pkg.Name == "main" {
|
2021-06-14 07:18:29 +00:00
|
|
|
|
// 该 main 二进制所关联的所有插桩变量的元信息
|
|
|
|
|
// 每个 main 之间是不相关的,需要重新定义
|
2021-09-02 06:36:41 +00:00
|
|
|
|
allMainCovers := make([]*PackageCover, 0)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
// 注入 main package
|
2021-09-02 06:36:41 +00:00
|
|
|
|
mainCover, mainDecl := b.addCounters(pkg)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
// 收集插桩变量的定义和元信息
|
|
|
|
|
allDecl += mainDecl
|
|
|
|
|
allMainCovers = append(allMainCovers, mainCover)
|
|
|
|
|
|
|
|
|
|
// 向 main package 的依赖注入插桩变量
|
|
|
|
|
for _, dep := range pkg.Deps {
|
2021-11-29 06:48:01 +00:00
|
|
|
|
if packageCover, ok := seen[dep]; ok {
|
|
|
|
|
allMainCovers = append(allMainCovers, packageCover)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 依赖需要忽略 Go 标准库和 go.mod 引入的第三方
|
|
|
|
|
if depPkg, ok := pkgs[dep]; ok {
|
|
|
|
|
// 注入依赖的 package
|
2021-09-02 06:36:41 +00:00
|
|
|
|
packageCover, depDecl := b.addCounters(depPkg)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
// 收集插桩变量的定义和元信息
|
|
|
|
|
allDecl += depDecl
|
|
|
|
|
allMainCovers = append(allMainCovers, packageCover)
|
|
|
|
|
// 避免重复访问
|
|
|
|
|
seen[dep] = packageCover
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 为每个 main 包注入 websocket handler
|
2021-09-02 06:36:41 +00:00
|
|
|
|
b.injectGocAgent(b.getPkgTmpDir(pkg.Dir), allMainCovers)
|
|
|
|
|
if b.Mode == "watch" {
|
|
|
|
|
log.Donef("inject main package [%v] with rpcagent and watchagent", pkg.ImportPath)
|
|
|
|
|
} else {
|
|
|
|
|
log.Donef("inject main package [%v] with rpcagent", pkg.ImportPath)
|
|
|
|
|
}
|
2021-06-09 07:40:02 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-14 07:18:29 +00:00
|
|
|
|
// 在工程根目录注入所有插桩变量的声明+定义
|
2021-09-02 06:36:41 +00:00
|
|
|
|
b.injectGlobalCoverVarFile(allDecl)
|
2021-08-10 14:50:30 +00:00
|
|
|
|
|
2021-06-20 07:44:16 +00:00
|
|
|
|
// 添加自定义 websocket 依赖
|
|
|
|
|
// 用户代码可能有 gorrila/websocket 的依赖,为避免依赖冲突,以及可能的 replace/vendor,
|
|
|
|
|
// 这里直接注入一份完整的 gorrila/websocket 实现
|
2021-09-02 06:36:41 +00:00
|
|
|
|
websocket.AddCustomWebsocketDep(b.GlobalCoverVarImportPathDir)
|
2021-06-20 07:44:16 +00:00
|
|
|
|
log.Donef("websocket library injected")
|
2021-06-14 07:18:29 +00:00
|
|
|
|
|
2021-06-09 07:40:02 +00:00
|
|
|
|
log.StopWait()
|
2021-09-02 06:36:41 +00:00
|
|
|
|
log.Donef("global cover variables injected")
|
2021-06-09 07:40:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-14 07:18:29 +00:00
|
|
|
|
// addCounters is different from official go tool cover
|
|
|
|
|
//
|
|
|
|
|
// 1. only inject covervar++ into source file
|
|
|
|
|
//
|
|
|
|
|
// 2. no declarartions for these covervars
|
|
|
|
|
//
|
|
|
|
|
// 3. return the declarations as string
|
2021-09-02 06:36:41 +00:00
|
|
|
|
func (b *Build) addCounters(pkg *Package) (*PackageCover, string) {
|
|
|
|
|
mode := b.Mode
|
|
|
|
|
gobalCoverVarImportPath := b.GlobalCoverVarImportPath
|
2021-06-14 07:18:29 +00:00
|
|
|
|
|
|
|
|
|
coverVarMap := declareCoverVars(pkg)
|
|
|
|
|
|
|
|
|
|
decl := ""
|
|
|
|
|
for file, coverVar := range coverVarMap {
|
2021-09-02 06:36:41 +00:00
|
|
|
|
decl += "\n" + tool.Annotate(filepath.Join(b.getPkgTmpDir(pkg.Dir), file), mode, coverVar.Var, coverVar.File, gobalCoverVarImportPath) + "\n"
|
2021-06-14 07:18:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-02 06:36:41 +00:00
|
|
|
|
return &PackageCover{
|
2021-06-14 07:18:29 +00:00
|
|
|
|
Package: pkg,
|
|
|
|
|
Vars: coverVarMap,
|
|
|
|
|
}, decl
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getPkgTmpDir gets corresponding pkg dir in temporary project
|
|
|
|
|
//
|
|
|
|
|
// the reason is that config.GocConfig.Pkgs is get in the original project.
|
|
|
|
|
// we need to transfer the direcory.
|
|
|
|
|
//
|
|
|
|
|
// 在原工程目录已经做了一次 go list -json,在临时目录没有必要再做一遍,直接转换一下就能得到
|
|
|
|
|
// 临时目录中的 pkg.Dir。
|
2021-09-02 06:36:41 +00:00
|
|
|
|
func (b *Build) getPkgTmpDir(pkgDir string) string {
|
|
|
|
|
relDir, err := filepath.Rel(b.CurModProjectDir, pkgDir)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("go json -list meta info wrong: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-02 06:36:41 +00:00
|
|
|
|
return filepath.Join(b.TmpModProjectDir, relDir)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 07:44:16 +00:00
|
|
|
|
// injectGocAgent inject handlers like following
|
2021-06-14 07:18:29 +00:00
|
|
|
|
//
|
2023-08-28 02:31:52 +00:00
|
|
|
|
// - xxx.go
|
|
|
|
|
// - yyy_package
|
|
|
|
|
// - main.go
|
|
|
|
|
// - goc-cover-agent-apis-auto-generated-11111-22222-bridge.go
|
|
|
|
|
// - goc-cover-agent-apis-auto-generated-11111-22222-package
|
|
|
|
|
// |
|
|
|
|
|
// -- rpcagent.go
|
|
|
|
|
// -- watchagent.go
|
2021-06-14 07:18:29 +00:00
|
|
|
|
//
|
2021-06-20 07:44:16 +00:00
|
|
|
|
// 11111_22222_bridge.go 仅仅用于引用 11111_22222_package, where package contains ws agent main logic.
|
2021-06-14 07:18:29 +00:00
|
|
|
|
// 使用 bridge.go 文件是为了避免插桩逻辑中的变量名污染 main 包
|
2021-09-02 06:36:41 +00:00
|
|
|
|
func (b *Build) injectGocAgent(where string, covers []*PackageCover) {
|
2022-02-21 12:15:52 +00:00
|
|
|
|
if len(covers) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(covers[0].Vars) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 07:44:16 +00:00
|
|
|
|
injectPkgName := "goc-cover-agent-apis-auto-generated-11111-22222-package"
|
2021-06-24 07:22:24 +00:00
|
|
|
|
injectBridgeName := "goc-cover-agent-apis-auto-generated-11111-22222-bridge.go"
|
2021-06-14 07:18:29 +00:00
|
|
|
|
wherePkg := filepath.Join(where, injectPkgName)
|
|
|
|
|
err := os.MkdirAll(wherePkg, os.ModePerm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("fail to generate %v directory: %v", injectPkgName, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// create bridge file
|
2021-06-24 07:22:24 +00:00
|
|
|
|
whereBridge := filepath.Join(where, injectBridgeName)
|
2021-06-24 07:39:20 +00:00
|
|
|
|
f1, err := os.Create(whereBridge)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("fail to create cover bridge file in temporary project: %v", err)
|
|
|
|
|
}
|
2021-06-24 07:39:20 +00:00
|
|
|
|
defer f1.Close()
|
2021-06-14 07:18:29 +00:00
|
|
|
|
|
|
|
|
|
tmplBridgeData := struct {
|
|
|
|
|
CoverImportPath string
|
|
|
|
|
}{
|
|
|
|
|
// covers[0] is the main package
|
|
|
|
|
CoverImportPath: covers[0].Package.ImportPath + "/" + injectPkgName,
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-24 07:39:20 +00:00
|
|
|
|
if err := coverBridgeTmpl.Execute(f1, tmplBridgeData); err != nil {
|
2021-06-14 07:18:29 +00:00
|
|
|
|
log.Fatalf("fail to generate cover bridge in temporary project: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 07:44:16 +00:00
|
|
|
|
// create ws agent files
|
2021-08-10 14:50:30 +00:00
|
|
|
|
dest := filepath.Join(wherePkg, "rpcagent.go")
|
2021-06-14 07:18:29 +00:00
|
|
|
|
|
2021-06-24 07:39:20 +00:00
|
|
|
|
f2, err := os.Create(dest)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
if err != nil {
|
2021-06-20 07:44:16 +00:00
|
|
|
|
log.Fatalf("fail to create cover agent file in temporary project: %v", err)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
}
|
2021-06-24 07:39:20 +00:00
|
|
|
|
defer f2.Close()
|
2021-06-14 07:18:29 +00:00
|
|
|
|
|
2021-06-24 07:22:24 +00:00
|
|
|
|
var _coverMode string
|
2021-09-02 06:36:41 +00:00
|
|
|
|
if b.Mode == "watch" {
|
2021-06-24 07:22:24 +00:00
|
|
|
|
_coverMode = "cover"
|
|
|
|
|
} else {
|
2021-09-02 06:36:41 +00:00
|
|
|
|
_coverMode = b.Mode
|
2021-06-24 07:22:24 +00:00
|
|
|
|
}
|
2023-09-08 03:31:19 +00:00
|
|
|
|
var commitID string
|
2023-09-18 09:10:23 +00:00
|
|
|
|
cmd := exec.Command("git", "rev-parse", "--short=8", "HEAD")
|
2023-09-07 11:05:02 +00:00
|
|
|
|
output, err := cmd.Output()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("git describe Error:", err)
|
|
|
|
|
} else {
|
2023-09-08 03:31:19 +00:00
|
|
|
|
commitID = strings.TrimRight(string(output), "\n")
|
2023-09-07 11:05:02 +00:00
|
|
|
|
}
|
2023-09-08 03:52:24 +00:00
|
|
|
|
var branch string
|
2023-09-11 03:08:47 +00:00
|
|
|
|
cmd = exec.Command("git", "branch", "--contains", commitID, "-r")
|
2023-09-08 03:52:24 +00:00
|
|
|
|
br, err := cmd.Output()
|
|
|
|
|
if err != nil {
|
2023-09-18 07:45:13 +00:00
|
|
|
|
log.Errorf("get git branch Error:", err)
|
2023-09-08 03:52:24 +00:00
|
|
|
|
} else {
|
2023-09-18 07:45:13 +00:00
|
|
|
|
log.Infof("[goc][info] raw branch: %v ", branch)
|
|
|
|
|
branch = strings.Replace(string(br), "\n", "", -1)
|
2023-09-11 06:21:25 +00:00
|
|
|
|
branch = strings.TrimLeft(branch, "heads/")
|
|
|
|
|
branch = strings.Trim(branch, " ")
|
2023-09-08 03:52:24 +00:00
|
|
|
|
}
|
|
|
|
|
log.Infof("[goc][info] branch: %v --- commitID: %v", branch, commitID)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
tmplData := struct {
|
2021-09-02 06:36:41 +00:00
|
|
|
|
Covers []*PackageCover
|
2021-06-14 07:18:29 +00:00
|
|
|
|
GlobalCoverVarImportPath string
|
|
|
|
|
Package string
|
2021-06-20 07:44:16 +00:00
|
|
|
|
Host string
|
2021-06-20 13:14:21 +00:00
|
|
|
|
Mode string
|
2023-09-07 11:05:02 +00:00
|
|
|
|
CommitID string
|
2023-09-08 03:52:24 +00:00
|
|
|
|
Branch string
|
2021-06-14 07:18:29 +00:00
|
|
|
|
}{
|
|
|
|
|
Covers: covers,
|
2021-09-02 06:36:41 +00:00
|
|
|
|
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
|
2021-06-14 07:18:29 +00:00
|
|
|
|
Package: injectPkgName,
|
2021-09-02 06:36:41 +00:00
|
|
|
|
Host: b.Host,
|
2021-06-24 07:22:24 +00:00
|
|
|
|
Mode: _coverMode,
|
2023-09-08 03:52:24 +00:00
|
|
|
|
Branch: branch,
|
2023-09-08 03:31:19 +00:00
|
|
|
|
CommitID: commitID,
|
2021-06-14 07:18:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-24 07:39:20 +00:00
|
|
|
|
if err := coverMainTmpl.Execute(f2, tmplData); err != nil {
|
2021-06-20 07:44:16 +00:00
|
|
|
|
log.Fatalf("fail to generate cover agent handlers in temporary project: %v", err)
|
2021-06-14 07:18:29 +00:00
|
|
|
|
}
|
2021-08-10 14:50:30 +00:00
|
|
|
|
|
|
|
|
|
// 写入 watch
|
2021-09-02 06:36:41 +00:00
|
|
|
|
if b.Mode != "watch" {
|
2021-08-10 14:50:30 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
f, err := os.Create(filepath.Join(wherePkg, "watchagent.go"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("fail to create watchagent file: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tmplwatchData := struct {
|
|
|
|
|
Random string
|
|
|
|
|
Host string
|
|
|
|
|
GlobalCoverVarImportPath string
|
|
|
|
|
}{
|
2021-09-02 06:36:41 +00:00
|
|
|
|
Random: filepath.Base(b.TmpModProjectDir),
|
|
|
|
|
Host: b.Host,
|
|
|
|
|
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
|
2021-08-10 14:50:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := coverWatchTmpl.Execute(f, tmplwatchData); err != nil {
|
|
|
|
|
log.Fatalf("fail to generate watchagent in temporary project: %v", err)
|
|
|
|
|
}
|
2021-06-14 07:18:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-19 08:17:29 +00:00
|
|
|
|
// injectGlobalCoverVarFile 写入所有插桩变量的全局定义至一个单独的文件
|
2021-09-02 06:36:41 +00:00
|
|
|
|
func (b *Build) injectGlobalCoverVarFile(decl string) {
|
|
|
|
|
globalCoverVarPackage := path.Base(b.GlobalCoverVarImportPath)
|
|
|
|
|
globalCoverDef := filepath.Join(b.TmpModProjectDir, globalCoverVarPackage)
|
|
|
|
|
b.GlobalCoverVarImportPathDir = globalCoverDef
|
2021-06-20 07:44:16 +00:00
|
|
|
|
|
2021-06-17 11:53:00 +00:00
|
|
|
|
err := os.MkdirAll(globalCoverDef, os.ModePerm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("fail to create global cover definition package dir: %v", err)
|
|
|
|
|
}
|
|
|
|
|
coverFile, err := os.Create(filepath.Join(globalCoverDef, "cover.go"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("fail to create global cover definition file: %v", err)
|
|
|
|
|
}
|
2021-06-09 07:40:02 +00:00
|
|
|
|
|
2021-06-17 11:53:00 +00:00
|
|
|
|
defer coverFile.Close()
|
|
|
|
|
|
|
|
|
|
packageName := "package coverdef\n\n"
|
|
|
|
|
|
2021-09-02 06:36:41 +00:00
|
|
|
|
random := filepath.Base(b.TmpModProjectDir)
|
2021-08-10 14:50:30 +00:00
|
|
|
|
varWatchDef := fmt.Sprintf(`
|
|
|
|
|
var WatchChannel_%v = make(chan *blockInfo, 1024)
|
|
|
|
|
|
|
|
|
|
var WatchEnabled_%v = false
|
|
|
|
|
|
|
|
|
|
type blockInfo struct {
|
|
|
|
|
Name string
|
|
|
|
|
Pos []uint32
|
|
|
|
|
I int
|
|
|
|
|
Stmts int
|
2021-06-09 07:40:02 +00:00
|
|
|
|
}
|
2021-06-24 07:22:24 +00:00
|
|
|
|
|
2021-08-10 14:50:30 +00:00
|
|
|
|
// UploadCoverChangeEvent_%v is non-blocking
|
|
|
|
|
func UploadCoverChangeEvent_%v(name string, pos []uint32, i int, stmts uint16) {
|
2021-06-24 07:22:24 +00:00
|
|
|
|
|
2021-08-10 14:50:30 +00:00
|
|
|
|
if WatchEnabled_%v == false {
|
|
|
|
|
return
|
2021-06-24 07:22:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-10 14:50:30 +00:00
|
|
|
|
// make sure send is non-blocking
|
|
|
|
|
select {
|
|
|
|
|
case WatchChannel_%v <- &blockInfo{
|
|
|
|
|
Name: name,
|
|
|
|
|
Pos: pos,
|
|
|
|
|
I: i,
|
|
|
|
|
Stmts: int(stmts),
|
|
|
|
|
}:
|
|
|
|
|
default:
|
2021-06-24 07:22:24 +00:00
|
|
|
|
}
|
2021-08-10 14:50:30 +00:00
|
|
|
|
}
|
2021-06-24 07:22:24 +00:00
|
|
|
|
|
2021-08-10 14:50:30 +00:00
|
|
|
|
`, random, random, random, random, random, random)
|
|
|
|
|
|
|
|
|
|
_, err = coverFile.WriteString(packageName + varWatchDef + decl)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("fail to write to global cover definition file: %v", err)
|
2021-06-24 07:22:24 +00:00
|
|
|
|
}
|
|
|
|
|
}
|