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 ( 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)
} }

View File

@ -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)
} }

View File

@ -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")
} }

View File

@ -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 的各种元属性

View File

@ -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
} }

View File

@ -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
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")
}
`