1. 去掉全局的配置 config
2. 合并 pkg/build, pkg/flag, pkg/cover, pkg/config 几个包(这几个包有强相关性,适合放一处。并且分开也容易造成循环依赖)
This commit is contained in:
lyyyuna 2021-09-02 14:36:41 +08:00 committed by Li Yiyang
parent d55c54d3a1
commit 456c883987
29 changed files with 727 additions and 636 deletions

View File

@ -2,8 +2,6 @@ package cmd
import (
"github.com/qiniu/goc/v2/pkg/build"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/flag"
"github.com/spf13/cobra"
)
@ -14,15 +12,29 @@ var buildCmd = &cobra.Command{
DisableFlagParsing: true, // build 命令需要用原生 go 的方式处理 flags
}
var (
gocmode string
gochost string
)
func init() {
buildCmd.Flags().StringVarP(&config.GocConfig.Mode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch")
buildCmd.Flags().StringVarP(&config.GocConfig.Host, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever")
buildCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch")
buildCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever")
rootCmd.AddCommand(buildCmd)
}
func buildAction(cmd *cobra.Command, args []string) {
// 1. 解析 goc 命令行和 go 命令行
remainedArgs := flag.BuildCmdArgsParse(cmd, args, flag.GO_BUILD)
b := build.NewBuild(remainedArgs)
sets := build.CustomParseCmdAndArgs(cmd, args)
b := build.NewBuild(
build.WithHost(gochost),
build.WithMode(gocmode),
build.WithFlagSets(sets),
build.WithArgs(args),
build.WithBuild(),
build.WithDebug(globalDebug),
)
b.Build()
}

View File

@ -2,8 +2,6 @@ package cmd
import (
"github.com/qiniu/goc/v2/pkg/build"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/flag"
"github.com/spf13/cobra"
)
@ -15,14 +13,23 @@ var installCmd = &cobra.Command{
}
func init() {
installCmd.Flags().StringVarP(&config.GocConfig.Mode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch")
installCmd.Flags().StringVarP(&config.GocConfig.Host, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever")
installCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch")
installCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever")
rootCmd.AddCommand(installCmd)
}
func installAction(cmd *cobra.Command, args []string) {
// 1. 解析 goc 命令行和 go 命令行
remainedArgs := flag.BuildCmdArgsParse(cmd, args, flag.GO_INSTALL)
b := build.NewInstall(remainedArgs)
sets := build.CustomParseCmdAndArgs(cmd, args)
b := build.NewInstall(
build.WithHost(gochost),
build.WithMode(gocmode),
build.WithFlagSets(sets),
build.WithArgs(args),
build.WithInstall(),
build.WithDebug(globalDebug),
)
b.Install()
}

View File

@ -2,7 +2,6 @@ package cmd
import (
"github.com/qiniu/goc/v2/pkg/client"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/spf13/cobra"
)
@ -17,14 +16,17 @@ goc list [flags]
Run: list,
}
var listWide bool
var (
listHost string
listWide bool
)
func init() {
listCmd.Flags().StringVar(&config.GocConfig.Host, "host", "127.0.0.1:7777", "specify the host of the goc server")
listCmd.Flags().StringVar(&listHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
listCmd.Flags().BoolVar(&listWide, "wide", false, "list all services with more information (such as pid)")
rootCmd.AddCommand(listCmd)
}
func list(cmd *cobra.Command, args []string) {
client.NewWorker("http://" + config.GocConfig.Host).ListAgents(listWide)
client.NewWorker("http://" + listHost).ListAgents(listWide)
}

View File

@ -2,7 +2,6 @@ package cmd
import (
"github.com/qiniu/goc/v2/pkg/client"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/spf13/cobra"
)
@ -19,14 +18,17 @@ goc profile --host=http://192.168.1.1:8080 --output=./coverage.cov
Run: profile,
}
var output string // --output flag
var (
profileHost string
profileoutput string // --output flag
)
func init() {
profileCmd.Flags().StringVar(&config.GocConfig.Host, "host", "127.0.0.1:7777", "specify the host of the goc server")
profileCmd.Flags().StringVarP(&output, "output", "o", "", "download cover profile")
profileCmd.Flags().StringVar(&profileHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
profileCmd.Flags().StringVarP(&profileoutput, "output", "o", "", "download cover profile")
rootCmd.AddCommand(profileCmd)
}
func profile(cmd *cobra.Command, args []string) {
client.NewWorker("http://" + config.GocConfig.Host).Profile(output)
client.NewWorker("http://" + profileHost).Profile(profileoutput)
}

View File

@ -14,7 +14,6 @@
package cmd
import (
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/log"
"github.com/spf13/cobra"
)
@ -30,7 +29,7 @@ Find more information at:
PersistentPreRun: func(cmd *cobra.Command, args []string) {
log.DisplayGoc()
// init logger
log.NewLogger()
log.NewLogger(globalDebug)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
@ -38,8 +37,10 @@ Find more information at:
},
}
var globalDebug bool
func init() {
rootCmd.PersistentFlags().BoolVar(&config.GocConfig.Debug, "gocdebug", false, "run goc in debug mode")
rootCmd.PersistentFlags().BoolVar(&globalDebug, "gocdebug", false, "run goc in debug mode")
}
// Execute the goc tool

View File

@ -1,7 +1,6 @@
package cmd
import (
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/server"
"github.com/spf13/cobra"
)
@ -15,13 +14,15 @@ var serverCmd = &cobra.Command{
Run: serve,
}
var (
serverHost string
)
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().StringVarP(&config.GocConfig.Host, "host", "", "127.0.0.1:7777", "specify the host of the goc server")
serverCmd.Flags().StringVarP(&serverHost, "host", "", "127.0.0.1:7777", "specify the host of the goc server")
rootCmd.AddCommand(serverCmd)
}
func serve(cmd *cobra.Command, args []string) {
server.RunGocServerUntilExit(config.GocConfig.Host)
server.RunGocServerUntilExit(serverHost)
}

View File

@ -1,7 +1,6 @@
package cmd
import (
"github.com/qiniu/goc/v2/pkg/config"
cli "github.com/qiniu/goc/v2/pkg/watch"
"github.com/spf13/cobra"
)
@ -15,11 +14,15 @@ var watchCmd = &cobra.Command{
Run: watch,
}
var (
watchHost string
)
func init() {
watchCmd.Flags().StringVarP(&config.GocConfig.Host, "host", "", "127.0.0.1:7777", "specify the host of the goc server")
watchCmd.Flags().StringVarP(&watchHost, "host", "", "127.0.0.1:7777", "specify the host of the goc server")
rootCmd.AddCommand(watchCmd)
}
func watch(cmd *cobra.Command, args []string) {
cli.Watch()
cli.Watch(watchHost)
}

View File

@ -3,25 +3,52 @@ package build
import (
"os"
"os/exec"
"strings"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/cover"
"github.com/qiniu/goc/v2/pkg/flag"
"github.com/qiniu/goc/v2/pkg/log"
"github.com/spf13/pflag"
)
// Build struct a build
// most configurations are stored in global variables: config.GocConfig & config.GoConfig
type Build struct {
Args []string // all goc + go command line args + flags
FlagSets *pflag.FlagSet
BuildType int
Debug bool
Host string
Mode string // cover mode
GOPATH string
GOBIN string
CurWd string
TmpWd string
CurModProjectDir string
TmpModProjectDir string
Goflags []string // go command line flags
GoArgs []string // go command line args
Packages []string // go command line [packages]
ImportPath string // the whole import path of the project
Pkgs map[string]*Package
GlobalCoverVarImportPath string
GlobalCoverVarImportPathDir string
}
// NewBuild creates a Build struct
//
func NewBuild(args []string) *Build {
func NewBuild(opts ...GocOption) *Build {
b := &Build{}
for _, opt := range opts {
opt(b)
}
// 1. 解析 goc 命令行和 go 命令行
b.buildCmdArgsParse()
// 2. 解析 go 包位置
flag.GetPackagesDir(args)
b.getPackagesDir()
// 3. 读取工程元信息go.mod, pkgs list ...
b.readProjectMetaInfo()
// 4. 展示元信息
@ -41,16 +68,19 @@ func (b *Build) Build() {
defer b.clean()
log.Donef("project copied to temporary directory")
// 2. inject cover vars
cover.Inject()
// 3. build in the temp project
// 2. update go.mod file if needed
b.updateGoModFile()
// 3. inject cover vars
b.Inject()
// 4. build in the temp project
b.doBuildInTemp()
}
func (b *Build) doBuildInTemp() {
log.StartWait("building the injected project")
goflags := config.GocConfig.Goflags
goflags := b.Goflags
// 检查用户是否自定义了 -o
oSet := false
for _, flag := range goflags {
@ -61,10 +91,10 @@ func (b *Build) doBuildInTemp() {
// 如果没被设置就加一个至原命令执行的目录
if !oSet {
goflags = append(goflags, "-o", config.GocConfig.CurWd)
goflags = append(goflags, "-o", b.CurWd)
}
pacakges := config.GocConfig.Packages
pacakges := b.Packages
goflags = append(goflags, pacakges...)
@ -72,11 +102,11 @@ func (b *Build) doBuildInTemp() {
args = append(args, goflags...)
// go 命令行由 go build [-o output] [build flags] [packages] 组成
cmd := exec.Command("go", args...)
cmd.Dir = config.GocConfig.TmpWd
cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Infof("go build cmd is: %v, in path [%v]", cmd.Args, cmd.Dir)
log.Infof("go build cmd is: %v, in path [%v]", nicePrintArgs(cmd.Args), cmd.Dir)
if err := cmd.Start(); err != nil {
log.Fatalf("fail to execute go build: %v", err)
}
@ -88,3 +118,21 @@ func (b *Build) doBuildInTemp() {
log.StopWait()
log.Donef("go build done")
}
// nicePrintArgs 优化 args 打印内容
//
// 假如go build -ldflags "-X my/package/config.Version=1.0.0" -o /home/lyy/gitdown/gin-test/cmd .
//
// 实际输出会变为go build -ldflags -X my/package/config.Version=1.0.0 -o /home/lyy/gitdown/gin-test/cmd .
func nicePrintArgs(args []string) []string {
output := make([]string, 0)
for _, arg := range args {
if strings.Contains(arg, " ") {
output = append(output, "\""+arg+"\"")
} else {
output = append(output, arg)
}
}
return output
}

425
pkg/build/build_flags.go Normal file
View File

@ -0,0 +1,425 @@
package build
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/qiniu/goc/v2/pkg/log"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var buildUsage string = `Usage:
goc build [-o output] [build flags] [packages] [goc flags]
[build flags] are same with go official command, you can copy them here directly.
The [goc flags] can be placed in anywhere in the command line.
However, other flags' order are same with the go official command.
`
var installUsage string = `Usage:
goc install [-o output] [build flags] [packages] [goc flags]
[build flags] are same with go official command, you can copy them here directly.
The [goc flags] can be placed in anywhere in the command line.
However, other flags' order are same with the go official command.
`
const (
GO_BUILD = iota
GO_INSTALL
)
// CustomParseCmdAndArgs 因为关闭了 cobra 的解析功能,需要手动构造并解析 goc flags
func CustomParseCmdAndArgs(cmd *cobra.Command, args []string) *pflag.FlagSet {
// 首先解析 cobra 定义的 flag
allFlagSets := cmd.Flags()
// 因为 args 里面含有 go 的 flag所以需要忽略解析 go flag 的错误
allFlagSets.Init("GOC", pflag.ContinueOnError)
// 忽略 go flag 在 goc 中的解析错误
allFlagSets.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{
UnknownFlags: true,
}
allFlagSets.Parse(args)
return allFlagSets
}
// buildCmdArgsParse parse both go flags and goc flags, it rewrite go flags if
// necessary, and returns all non-flag arguments.
//
// 吞下 [packages] 之前所有的 flags.
func (b *Build) buildCmdArgsParse() {
args := b.Args
cmdType := b.BuildType
allFlagSets := b.FlagSets
// 重写 help
helpFlag := allFlagSets.Lookup("help")
if helpFlag.Changed {
if cmdType == GO_BUILD {
printGoHelp(buildUsage)
} else if cmdType == GO_INSTALL {
printGoHelp(installUsage)
}
os.Exit(0)
}
// 删除 help flag
args = findAndDelHelpFlag(args)
// 必须手动调用
// 由于关闭了 cobra 的 flag parseroot PersistentPreRun 调用时log.NewLogger 并没有拿到 debug 值
log.NewLogger(b.Debug)
// 删除 cobra 定义的 flag
allFlagSets.Visit(func(f *pflag.Flag) {
args = findAndDelGocFlag(args, f.Name, f.Value.String())
})
// 然后解析 go 的 flag
goFlagSets := flag.NewFlagSet("GO", flag.ContinueOnError)
addBuildFlags(goFlagSets)
addOutputFlags(goFlagSets)
err := goFlagSets.Parse(args)
if err != nil {
log.Fatalf("%v", err)
}
// 找出设置的 go flag
curWd, err := os.Getwd()
if err != nil {
log.Fatalf("fail to get current working directory: %v", err)
}
flags := make([]string, 0)
goFlagSets.Visit(func(f *flag.Flag) {
// 将用户指定 -o 改成绝对目录
if f.Name == "o" {
outputDir := f.Value.String()
outputDir, err := filepath.Abs(outputDir)
if err != nil {
log.Fatalf("output flag is not valid: %v", err)
}
flags = append(flags, "-o", outputDir)
} else {
flags = append(flags, "-"+f.Name, f.Value.String())
}
})
b.Goflags = flags
b.CurWd = curWd
b.GoArgs = goFlagSets.Args()
return
}
func findAndDelGocFlag(a []string, x string, v string) []string {
new := make([]string, 0, len(a))
x = "--" + x
x_v := x + "=" + v
for i := 0; i < len(a); i++ {
if a[i] == "--gocdebug" {
// debug 是 bool就一个元素
continue
} else if a[i] == x {
// 有 goc flag 长这样 --mode watch
i++
continue
} else if a[i] == x_v {
// 有 goc flag 长这样 --mode=watch
continue
} else {
// 剩下的是 go flag
new = append(new, a[i])
}
}
return new
}
func findAndDelHelpFlag(a []string) []string {
new := make([]string, 0, len(a))
for _, v := range a {
if v == "--help" || v == "-h" {
continue
} else {
new = append(new, v)
}
}
return new
}
type goConfig struct {
BuildA bool
BuildBuildmode string // -buildmode flag
BuildMod string // -mod flag
BuildModReason string // reason -mod flag is set, if set by default
BuildI bool // -i flag
BuildLinkshared bool // -linkshared flag
BuildMSan bool // -msan flag
BuildN bool // -n flag
BuildO string // -o flag
BuildP int // -p flag
BuildPkgdir string // -pkgdir flag
BuildRace bool // -race flag
BuildToolexec string // -toolexec flag
BuildToolchainName string
BuildToolchainCompiler func() string
BuildToolchainLinker func() string
BuildTrimpath bool // -trimpath flag
BuildV bool // -v flag
BuildWork bool // -work flag
BuildX bool // -x flag
// from buildcontext
Installsuffix string // -installSuffix
BuildTags string // -tags
// from load
BuildAsmflags string
BuildCompiler string
BuildGcflags string
BuildGccgoflags string
BuildLdflags string
// mod related
ModCacheRW bool
ModFile string
}
var goflags goConfig
func addBuildFlags(cmdSet *flag.FlagSet) {
cmdSet.BoolVar(&goflags.BuildA, "a", false, "")
cmdSet.BoolVar(&goflags.BuildN, "n", false, "")
cmdSet.IntVar(&goflags.BuildP, "p", 4, "")
cmdSet.BoolVar(&goflags.BuildV, "v", false, "")
cmdSet.BoolVar(&goflags.BuildX, "x", false, "")
cmdSet.StringVar(&goflags.BuildBuildmode, "buildmode", "default", "")
cmdSet.StringVar(&goflags.BuildMod, "mod", "", "")
cmdSet.StringVar(&goflags.Installsuffix, "installsuffix", "", "")
// 类型和 go 原生的不一样,这里纯粹是为了 parse 并传递给 go
cmdSet.StringVar(&goflags.BuildAsmflags, "asmflags", "", "")
cmdSet.StringVar(&goflags.BuildCompiler, "compiler", "", "")
cmdSet.StringVar(&goflags.BuildGcflags, "gcflags", "", "")
cmdSet.StringVar(&goflags.BuildGccgoflags, "gccgoflags", "", "")
// mod related
cmdSet.BoolVar(&goflags.ModCacheRW, "modcacherw", false, "")
cmdSet.StringVar(&goflags.ModFile, "modfile", "", "")
cmdSet.StringVar(&goflags.BuildLdflags, "ldflags", "", "")
cmdSet.BoolVar(&goflags.BuildLinkshared, "linkshared", false, "")
cmdSet.StringVar(&goflags.BuildPkgdir, "pkgdir", "", "")
cmdSet.BoolVar(&goflags.BuildRace, "race", false, "")
cmdSet.BoolVar(&goflags.BuildMSan, "msan", false, "")
cmdSet.StringVar(&goflags.BuildTags, "tags", "", "")
cmdSet.StringVar(&goflags.BuildToolexec, "toolexec", "", "")
cmdSet.BoolVar(&goflags.BuildTrimpath, "trimpath", false, "")
cmdSet.BoolVar(&goflags.BuildWork, "work", false, "")
}
func addOutputFlags(cmdSet *flag.FlagSet) {
cmdSet.StringVar(&goflags.BuildO, "o", "", "")
}
func printGoHelp(usage string) {
fmt.Println(usage)
}
func printGocHelp(cmd *cobra.Command) {
flags := cmd.LocalFlags()
globalFlags := cmd.Parent().PersistentFlags()
fmt.Println("Flags:")
fmt.Println(flags.FlagUsages())
fmt.Println("Global Flags:")
fmt.Println(globalFlags.FlagUsages())
}
// GetPackagesDir parse [pacakges] part of args, it will fatal if error encountered
//
// 函数获取 1 [packages] 所在的目录位置,供后续插桩使用。
//
// 函数获取 2 如果参数是 *.go第一个 .go 文件的文件名。go build 中,二进制名字既可能是目录名也可能是文件名,和参数类型有关。
//
// 如果 [packages] 非法(即不符合 go 原生的定义),则返回对应错误
// 这里只考虑 go mod 的方式
func (b *Build) getPackagesDir() {
patterns := b.GoArgs
packages := make([]string, 0)
for _, p := range patterns {
// patterns 只支持两种格式
// 1. 要么是直接指向某些 .go 文件的相对/绝对路径
if strings.HasSuffix(p, ".go") {
if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
// check if valid
if err := goFilesPackage(patterns); err != nil {
log.Fatalf("%v", err)
}
// 获取相对于 current working directory 对路径
for _, p := range patterns {
if filepath.IsAbs(p) {
relPath, err := filepath.Rel(b.CurWd, p)
if err != nil {
log.Fatalf("fail to get [packages] relative path from current working directory: %v", err)
}
packages = append(packages, relPath)
} else {
packages = append(packages, p)
}
}
// fix: go build ./xx/main.go 需要转换为
// go build ./xx/main.go ./xx/goc-cover-agent-apis-auto-generated-11111-22222-bridge.go
dir := filepath.Dir(packages[0])
packages = append(packages, filepath.Join(dir, "goc-cover-agent-apis-auto-generated-11111-22222-bridge.go"))
b.Packages = packages
return
}
}
}
// 2. 要么是 import path
b.Packages = patterns
}
// goFilesPackage 对一组 go 文件解析,判断是否合法
// go 本身还判断语法上是否是同一个 packagegoc 这里不做解析
// 1. 都是 *.go 文件?
// 2. *.go 文件都在同一个目录?
// 3. *.go 文件存在?
func goFilesPackage(gofiles []string) error {
// 1. 必须都是 *.go 结尾
for _, f := range gofiles {
if !strings.HasSuffix(f, ".go") {
return fmt.Errorf("named files must be .go files: %s", f)
}
}
var dir string
for _, file := range gofiles {
// 3. 文件都存在?
fi, err := os.Stat(file)
if err != nil {
return err
}
// 2.1 有可能以 *.go 结尾的目录
if fi.IsDir() {
return fmt.Errorf("%s is a directory, should be a Go file", file)
}
// 2.2 所有 *.go 必须在同一个目录内
dir1, _ := filepath.Split(file)
if dir1 == "" {
dir1 = "./"
}
if dir == "" {
dir = dir1
} else if dir != dir1 {
return fmt.Errorf("named files must all be in one directory: have %s and %s", dir, dir1)
}
}
return nil
}
// getDirFromImportPaths return the import path's real abs directory
//
// 该函数接收到的只有 dir 或 import pathfile 在上一步已被排除
// 只考虑 go modules 的情况
func getDirFromImportPaths(patterns []string) (string, error) {
// no import path, pattern = current wd
if len(patterns) == 0 {
wd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("fail to parse import path: %w", err)
}
return wd, nil
}
// 为了简化插桩的逻辑goc 对 import path 要求必须都在同一个目录
// 所以干脆只允许一个 pattern 得了 -_-
// 对于 goc build/run 来说本身就是只能在一个目录内
// 对于 goc install 来讲,这个行为就和 go install 不同,不过多 import path 较少见 >_<,先忽略
if len(patterns) > 1 {
return "", fmt.Errorf("goc only support one import path now")
}
pattern := patterns[0]
switch {
// case isLocalImport(pattern) || filepath.IsAbs(pattern):
// dir1, err := filepath.Abs(pattern)
// if err != nil {
// return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
// }
// if _, err := os.Stat(dir1); err != nil {
// return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
// }
// return dir1, nil
case strings.Contains(pattern, "..."):
i := strings.Index(pattern, "...")
dir, _ := filepath.Split(pattern[:i])
dir, _ = filepath.Abs(dir)
if _, err := os.Stat(dir); err != nil {
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
}
return dir, nil
case strings.IndexByte(pattern, '@') > 0:
return "", fmt.Errorf("import path with @ version query is not supported in goc")
case isMetaPackage(pattern):
return "", fmt.Errorf("`std`, `cmd`, `all` import path is not supported by goc")
default: // 到这一步认为 pattern 是相对路径或者绝对路径
dir1, err := filepath.Abs(pattern)
if err != nil {
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
}
if _, err := os.Stat(dir1); err != nil {
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
}
return dir1, nil
}
}
// isLocalImport reports whether the import path is
// a local import path, like ".", "..", "./foo", or "../foo"
func isLocalImport(path string) bool {
return path == "." || path == ".." ||
strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
}
// isMetaPackage checks if the name is a reserved package name
func isMetaPackage(name string) bool {
return name == "std" || name == "cmd" || name == "all"
}
// find direct path of current project which contains go.mod
func findModuleRoot(dir string) string {
dir = filepath.Clean(dir)
// look for enclosing go.mod
for {
if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
return dir
}
d := filepath.Dir(dir)
if d == dir {
break
}
dir = d
}
return ""
}

49
pkg/build/config.go Normal file
View File

@ -0,0 +1,49 @@
package build
import (
"github.com/spf13/pflag"
)
type GocOption func(*Build)
func WithHost(host string) GocOption {
return func(b *Build) {
b.Host = host
}
}
func WithMode(mode string) GocOption {
return func(b *Build) {
b.Mode = mode
}
}
func WithArgs(args []string) GocOption {
return func(b *Build) {
b.Args = args
}
}
func WithFlagSets(sets *pflag.FlagSet) GocOption {
return func(b *Build) {
b.FlagSets = sets
}
}
func WithBuild() GocOption {
return func(b *Build) {
b.BuildType = 0
}
}
func WithInstall() GocOption {
return func(b *Build) {
b.BuildType = 1
}
}
func WithDebug(enable bool) GocOption {
return func(b *Build) {
b.Debug = enable
}
}

View File

@ -1,69 +1,48 @@
package config
package build
import "time"
import (
"crypto/sha256"
"fmt"
"path"
"time"
)
type gocConfig struct {
Debug bool
Host string
Mode string // cover mode
// declareCoverVars attaches the required cover variables names
// to the files, to be used when annotating the files.
func declareCoverVars(p *Package) map[string]*FileVar {
coverVars := make(map[string]*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] = &FileVar{
File: longFile,
Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
}
coverIndex++
}
GOPATH string
GOBIN string
CurWd string
TmpWd string
CurModProjectDir string
TmpModProjectDir string
for _, file := range p.CgoFiles {
// These names appear in the cmd/cover HTML interface.
var longFile = path.Join(p.ImportPath, file)
coverVars[file] = &FileVar{
File: longFile,
Var: fmt.Sprintf("GoCover_%d_%x", coverIndex, h),
}
coverIndex++
}
Goflags []string // command line flags
Packages []string // command line [packages]
ImportPath string // the whole import path of the project
Pkgs map[string]*Package
GlobalCoverVarImportPath string
GlobalCoverVarImportPathDir string
return coverVars
}
// GocConfig 全局变量,存放 goc 的各种元属性
var GocConfig gocConfig
type goConfig struct {
BuildA bool
BuildBuildmode string // -buildmode flag
BuildMod string // -mod flag
BuildModReason string // reason -mod flag is set, if set by default
BuildI bool // -i flag
BuildLinkshared bool // -linkshared flag
BuildMSan bool // -msan flag
BuildN bool // -n flag
BuildO string // -o flag
BuildP int // -p flag
BuildPkgdir string // -pkgdir flag
BuildRace bool // -race flag
BuildToolexec string // -toolexec flag
BuildToolchainName string
BuildToolchainCompiler func() string
BuildToolchainLinker func() string
BuildTrimpath bool // -trimpath flag
BuildV bool // -v flag
BuildWork bool // -work flag
BuildX bool // -x flag
// from buildcontext
Installsuffix string // -installSuffix
BuildTags string // -tags
// from load
BuildAsmflags string
BuildCompiler string
BuildGcflags string
BuildGccgoflags string
BuildLdflags string
// mod related
ModCacheRW bool
ModFile string
}
var GoConfig goConfig
// PackageCover holds all the generate coverage variables of a package
type PackageCover struct {
Package *Package

View File

@ -10,17 +10,16 @@ import (
"path/filepath"
"strings"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/log"
)
// readProjectMetaInfo reads all meta informations of the corresponding project
func (b *Build) readProjectMetaInfo() {
// get gopath & gobin
config.GocConfig.GOPATH = b.readGOPATH()
config.GocConfig.GOBIN = b.readGOBIN()
b.GOPATH = b.readGOPATH()
b.GOBIN = b.readGOBIN()
// 获取 [packages] 及其依赖的 package list
pkgs := b.listPackages(config.GocConfig.CurWd)
pkgs := b.listPackages(b.CurWd)
// get mod info
for _, pkg := range pkgs {
@ -29,34 +28,34 @@ func (b *Build) readProjectMetaInfo() {
log.Fatalf("Go module is not enabled, please set GO111MODULE=auto or on")
}
// 工程根目录
config.GocConfig.CurModProjectDir = pkg.Module.Dir
config.GocConfig.ImportPath = pkg.Module.Path
b.CurModProjectDir = pkg.Module.Dir
b.ImportPath = pkg.Module.Path
break
}
// 如果当前目录不是工程根目录,那再次 go list 一次,获取整个工程的包信息
if config.GocConfig.CurWd != config.GocConfig.CurModProjectDir {
config.GocConfig.Pkgs = b.listPackages(config.GocConfig.CurModProjectDir)
if b.CurWd != b.CurModProjectDir {
b.Pkgs = b.listPackages(b.CurModProjectDir)
} else {
config.GocConfig.Pkgs = pkgs
b.Pkgs = pkgs
}
// get tmp folder name
config.GocConfig.TmpModProjectDir = filepath.Join(os.TempDir(), TmpFolderName(config.GocConfig.CurModProjectDir))
b.TmpModProjectDir = filepath.Join(os.TempDir(), TmpFolderName(b.CurModProjectDir))
// get working dir in the corresponding tmp dir
config.GocConfig.TmpWd = filepath.Join(config.GocConfig.TmpModProjectDir, config.GocConfig.CurWd[len(config.GocConfig.CurModProjectDir):])
b.TmpWd = filepath.Join(b.TmpModProjectDir, b.CurWd[len(b.CurModProjectDir):])
// get GlobalCoverVarImportPath
config.GocConfig.GlobalCoverVarImportPath = path.Join(config.GocConfig.ImportPath, TmpFolderName(config.GocConfig.CurModProjectDir))
b.GlobalCoverVarImportPath = path.Join(b.ImportPath, TmpFolderName(b.CurModProjectDir))
log.Donef("project meta information parsed")
}
// displayProjectMetaInfo prints basic infomation of this project to stdout
func (b *Build) displayProjectMetaInfo() {
log.Infof("GOPATH: %v", config.GocConfig.GOPATH)
log.Infof("GOBIN: %v", config.GocConfig.GOBIN)
log.Infof("Project Directory: %v", config.GocConfig.CurModProjectDir)
log.Infof("Temporary Project Directory: %v", config.GocConfig.TmpModProjectDir)
log.Infof("GOPATH: %v", b.GOPATH)
log.Infof("GOBIN: %v", b.GOBIN)
log.Infof("Project Directory: %v", b.CurModProjectDir)
log.Infof("Temporary Project Directory: %v", b.TmpModProjectDir)
log.Infof("")
}
@ -79,7 +78,7 @@ func (b *Build) readGOBIN() string {
}
// listPackages list all packages under specific via go list command.
func (b *Build) listPackages(dir string) map[string]*config.Package {
func (b *Build) listPackages(dir string) map[string]*Package {
cmd := exec.Command("go", "list", "-json", "./...")
cmd.Dir = dir
@ -95,10 +94,10 @@ func (b *Build) listPackages(dir string) map[string]*config.Package {
}
dec := json.NewDecoder(bytes.NewBuffer(out))
pkgs := make(map[string]*config.Package, 0)
pkgs := make(map[string]*Package)
for {
var pkg config.Package
var pkg Package
if err := dec.Decode(&pkg); err != nil {
if err == io.EOF {
break

View File

@ -1,4 +1,4 @@
package cover
package build
import (
"fmt"
@ -6,30 +6,28 @@ import (
"path"
"path/filepath"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/cover/internal/tool"
"github.com/qiniu/goc/v2/pkg/cover/internal/websocket"
"github.com/qiniu/goc/v2/pkg/build/internal/tool"
"github.com/qiniu/goc/v2/pkg/build/internal/websocket"
"github.com/qiniu/goc/v2/pkg/log"
)
// Inject injects cover variables for all the .go files in the target directory
func Inject() {
func (b *Build) Inject() {
log.StartWait("injecting cover variables")
var seen = make(map[string]*config.PackageCover)
var seen = make(map[string]*PackageCover)
// 所有插桩变量定义声明
allDecl := ""
pkgs := config.GocConfig.Pkgs
pkgs := b.Pkgs
for _, pkg := range pkgs {
if pkg.Name == "main" {
log.Infof("handle main package: %v", pkg.ImportPath)
// 该 main 二进制所关联的所有插桩变量的元信息
// 每个 main 之间是不相关的,需要重新定义
allMainCovers := make([]*config.PackageCover, 0)
allMainCovers := make([]*PackageCover, 0)
// 注入 main package
mainCover, mainDecl := addCounters(pkg)
mainCover, mainDecl := b.addCounters(pkg)
// 收集插桩变量的定义和元信息
allDecl += mainDecl
allMainCovers = append(allMainCovers, mainCover)
@ -43,7 +41,7 @@ func Inject() {
// 依赖需要忽略 Go 标准库和 go.mod 引入的第三方
if depPkg, ok := pkgs[dep]; ok {
// 注入依赖的 package
packageCover, depDecl := addCounters(depPkg)
packageCover, depDecl := b.addCounters(depPkg)
// 收集插桩变量的定义和元信息
allDecl += depDecl
allMainCovers = append(allMainCovers, packageCover)
@ -52,20 +50,25 @@ func Inject() {
}
}
// 为每个 main 包注入 websocket handler
injectGocAgent(getPkgTmpDir(pkg.Dir), allMainCovers)
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)
}
}
}
// 在工程根目录注入所有插桩变量的声明+定义
injectGlobalCoverVarFile(allDecl)
b.injectGlobalCoverVarFile(allDecl)
// 添加自定义 websocket 依赖
// 用户代码可能有 gorrila/websocket 的依赖,为避免依赖冲突,以及可能的 replace/vendor
// 这里直接注入一份完整的 gorrila/websocket 实现
websocket.AddCustomWebsocketDep()
websocket.AddCustomWebsocketDep(b.GlobalCoverVarImportPathDir)
log.Donef("websocket library injected")
log.StopWait()
log.Donef("cover variables injected")
log.Donef("global cover variables injected")
}
// addCounters is different from official go tool cover
@ -75,18 +78,18 @@ func Inject() {
// 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
func (b *Build) addCounters(pkg *Package) (*PackageCover, string) {
mode := b.Mode
gobalCoverVarImportPath := b.GlobalCoverVarImportPath
coverVarMap := declareCoverVars(pkg)
decl := ""
for file, coverVar := range coverVarMap {
decl += "\n" + tool.Annotate(filepath.Join(getPkgTmpDir(pkg.Dir), file), mode, coverVar.Var, coverVar.File, gobalCoverVarImportPath) + "\n"
decl += "\n" + tool.Annotate(filepath.Join(b.getPkgTmpDir(pkg.Dir), file), mode, coverVar.Var, coverVar.File, gobalCoverVarImportPath) + "\n"
}
return &config.PackageCover{
return &PackageCover{
Package: pkg,
Vars: coverVarMap,
}, decl
@ -99,13 +102,13 @@ func addCounters(pkg *config.Package) (*config.PackageCover, string) {
//
// 在原工程目录已经做了一次 go list -json在临时目录没有必要再做一遍直接转换一下就能得到
// 临时目录中的 pkg.Dir。
func getPkgTmpDir(pkgDir string) string {
relDir, err := filepath.Rel(config.GocConfig.CurModProjectDir, pkgDir)
func (b *Build) getPkgTmpDir(pkgDir string) string {
relDir, err := filepath.Rel(b.CurModProjectDir, pkgDir)
if err != nil {
log.Fatalf("go json -list meta info wrong: %v", err)
}
return filepath.Join(config.GocConfig.TmpModProjectDir, relDir)
return filepath.Join(b.TmpModProjectDir, relDir)
}
// injectGocAgent inject handlers like following
@ -121,7 +124,7 @@ func getPkgTmpDir(pkgDir string) string {
//
// 11111_22222_bridge.go 仅仅用于引用 11111_22222_package, where package contains ws agent main logic.
// 使用 bridge.go 文件是为了避免插桩逻辑中的变量名污染 main 包
func injectGocAgent(where string, covers []*config.PackageCover) {
func (b *Build) injectGocAgent(where string, covers []*PackageCover) {
injectPkgName := "goc-cover-agent-apis-auto-generated-11111-22222-package"
injectBridgeName := "goc-cover-agent-apis-auto-generated-11111-22222-bridge.go"
wherePkg := filepath.Join(where, injectPkgName)
@ -159,22 +162,22 @@ func injectGocAgent(where string, covers []*config.PackageCover) {
defer f2.Close()
var _coverMode string
if config.GocConfig.Mode == "watch" {
if b.Mode == "watch" {
_coverMode = "cover"
} else {
_coverMode = config.GocConfig.Mode
_coverMode = b.Mode
}
tmplData := struct {
Covers []*config.PackageCover
Covers []*PackageCover
GlobalCoverVarImportPath string
Package string
Host string
Mode string
}{
Covers: covers,
GlobalCoverVarImportPath: config.GocConfig.GlobalCoverVarImportPath,
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
Package: injectPkgName,
Host: config.GocConfig.Host,
Host: b.Host,
Mode: _coverMode,
}
@ -183,7 +186,7 @@ func injectGocAgent(where string, covers []*config.PackageCover) {
}
// 写入 watch
if config.GocConfig.Mode != "watch" {
if b.Mode != "watch" {
return
}
f, err := os.Create(filepath.Join(wherePkg, "watchagent.go"))
@ -196,9 +199,9 @@ func injectGocAgent(where string, covers []*config.PackageCover) {
Host string
GlobalCoverVarImportPath string
}{
Random: filepath.Base(config.GocConfig.TmpModProjectDir),
Host: config.GocConfig.Host,
GlobalCoverVarImportPath: config.GocConfig.GlobalCoverVarImportPath,
Random: filepath.Base(b.TmpModProjectDir),
Host: b.Host,
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
}
if err := coverWatchTmpl.Execute(f, tmplwatchData); err != nil {
@ -207,10 +210,10 @@ func injectGocAgent(where string, covers []*config.PackageCover) {
}
// injectGlobalCoverVarFile 写入所有插桩变量的全局定义至一个单独的文件
func injectGlobalCoverVarFile(decl string) {
globalCoverVarPackage := path.Base(config.GocConfig.GlobalCoverVarImportPath)
globalCoverDef := filepath.Join(config.GocConfig.TmpModProjectDir, globalCoverVarPackage)
config.GocConfig.GlobalCoverVarImportPathDir = globalCoverDef
func (b *Build) injectGlobalCoverVarFile(decl string) {
globalCoverVarPackage := path.Base(b.GlobalCoverVarImportPath)
globalCoverDef := filepath.Join(b.TmpModProjectDir, globalCoverVarPackage)
b.GlobalCoverVarImportPathDir = globalCoverDef
err := os.MkdirAll(globalCoverDef, os.ModePerm)
if err != nil {
@ -225,7 +228,7 @@ func injectGlobalCoverVarFile(decl string) {
packageName := "package coverdef\n\n"
random := filepath.Base(config.GocConfig.TmpModProjectDir)
random := filepath.Base(b.TmpModProjectDir)
varWatchDef := fmt.Sprintf(`
var WatchChannel_%v = make(chan *blockInfo, 1024)

View File

@ -4,13 +4,11 @@ import (
"os"
"os/exec"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/cover"
"github.com/qiniu/goc/v2/pkg/log"
)
func NewInstall(args []string) *Build {
return NewBuild(args)
func NewInstall(opts ...GocOption) *Build {
return NewBuild(opts...)
}
// Install starts go install
@ -24,18 +22,21 @@ func (b *Build) Install() {
defer b.clean()
log.Donef("project copied to temporary directory")
// 2. inject cover vars
cover.Inject()
// 3. install in the temp project
// 2. update go.mod file if needed
b.updateGoModFile()
// 3. inject cover vars
b.Inject()
// 4. install in the temp project
b.doInstallInTemp()
}
func (b *Build) doInstallInTemp() {
log.StartWait("installing the injected project")
goflags := config.GocConfig.Goflags
goflags := b.Goflags
pacakges := config.GocConfig.Packages
pacakges := b.Packages
goflags = append(goflags, pacakges...)
@ -43,7 +44,7 @@ func (b *Build) doInstallInTemp() {
args = append(args, goflags...)
// go 命令行由 go install [build flags] [packages] 组成
cmd := exec.Command("go", args...)
cmd.Dir = config.GocConfig.TmpWd
cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

View File

@ -8,7 +8,6 @@ import (
"os"
"path/filepath"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/log"
)
@ -19,7 +18,7 @@ var depTarFile embed.FS
//
// 从 embed 文件系统中解压 websocket.tar 文件,并依次写入临时工程中,作为一个单独的包存在。
// gorrila/websocket 是一个无第三方依赖的库,因此其位置可以随处移动,而不影响自身的编译。
func AddCustomWebsocketDep() {
func AddCustomWebsocketDep(customWebsocketPath string) {
data, err := depTarFile.ReadFile("websocket.tar")
if err != nil {
log.Fatalf("cannot find the websocket.tar in the embed file: %v", err)
@ -36,7 +35,6 @@ func AddCustomWebsocketDep() {
log.Fatalf("cannot untar the websocket.tar: %v", err)
}
customWebsocketPath := config.GocConfig.GlobalCoverVarImportPathDir
fpath := filepath.Join(customWebsocketPath, hdr.Name)
if hdr.FileInfo().IsDir() {
// 处理目录

View File

@ -1,4 +1,4 @@
package cover
package build
import (
_ "embed"

View File

@ -8,7 +8,6 @@ import (
"path/filepath"
"strings"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/log"
"github.com/tongjingran/copy"
"golang.org/x/mod/modfile"
@ -18,8 +17,8 @@ import (
//
// It will ignore .git and irregular files, only copy source(text) files
func (b *Build) copyProjectToTmp() {
curProject := config.GocConfig.CurModProjectDir
tmpProject := config.GocConfig.TmpModProjectDir
curProject := b.CurModProjectDir
tmpProject := b.TmpModProjectDir
if _, err := os.Stat(tmpProject); !os.IsNotExist(err) {
log.Infof("find previous temporary directory, delete")
@ -67,8 +66,8 @@ func skipCopy(src string, info os.FileInfo) (bool, error) {
// clean clears the temporary project
func (b *Build) clean() {
if config.GocConfig.Debug != true {
if err := os.RemoveAll(config.GocConfig.TmpModProjectDir); err != nil {
if !b.Debug {
if err := os.RemoveAll(b.TmpModProjectDir); err != nil {
log.Fatalf("fail to delete the temporary project: %v", err)
}
log.Donef("delete the temporary project")
@ -90,7 +89,7 @@ func (b *Build) clean() {
// after the project is copied to temporary directory, it should be rewritten as
// 'replace github.com/qiniu/bar => /path/to/aa/bb/home/foo/bar'
func (b *Build) updateGoModFile() (updateFlag bool, newModFile []byte) {
tempModfile := filepath.Join(config.GocConfig.TmpModProjectDir, "go.mod")
tempModfile := filepath.Join(b.TmpModProjectDir, "go.mod")
buf, err := ioutil.ReadFile(tempModfile)
if err != nil {
log.Fatalf("cannot find go.mod file in temporary directory: %v", err)
@ -111,7 +110,7 @@ func (b *Build) updateGoModFile() (updateFlag bool, newModFile []byte) {
// absolute path no need to rewrite
if newVersion == "" && !filepath.IsAbs(newPath) {
var absPath string
fullPath := filepath.Join(config.GocConfig.CurModProjectDir, newPath)
fullPath := filepath.Join(b.CurModProjectDir, newPath)
absPath, _ = filepath.Abs(fullPath)
// DropReplace & AddReplace will not return error
// so no need to check the error
@ -126,5 +125,13 @@ func (b *Build) updateGoModFile() (updateFlag bool, newModFile []byte) {
// return Format(f.Syntax), nil
// }
newModFile, _ = oriGoModFile.Format()
if updateFlag {
log.Infof("go.mod needs rewrite")
err := os.WriteFile(tempModfile, newModFile, os.ModePerm)
if err != nil {
log.Fatalf("fail to update go.mod: %v", err)
}
}
return
}

View File

@ -1,45 +0,0 @@
package cover
import (
"crypto/sha256"
"fmt"
"path"
"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,146 +0,0 @@
package flag
import (
"flag"
"os"
"path/filepath"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/log"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var buildUsage string = `Usage:
goc build [-o output] [build flags] [packages] [goc flags]
[build flags] are same with go official command, you can copy them here directly.
The [goc flags] can be placed in anywhere in the command line.
However, other flags' order are same with the go official command.
`
var installUsage string = `Usage:
goc install [-o output] [build flags] [packages] [goc flags]
[build flags] are same with go official command, you can copy them here directly.
The [goc flags] can be placed in anywhere in the command line.
However, other flags' order are same with the go official command.
`
const (
GO_BUILD = iota
GO_INSTALL
)
// BuildCmdArgsParse parse both go flags and goc flags, it rewrite go flags if
// necessary, and returns all non-flag arguments.
//
// 吞下 [packages] 之前所有的 flags.
func BuildCmdArgsParse(cmd *cobra.Command, args []string, cmdType int) []string {
// 首先解析 cobra 定义的 flag
allFlagSets := cmd.Flags()
// 因为 args 里面含有 go 的 flag所以需要忽略解析 go flag 的错误
allFlagSets.Init("GOC", pflag.ContinueOnError)
// 忽略 go flag 在 goc 中的解析错误
allFlagSets.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{
UnknownFlags: true,
}
allFlagSets.Parse(args)
// 重写 help
helpFlag := allFlagSets.Lookup("help")
if helpFlag.Changed {
if cmdType == GO_BUILD {
printHelp(buildUsage, cmd)
} else if cmdType == GO_INSTALL {
printHelp(installUsage, cmd)
}
os.Exit(0)
}
// 删除 help flag
args = findAndDelHelpFlag(args)
// 必须手动调用
// 由于关闭了 cobra 的 flag parseroot PersistentPreRun 调用时log.NewLogger 并没有拿到 debug 值
log.NewLogger()
// 删除 cobra 定义的 flag
allFlagSets.Visit(func(f *pflag.Flag) {
args = findAndDelGocFlag(args, f.Name, f.Value.String())
})
// 然后解析 go 的 flag
goFlagSets := flag.NewFlagSet("GO", flag.ContinueOnError)
addBuildFlags(goFlagSets)
addOutputFlags(goFlagSets)
err := goFlagSets.Parse(args)
if err != nil {
log.Fatalf("%v", err)
}
// 找出设置的 go flag
curWd, err := os.Getwd()
if err != nil {
log.Fatalf("fail to get current working directory: %v", err)
}
config.GocConfig.CurWd = curWd
flags := make([]string, 0)
goFlagSets.Visit(func(f *flag.Flag) {
// 将用户指定 -o 改成绝对目录
if f.Name == "o" {
outputDir := f.Value.String()
outputDir, err := filepath.Abs(outputDir)
if err != nil {
log.Fatalf("output flag is not valid: %v", err)
}
flags = append(flags, "-o", outputDir)
} else {
flags = append(flags, "-"+f.Name, f.Value.String())
}
})
config.GocConfig.Goflags = flags
return goFlagSets.Args()
}
func findAndDelGocFlag(a []string, x string, v string) []string {
new := make([]string, 0, len(a))
x = "--" + x
x_v := x + "=" + v
for i := 0; i < len(a); i++ {
if a[i] == "--gocdebug" {
// debug 是 bool就一个元素
continue
} else if a[i] == x {
// 有 goc flag 长这样 --mode watch
i++
continue
} else if a[i] == x_v {
// 有 goc flag 长这样 --mode=watch
continue
} else {
// 剩下的是 go flag
new = append(new, a[i])
}
}
return new
}
func findAndDelHelpFlag(a []string) []string {
new := make([]string, 0, len(a))
for _, v := range a {
if v == "--help" || v == "-h" {
continue
} else {
new = append(new, v)
}
}
return new
}

View File

@ -1,40 +0,0 @@
package flag
import (
"flag"
"github.com/qiniu/goc/v2/pkg/config"
)
func addBuildFlags(cmdSet *flag.FlagSet) {
cmdSet.BoolVar(&config.GoConfig.BuildA, "a", false, "")
cmdSet.BoolVar(&config.GoConfig.BuildN, "n", false, "")
cmdSet.IntVar(&config.GoConfig.BuildP, "p", 4, "")
cmdSet.BoolVar(&config.GoConfig.BuildV, "v", false, "")
cmdSet.BoolVar(&config.GoConfig.BuildX, "x", false, "")
cmdSet.StringVar(&config.GoConfig.BuildBuildmode, "buildmode", "default", "")
cmdSet.StringVar(&config.GoConfig.BuildMod, "mod", "", "")
cmdSet.StringVar(&config.GoConfig.Installsuffix, "installsuffix", "", "")
// 类型和 go 原生的不一样,这里纯粹是为了 parse 并传递给 go
cmdSet.StringVar(&config.GoConfig.BuildAsmflags, "asmflags", "", "")
cmdSet.StringVar(&config.GoConfig.BuildCompiler, "compiler", "", "")
cmdSet.StringVar(&config.GoConfig.BuildGcflags, "gcflags", "", "")
cmdSet.StringVar(&config.GoConfig.BuildGccgoflags, "gccgoflags", "", "")
// mod related
cmdSet.BoolVar(&config.GoConfig.ModCacheRW, "modcacherw", false, "")
cmdSet.StringVar(&config.GoConfig.ModFile, "modfile", "", "")
cmdSet.StringVar(&config.GoConfig.BuildLdflags, "ldflags", "", "")
cmdSet.BoolVar(&config.GoConfig.BuildLinkshared, "linkshared", false, "")
cmdSet.StringVar(&config.GoConfig.BuildPkgdir, "pkgdir", "", "")
cmdSet.BoolVar(&config.GoConfig.BuildRace, "race", false, "")
cmdSet.BoolVar(&config.GoConfig.BuildMSan, "msan", false, "")
cmdSet.StringVar(&config.GoConfig.BuildTags, "tags", "", "")
cmdSet.StringVar(&config.GoConfig.BuildToolexec, "toolexec", "", "")
cmdSet.BoolVar(&config.GoConfig.BuildTrimpath, "trimpath", false, "")
cmdSet.BoolVar(&config.GoConfig.BuildWork, "work", false, "")
}
func addOutputFlags(cmdSet *flag.FlagSet) {
cmdSet.StringVar(&config.GoConfig.BuildO, "o", "", "")
}

View File

@ -1,20 +0,0 @@
package flag
import (
"fmt"
"github.com/spf13/cobra"
)
func printHelp(usage string, cmd *cobra.Command) {
fmt.Println(usage)
flags := cmd.LocalFlags()
globalFlags := cmd.Parent().PersistentFlags()
fmt.Println("Flags:")
fmt.Println(flags.FlagUsages())
fmt.Println("Global Flags:")
fmt.Println(globalFlags.FlagUsages())
}

View File

@ -1,193 +0,0 @@
package flag
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/log"
)
// GetPackagesDir parse [pacakges] part of args, it will fatal if error encountered
//
// 函数获取 1 [packages] 所在的目录位置,供后续插桩使用。
//
// 函数获取 2 如果参数是 *.go第一个 .go 文件的文件名。go build 中,二进制名字既可能是目录名也可能是文件名,和参数类型有关。
//
// 如果 [packages] 非法(即不符合 go 原生的定义),则返回对应错误
// 这里只考虑 go mod 的方式
func GetPackagesDir(patterns []string) {
packages := make([]string, 0)
for _, p := range patterns {
// patterns 只支持两种格式
// 1. 要么是直接指向某些 .go 文件的相对/绝对路径
if strings.HasSuffix(p, ".go") {
if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
// check if valid
if err := goFilesPackage(patterns); err != nil {
log.Fatalf("%v", err)
}
// 获取相对于 current working directory 对路径
for _, p := range patterns {
if filepath.IsAbs(p) {
relPath, err := filepath.Rel(config.GocConfig.CurWd, p)
if err != nil {
log.Fatalf("fail to get [packages] relative path from current working directory: %v", err)
}
packages = append(packages, relPath)
} else {
packages = append(packages, p)
}
}
// fix: go build ./xx/main.go 需要转换为
// go build ./xx/main.go ./xx/goc-cover-agent-apis-auto-generated-11111-22222-bridge.go
dir := filepath.Dir(packages[0])
packages = append(packages, filepath.Join(dir, "goc-cover-agent-apis-auto-generated-11111-22222-bridge.go"))
config.GocConfig.Packages = packages
return
}
}
}
// 2. 要么是 import path
config.GocConfig.Packages = patterns
}
// goFilesPackage 对一组 go 文件解析,判断是否合法
// go 本身还判断语法上是否是同一个 packagegoc 这里不做解析
// 1. 都是 *.go 文件?
// 2. *.go 文件都在同一个目录?
// 3. *.go 文件存在?
func goFilesPackage(gofiles []string) error {
// 1. 必须都是 *.go 结尾
for _, f := range gofiles {
if !strings.HasSuffix(f, ".go") {
return fmt.Errorf("named files must be .go files: %s", f)
}
}
var dir string
for _, file := range gofiles {
// 3. 文件都存在?
fi, err := os.Stat(file)
if err != nil {
return err
}
// 2.1 有可能以 *.go 结尾的目录
if fi.IsDir() {
return fmt.Errorf("%s is a directory, should be a Go file", file)
}
// 2.2 所有 *.go 必须在同一个目录内
dir1, _ := filepath.Split(file)
if dir1 == "" {
dir1 = "./"
}
if dir == "" {
dir = dir1
} else if dir != dir1 {
return fmt.Errorf("named files must all be in one directory: have %s and %s", dir, dir1)
}
}
return nil
}
// getDirFromImportPaths return the import path's real abs directory
//
// 该函数接收到的只有 dir 或 import pathfile 在上一步已被排除
// 只考虑 go modules 的情况
func getDirFromImportPaths(patterns []string) (string, error) {
// no import path, pattern = current wd
if len(patterns) == 0 {
wd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("fail to parse import path: %w", err)
}
return wd, nil
}
// 为了简化插桩的逻辑goc 对 import path 要求必须都在同一个目录
// 所以干脆只允许一个 pattern 得了 -_-
// 对于 goc build/run 来说本身就是只能在一个目录内
// 对于 goc install 来讲,这个行为就和 go install 不同,不过多 import path 较少见 >_<,先忽略
if len(patterns) > 1 {
return "", fmt.Errorf("goc only support one import path now")
}
pattern := patterns[0]
switch {
// case isLocalImport(pattern) || filepath.IsAbs(pattern):
// dir1, err := filepath.Abs(pattern)
// if err != nil {
// return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
// }
// if _, err := os.Stat(dir1); err != nil {
// return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
// }
// return dir1, nil
case strings.Contains(pattern, "..."):
i := strings.Index(pattern, "...")
dir, _ := filepath.Split(pattern[:i])
dir, _ = filepath.Abs(dir)
if _, err := os.Stat(dir); err != nil {
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
}
return dir, nil
case strings.IndexByte(pattern, '@') > 0:
return "", fmt.Errorf("import path with @ version query is not supported in goc")
case isMetaPackage(pattern):
return "", fmt.Errorf("`std`, `cmd`, `all` import path is not supported by goc")
default: // 到这一步认为 pattern 是相对路径或者绝对路径
dir1, err := filepath.Abs(pattern)
if err != nil {
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
}
if _, err := os.Stat(dir1); err != nil {
return "", fmt.Errorf("error (%w) get directory from the import path: %v", err, pattern)
}
return dir1, nil
}
}
// isLocalImport reports whether the import path is
// a local import path, like ".", "..", "./foo", or "../foo"
func isLocalImport(path string) bool {
return path == "." || path == ".." ||
strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
}
// isMetaPackage checks if the name is a reserved package name
func isMetaPackage(name string) bool {
return name == "std" || name == "cmd" || name == "all"
}
// find direct path of current project which contains go.mod
func findModuleRoot(dir string) string {
dir = filepath.Clean(dir)
// look for enclosing go.mod
for {
if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
return dir
}
d := filepath.Dir(dir)
if d == dir {
break
}
dir = d
}
return ""
}

View File

@ -1,14 +1,13 @@
package log
import (
"github.com/qiniu/goc/v2/pkg/config"
"go.uber.org/zap/zapcore"
)
var g Logger
func NewLogger() {
if config.GocConfig.Debug == true {
func NewLogger(debug bool) {
if debug == true {
g = newCiLogger()
} else {
g = &terminalLogger{

View File

@ -4,12 +4,11 @@ import (
"fmt"
"github.com/gorilla/websocket"
"github.com/qiniu/goc/v2/pkg/config"
"github.com/qiniu/goc/v2/pkg/log"
)
func Watch() {
watchUrl := fmt.Sprintf("ws://%v/v2/cover/ws/watch", config.GocConfig.Host)
func Watch(host string) {
watchUrl := fmt.Sprintf("ws://%v/v2/cover/ws/watch", host)
c, _, err := websocket.DefaultDialer.Dial(watchUrl, nil)
if err != nil {
log.Fatalf("cannot connect to goc server: %v", err)