add bridge package

This commit is contained in:
lyyyuna 2021-06-14 15:18:29 +08:00
parent 495a887dc4
commit b3647fd9d9
7 changed files with 237 additions and 105 deletions

View File

@ -2,6 +2,7 @@ package cmd
import (
"github.com/qiniu/goc/v2/pkg/build"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/spf13/cobra"
)
@ -13,6 +14,7 @@ var buildCmd = &cobra.Command{
}
func init() {
buildCmd.Flags().StringVarP(&config.GocConfig.Mode, "mode", "", "count", "coverage mode: set, count, atomic")
rootCmd.AddCommand(buildCmd)
}

View File

@ -2,7 +2,6 @@ package cmd
import (
"github.com/qiniu/goc/v2/pkg/build"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/spf13/cobra"
)
@ -16,8 +15,8 @@ var serverCmd = &cobra.Command{
}
func init() {
serverCmd.Flags().IntVarP(&config.GocConfig.Port, "port", "", 7777, "listen port to start a coverage host center")
serverCmd.Flags().StringVarP(&config.GocConfig.StorePath, "storepath", "", "goc.store", "the file to save all goc server information")
// serverCmd.Flags().IntVarP(&config.GocConfig.Port, "port", "", 7777, "listen port to start a coverage host center")
// serverCmd.Flags().StringVarP(&config.GocConfig.StorePath, "storepath", "", "goc.store", "the file to save all goc server information")
rootCmd.AddCommand(serverCmd)
}

View File

@ -30,6 +30,7 @@ func (b *Build) readProjectMetaInfo() {
}
// 工程根目录
config.GocConfig.CurModProjectDir = pkg.Root
config.GocConfig.ImportPath = pkg.Module.Path
break
}
@ -38,6 +39,8 @@ func (b *Build) readProjectMetaInfo() {
config.GocConfig.TmpModProjectDir = filepath.Join(os.TempDir(), tmpFolderName(config.GocConfig.CurModProjectDir))
// get cur pkg dir in the corresponding tmp dir
config.GocConfig.TmpPkgDir = filepath.Join(config.GocConfig.TmpModProjectDir, config.GocConfig.CurPkgDir[len(config.GocConfig.CurModProjectDir):])
// get GlobalCoverVarImportPath
config.GocConfig.GlobalCoverVarImportPath = tmpFolderName(config.GocConfig.CurModProjectDir)
log.Donef("project meta information parsed")
}

View File

@ -3,19 +3,21 @@ package config
import "time"
type gocConfig struct {
Debug bool
CurPkgDir string
CurModProjectDir string
TmpModProjectDir string
TmpPkgDir string
BinaryName string
Pkgs map[string]*Package
GOPATH string
GOBIN string
IsMod bool // deprecated
Debug bool
ImportPath string // import path of the project
CurPkgDir string
CurModProjectDir string
TmpModProjectDir string
TmpPkgDir string
BinaryName string
Pkgs map[string]*Package
GOPATH string
GOBIN string
IsMod bool // deprecated
GlobalCoverVarImportPath string
Port int // used both by server & client
StorePath string // persist store location
Host string
Mode string // cover mode
}
// GocConfig 全局变量,存放 goc 的各种元属性

View File

@ -1,87 +1,45 @@
package cover
import "time"
import (
"crypto/sha256"
"fmt"
"path"
// PackageCover holds all the generate coverage variables of a package
type PackageCover struct {
Package *Package
Vars map[string]*FileVar
}
// FileVar holds the name of the generated coverage variables targeting the named file.
type FileVar struct {
File string
Var string
}
// Package map a package output by go list
// this is subset of package struct in: https://github.com/golang/go/blob/master/src/cmd/go/internal/load/pkg.go#L58
type Package struct {
Dir string `json:"Dir"` // directory containing package sources
ImportPath string `json:"ImportPath"` // import path of package in dir
Name string `json:"Name"` // package name
Target string `json:",omitempty"` // installed target for this package (may be executable)
Root string `json:",omitempty"` // Go root, Go path dir, or module root dir containing this package
Module *ModulePublic `json:",omitempty"` // info about package's module, if any
Goroot bool `json:"Goroot,omitempty"` // is this package in the Go root?
Standard bool `json:"Standard,omitempty"` // is this package part of the standard Go library?
DepOnly bool `json:"DepOnly,omitempty"` // package is only a dependency, not explicitly listed
// Source files
GoFiles []string `json:"GoFiles,omitempty"` // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
CgoFiles []string `json:"CgoFiles,omitempty"` // .go source files that import "C"
// Dependency information
Deps []string `json:"Deps,omitempty"` // all (recursively) imported dependencies
Imports []string `json:",omitempty"` // import paths used by this package
ImportMap map[string]string `json:",omitempty"` // map from source import to ImportPath (identity entries omitted)
// Error information
Incomplete bool `json:"Incomplete,omitempty"` // this package or a dependency has an error
Error *PackageError `json:"Error,omitempty"` // error loading package
DepsErrors []*PackageError `json:"DepsErrors,omitempty"` // errors loading dependencies
}
// ModulePublic represents the package info of a module
type ModulePublic struct {
Path string `json:",omitempty"` // module path
Version string `json:",omitempty"` // module version
Versions []string `json:",omitempty"` // available module versions
Replace *ModulePublic `json:",omitempty"` // replaced by this module
Time *time.Time `json:",omitempty"` // time version was created
Update *ModulePublic `json:",omitempty"` // available update (with -u)
Main bool `json:",omitempty"` // is this the main module?
Indirect bool `json:",omitempty"` // module is only indirectly needed by main module
Dir string `json:",omitempty"` // directory holding local copy of files, if any
GoMod string `json:",omitempty"` // path to go.mod file describing module, if any
GoVersion string `json:",omitempty"` // go version used in module
Error *ModuleError `json:",omitempty"` // error loading module
}
// ModuleError represents the error loading module
type ModuleError struct {
Err string // error text
}
// PackageError is the error info for a package when list failed
type PackageError struct {
ImportStack []string // shortest path from package named on command line to this one
Pos string // position of error (if present, file:line:col)
Err string // the error itself
}
// CoverBuildInfo retreives some info from build
type CoverInfo struct {
Target string
GoPath string
IsMod bool
ModRootPath string
GlobalCoverVarImportPath string // path for the injected global cover var file
OneMainPackage bool
Args string
Mode string
AgentPort string
Center string
Singleton bool
"github.com/qiniu/goc/v2/pkg/config"
)
// declareCoverVars attaches the required cover variables names
// to the files, to be used when annotating the files.
func declareCoverVars(p *config.Package) map[string]*config.FileVar {
coverVars := make(map[string]*config.FileVar)
coverIndex := 0
// We create the cover counters as new top-level variables in the package.
// We need to avoid collisions with user variables (GoCover_0 is unlikely but still)
// and more importantly with dot imports of other covered packages,
// so we append 12 hex digits from the SHA-256 of the import path.
// The point is only to avoid accidents, not to defeat users determined to
// break things.
sum := sha256.Sum256([]byte(p.ImportPath))
h := fmt.Sprintf("%x", sum[:6])
for _, file := range p.GoFiles {
// These names appear in the cmd/cover HTML interface.
var longFile = path.Join(p.ImportPath, file)
coverVars[file] = &config.FileVar{
File: longFile,
Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
}
coverIndex++
}
for _, file := range p.CgoFiles {
// These names appear in the cmd/cover HTML interface.
var longFile = path.Join(p.ImportPath, file)
coverVars[file] = &config.FileVar{
File: longFile,
Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
}
coverIndex++
}
return coverVars
}

View File

@ -1,7 +1,11 @@
package cover
import (
"os"
"path/filepath"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/cover/internal/tool"
"github.com/qiniu/goc/v2/pkg/log"
)
@ -9,20 +13,153 @@ import (
func Inject() {
log.StartWait("injecting cover variables")
// var seen := make(map[string]*PackageCover)
var seen = make(map[string]*config.PackageCover)
for _, pkg := range config.GocConfig.Pkgs {
// 所有插桩变量定义声明
allDecl := ""
pkgs := config.GocConfig.Pkgs
for _, pkg := range pkgs {
if pkg.Name == "main" {
log.Infof("handle package: %v", pkg.ImportPath)
log.Infof("handle main package: %v", pkg.ImportPath)
// 该 main 二进制所关联的所有插桩变量的元信息
// 每个 main 之间是不相关的,需要重新定义
allMainCovers := make([]*config.PackageCover, 0)
// 注入 main package
mainCover, mainDecl := addCounters(pkg)
// 收集插桩变量的定义和元信息
allDecl += mainDecl
allMainCovers = append(allMainCovers, mainCover)
// 向 main package 的依赖注入插桩变量
for _, dep := range pkg.Deps {
if _, ok := seen[dep]; ok {
continue
}
// 依赖需要忽略 Go 标准库和 go.mod 引入的第三方
if depPkg, ok := pkgs[dep]; ok {
// 注入依赖的 package
packageCover, depDecl := addCounters(depPkg)
// 收集插桩变量的定义和元信息
allDecl += depDecl
allMainCovers = append(allMainCovers, packageCover)
// 避免重复访问
seen[dep] = packageCover
}
}
// 为每个 main 包注入 websocket handler
injectCoverHandler(getPkgTmpDir(pkg.Dir), allMainCovers)
}
}
// 在工程根目录注入所有插桩变量的声明+定义
injectGlobalCoverVarFile(allDecl)
log.StopWait()
log.Donef("cover variables injected")
}
// declareCoverVars attaches the required cover variables names
// to the files, to be used when annotating the files.
func declareCoverVars(p *Package) map[string]*FileVar {
// 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 addCounters(pkg *config.Package) (*config.PackageCover, string) {
mode := config.GocConfig.Mode
gobalCoverVarImportPath := config.GocConfig.GlobalCoverVarImportPath
coverVarMap := declareCoverVars(pkg)
decl := ""
for file, coverVar := range coverVarMap {
decl += "\n" + tool.Annotate(filepath.Join(getPkgTmpDir(pkg.Dir), file), mode, coverVar.Var, gobalCoverVarImportPath) + "\n"
}
return &config.PackageCover{
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 getPkgTmpDir(pkgDir string) string {
relDir, err := filepath.Rel(config.GocConfig.CurModProjectDir, pkgDir)
if err != nil {
log.Fatalf("go json -list meta info wrong: %v", err)
}
return filepath.Join(config.GocConfig.TmpModProjectDir, relDir)
}
// injectCoverHandler inject handlers like following
//
// - xxx.go
// - yyy_package
// - main.go
// - goc_http_cover_apis_auto_generated_11111_22222_bridge.go
// - goc_http_cover_apis_auto_generated_11111_22222_package
// |
// -- init.go
//
// 11111_22222_bridge.go just import 11111_22222_package, where package contains ws handlers.
// 使用 bridge.go 文件是为了避免插桩逻辑中的变量名污染 main 包
func injectCoverHandler(where string, covers []*config.PackageCover) {
injectPkgName := "goc_http_cover_apis_auto_generated_11111_22222_package"
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
whereBridge := filepath.Join(where, "goc_http_cover_apis_auto_generated_11111_22222_bridge.go")
f, err := os.Create(whereBridge)
if err != nil {
log.Fatalf("fail to create cover bridge file in temporary project: %v", err)
}
tmplBridgeData := struct {
CoverImportPath string
}{
// covers[0] is the main package
CoverImportPath: covers[0].Package.ImportPath + "/" + injectPkgName,
}
if err := coverBridgeTmpl.Execute(f, tmplBridgeData); err != nil {
log.Fatalf("fail to generate cover bridge in temporary project: %v", err)
}
// create ws handler files
dest := filepath.Join(wherePkg, "init.go")
f, err = os.Create(dest)
if err != nil {
log.Fatalf("fail to create cover handlers in temporary project: %v", err)
}
tmplData := struct {
Covers []*config.PackageCover
GlobalCoverVarImportPath string
Package string
}{
Covers: covers,
GlobalCoverVarImportPath: config.GocConfig.GlobalCoverVarImportPath,
Package: injectPkgName,
}
if err := coverMainTmpl.Execute(f, tmplData); err != nil {
log.Fatalf("fail to generate cover handlers in temporary project: %v", err)
}
}
func injectGlobalCoverVarFile(decl string) {
return nil
}

31
pkg/cover/template.go Normal file
View File

@ -0,0 +1,31 @@
package cover
import "html/template"
var coverBridgeTmpl = template.Must(template.New("coverBridge").Parse(coverBridge))
const coverBridge = `
// Code generated by goc system. DO NOT EDIT.
package main
import _ {{.CoverImportPath | printf "%q"}}
`
var coverMainTmpl = template.Must(template.New("coverMain").Parse(coverMain))
const coverMain = `
// Code generated by goc system. DO NOT EDIT.
package {{.Package | printf ".%q"}}
import (
"log"
_cover {{.GlobalCoverVarImportPath | printf "%q"}}
)
func init() {
log.Println("hhhh")
}
`