add bridge package
This commit is contained in:
parent
495a887dc4
commit
b3647fd9d9
@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qiniu/goc/v2/pkg/build"
|
"github.com/qiniu/goc/v2/pkg/build"
|
||||||
|
"github.com/qiniu/goc/v2/pkg/config"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ var buildCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
buildCmd.Flags().StringVarP(&config.GocConfig.Mode, "mode", "", "count", "coverage mode: set, count, atomic")
|
||||||
rootCmd.AddCommand(buildCmd)
|
rootCmd.AddCommand(buildCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qiniu/goc/v2/pkg/build"
|
"github.com/qiniu/goc/v2/pkg/build"
|
||||||
"github.com/qiniu/goc/v2/pkg/config"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,8 +15,8 @@ var serverCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
serverCmd.Flags().IntVarP(&config.GocConfig.Port, "port", "", 7777, "listen port to start a coverage host center")
|
// 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().StringVarP(&config.GocConfig.StorePath, "storepath", "", "goc.store", "the file to save all goc server information")
|
||||||
|
|
||||||
rootCmd.AddCommand(serverCmd)
|
rootCmd.AddCommand(serverCmd)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ func (b *Build) readProjectMetaInfo() {
|
|||||||
}
|
}
|
||||||
// 工程根目录
|
// 工程根目录
|
||||||
config.GocConfig.CurModProjectDir = pkg.Root
|
config.GocConfig.CurModProjectDir = pkg.Root
|
||||||
|
config.GocConfig.ImportPath = pkg.Module.Path
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -38,6 +39,8 @@ func (b *Build) readProjectMetaInfo() {
|
|||||||
config.GocConfig.TmpModProjectDir = filepath.Join(os.TempDir(), tmpFolderName(config.GocConfig.CurModProjectDir))
|
config.GocConfig.TmpModProjectDir = filepath.Join(os.TempDir(), tmpFolderName(config.GocConfig.CurModProjectDir))
|
||||||
// get cur pkg dir in the corresponding tmp dir
|
// get cur pkg dir in the corresponding tmp dir
|
||||||
config.GocConfig.TmpPkgDir = filepath.Join(config.GocConfig.TmpModProjectDir, config.GocConfig.CurPkgDir[len(config.GocConfig.CurModProjectDir):])
|
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")
|
log.Donef("project meta information parsed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,19 +3,21 @@ package config
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type gocConfig struct {
|
type gocConfig struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
CurPkgDir string
|
ImportPath string // import path of the project
|
||||||
CurModProjectDir string
|
CurPkgDir string
|
||||||
TmpModProjectDir string
|
CurModProjectDir string
|
||||||
TmpPkgDir string
|
TmpModProjectDir string
|
||||||
BinaryName string
|
TmpPkgDir string
|
||||||
Pkgs map[string]*Package
|
BinaryName string
|
||||||
GOPATH string
|
Pkgs map[string]*Package
|
||||||
GOBIN string
|
GOPATH string
|
||||||
IsMod bool // deprecated
|
GOBIN string
|
||||||
|
IsMod bool // deprecated
|
||||||
|
GlobalCoverVarImportPath string
|
||||||
|
|
||||||
Port int // used both by server & client
|
Host string
|
||||||
StorePath string // persist store location
|
Mode string // cover mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// GocConfig 全局变量,存放 goc 的各种元属性
|
// GocConfig 全局变量,存放 goc 的各种元属性
|
||||||
|
@ -1,87 +1,45 @@
|
|||||||
package cover
|
package cover
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
// PackageCover holds all the generate coverage variables of a package
|
"github.com/qiniu/goc/v2/pkg/config"
|
||||||
type PackageCover struct {
|
)
|
||||||
Package *Package
|
|
||||||
Vars map[string]*FileVar
|
// 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 {
|
||||||
// FileVar holds the name of the generated coverage variables targeting the named file.
|
coverVars := make(map[string]*config.FileVar)
|
||||||
type FileVar struct {
|
coverIndex := 0
|
||||||
File string
|
// We create the cover counters as new top-level variables in the package.
|
||||||
Var string
|
// 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.
|
||||||
// Package map a package output by go list
|
// The point is only to avoid accidents, not to defeat users determined to
|
||||||
// this is subset of package struct in: https://github.com/golang/go/blob/master/src/cmd/go/internal/load/pkg.go#L58
|
// break things.
|
||||||
type Package struct {
|
sum := sha256.Sum256([]byte(p.ImportPath))
|
||||||
Dir string `json:"Dir"` // directory containing package sources
|
h := fmt.Sprintf("%x", sum[:6])
|
||||||
ImportPath string `json:"ImportPath"` // import path of package in dir
|
for _, file := range p.GoFiles {
|
||||||
Name string `json:"Name"` // package name
|
// These names appear in the cmd/cover HTML interface.
|
||||||
Target string `json:",omitempty"` // installed target for this package (may be executable)
|
var longFile = path.Join(p.ImportPath, file)
|
||||||
Root string `json:",omitempty"` // Go root, Go path dir, or module root dir containing this package
|
coverVars[file] = &config.FileVar{
|
||||||
|
File: longFile,
|
||||||
Module *ModulePublic `json:",omitempty"` // info about package's module, if any
|
Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
|
||||||
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?
|
coverIndex++
|
||||||
DepOnly bool `json:"DepOnly,omitempty"` // package is only a dependency, not explicitly listed
|
}
|
||||||
|
|
||||||
// Source files
|
for _, file := range p.CgoFiles {
|
||||||
GoFiles []string `json:"GoFiles,omitempty"` // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
|
// These names appear in the cmd/cover HTML interface.
|
||||||
CgoFiles []string `json:"CgoFiles,omitempty"` // .go source files that import "C"
|
var longFile = path.Join(p.ImportPath, file)
|
||||||
|
coverVars[file] = &config.FileVar{
|
||||||
// Dependency information
|
File: longFile,
|
||||||
Deps []string `json:"Deps,omitempty"` // all (recursively) imported dependencies
|
Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
|
||||||
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)
|
coverIndex++
|
||||||
|
}
|
||||||
// Error information
|
|
||||||
Incomplete bool `json:"Incomplete,omitempty"` // this package or a dependency has an error
|
return coverVars
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package cover
|
package cover
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/qiniu/goc/v2/pkg/config"
|
"github.com/qiniu/goc/v2/pkg/config"
|
||||||
|
"github.com/qiniu/goc/v2/pkg/cover/internal/tool"
|
||||||
"github.com/qiniu/goc/v2/pkg/log"
|
"github.com/qiniu/goc/v2/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -9,20 +13,153 @@ import (
|
|||||||
func Inject() {
|
func Inject() {
|
||||||
log.StartWait("injecting cover variables")
|
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" {
|
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.StopWait()
|
||||||
log.Donef("cover variables injected")
|
log.Donef("cover variables injected")
|
||||||
}
|
}
|
||||||
|
|
||||||
// declareCoverVars attaches the required cover variables names
|
// addCounters is different from official go tool cover
|
||||||
// to the files, to be used when annotating the files.
|
//
|
||||||
func declareCoverVars(p *Package) map[string]*FileVar {
|
// 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
31
pkg/cover/template.go
Normal 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")
|
||||||
|
}
|
||||||
|
`
|
Loading…
Reference in New Issue
Block a user