add bridge package
This commit is contained in:
parent
495a887dc4
commit
b3647fd9d9
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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 的各种元属性
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
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