goc/pkg/build/inject.go

284 lines
8.3 KiB
Go
Raw Normal View History

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.
*/
package build
2021-06-09 07:40:02 +00:00
import (
"fmt"
2021-06-14 07:18:29 +00:00
"os"
2021-06-17 11:53:00 +00:00
"path"
2021-06-14 07:18:29 +00:00
"path/filepath"
"github.com/qiniu/goc/v2/pkg/build/internal/tool"
"github.com/qiniu/goc/v2/pkg/build/internal/websocket"
2021-06-09 07:40:02 +00:00
"github.com/qiniu/goc/v2/pkg/log"
)
// Inject injects cover variables for all the .go files in the target directory
func (b *Build) Inject() {
2021-06-09 07:40:02 +00:00
log.StartWait("injecting cover variables")
var seen = make(map[string]*PackageCover)
2021-06-14 07:18:29 +00:00
// 所有插桩变量定义声明
allDecl := ""
2021-06-09 07:40:02 +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 之间是不相关的,需要重新定义
allMainCovers := make([]*PackageCover, 0)
2021-06-14 07:18:29 +00:00
// 注入 main package
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
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
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
// 在工程根目录注入所有插桩变量的声明+定义
b.injectGlobalCoverVarFile(allDecl)
2021-06-20 07:44:16 +00:00
// 添加自定义 websocket 依赖
// 用户代码可能有 gorrila/websocket 的依赖,为避免依赖冲突,以及可能的 replace/vendor
// 这里直接注入一份完整的 gorrila/websocket 实现
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()
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
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 {
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
}
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。
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)
}
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
//
// - xxx.go
// - yyy_package
// - main.go
2021-06-20 07:44:16 +00:00
// - goc-cover-agent-apis-auto-generated-11111-22222-bridge.go
// - goc-cover-agent-apis-auto-generated-11111-22222-package
2021-06-14 07:18:29 +00:00
// |
// -- 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 包
func (b *Build) injectGocAgent(where string, covers []*PackageCover) {
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)
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)
}
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,
}
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
dest := filepath.Join(wherePkg, "rpcagent.go")
2021-06-14 07:18:29 +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
}
defer f2.Close()
2021-06-14 07:18:29 +00:00
2021-06-24 07:22:24 +00:00
var _coverMode string
if b.Mode == "watch" {
2021-06-24 07:22:24 +00:00
_coverMode = "cover"
} else {
_coverMode = b.Mode
2021-06-24 07:22:24 +00:00
}
2021-06-14 07:18:29 +00:00
tmplData := struct {
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
2021-06-14 07:18:29 +00:00
}{
Covers: covers,
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
2021-06-14 07:18:29 +00:00
Package: injectPkgName,
Host: b.Host,
2021-06-24 07:22:24 +00:00
Mode: _coverMode,
2021-06-14 07:18:29 +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
}
// 写入 watch
if b.Mode != "watch" {
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
}{
Random: filepath.Base(b.TmpModProjectDir),
Host: b.Host,
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
}
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 写入所有插桩变量的全局定义至一个单独的文件
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"
random := filepath.Base(b.TmpModProjectDir)
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
// UploadCoverChangeEvent_%v is non-blocking
func UploadCoverChangeEvent_%v(name string, pos []uint32, i int, stmts uint16) {
2021-06-24 07:22:24 +00:00
if WatchEnabled_%v == false {
return
2021-06-24 07:22:24 +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-06-24 07:22:24 +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
}
}