refactor: go mod v2

This commit is contained in:
liruichen_wsl 2024-03-06 16:49:35 +08:00
parent 71d79c55db
commit 293cf38971
31 changed files with 2666 additions and 2625 deletions

View File

@ -14,40 +14,40 @@
package cmd
import (
"github.com/RickLeee/goc-v2/pkg/build"
"github.com/spf13/cobra"
"github.com/RickLeee/goc/v2/pkg/build"
"github.com/spf13/cobra"
)
var buildCmd = &cobra.Command{
Use: "build",
Run: buildAction,
Use: "build",
Run: buildAction,
DisableFlagParsing: true, // build 命令需要用原生 go 的方式处理 flags
DisableFlagParsing: true, // build 命令需要用原生 go 的方式处理 flags
}
var (
gocmode string
gochost string
gocmode string
gochost string
)
func init() {
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)
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) {
sets := build.CustomParseCmdAndArgs(cmd, args)
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()
b := build.NewBuild(
build.WithHost(gochost),
build.WithMode(gocmode),
build.WithFlagSets(sets),
build.WithArgs(args),
build.WithBuild(),
build.WithDebug(globalDebug),
)
b.Build()
}

View File

@ -14,35 +14,35 @@
package cmd
import (
"github.com/RickLeee/goc-v2/pkg/build"
"github.com/spf13/cobra"
"github.com/RickLeee/goc/v2/pkg/build"
"github.com/spf13/cobra"
)
var installCmd = &cobra.Command{
Use: "install",
Run: installAction,
Use: "install",
Run: installAction,
DisableFlagParsing: true, // install 命令需要用原生 go 的方式处理 flags
DisableFlagParsing: true, // install 命令需要用原生 go 的方式处理 flags
}
func init() {
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)
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) {
sets := build.CustomParseCmdAndArgs(cmd, args)
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()
b := build.NewInstall(
build.WithHost(gochost),
build.WithMode(gocmode),
build.WithFlagSets(sets),
build.WithArgs(args),
build.WithInstall(),
build.WithDebug(globalDebug),
)
b.Install()
}

View File

@ -14,55 +14,55 @@
package cmd
import (
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/spf13/cobra"
"golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov"
"k8s.io/test-infra/gopherage/pkg/util"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/spf13/cobra"
"golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov"
"k8s.io/test-infra/gopherage/pkg/util"
)
var mergeCmd = &cobra.Command{
Use: "merge [files...]",
Short: "Merge multiple coherent Go coverage files into a single file.",
Long: `Merge will merge multiple Go coverage files into a single coverage file.
Use: "merge [files...]",
Short: "Merge multiple coherent Go coverage files into a single file.",
Long: `Merge will merge multiple Go coverage files into a single coverage file.
merge requires that the files are 'coherent', meaning that if they both contain references to the
same paths, then the contents of those source files were identical for the binary that generated
each file.
`,
Run: func(cmd *cobra.Command, args []string) {
runMerge(args, outputMergeProfile)
},
Run: func(cmd *cobra.Command, args []string) {
runMerge(args, outputMergeProfile)
},
}
var outputMergeProfile string
func init() {
mergeCmd.Flags().StringVarP(&outputMergeProfile, "output", "o", "mergeprofile.cov", "output file")
mergeCmd.Flags().StringVarP(&outputMergeProfile, "output", "o", "mergeprofile.cov", "output file")
rootCmd.AddCommand(mergeCmd)
rootCmd.AddCommand(mergeCmd)
}
func runMerge(args []string, output string) {
if len(args) == 0 {
log.Fatalf("Expected at least one coverage file.")
}
if len(args) == 0 {
log.Fatalf("Expected at least one coverage file.")
}
profiles := make([][]*cover.Profile, len(args))
for _, path := range args {
profile, err := util.LoadProfile(path)
if err != nil {
log.Fatalf("failed to open %s: %v", path, err)
}
profiles = append(profiles, profile)
}
profiles := make([][]*cover.Profile, len(args))
for _, path := range args {
profile, err := util.LoadProfile(path)
if err != nil {
log.Fatalf("failed to open %s: %v", path, err)
}
profiles = append(profiles, profile)
}
merged, err := cov.MergeMultipleProfiles(profiles)
if err != nil {
log.Fatalf("failed to merge files: %v", err)
}
merged, err := cov.MergeMultipleProfiles(profiles)
if err != nil {
log.Fatalf("failed to merge files: %v", err)
}
err = util.DumpProfile(output, merged)
if err != nil {
log.Fatalf("fail to dump the merged file: %v", err)
}
err = util.DumpProfile(output, merged)
if err != nil {
log.Fatalf("fail to dump the merged file: %v", err)
}
}

View File

@ -14,65 +14,65 @@
package cmd
import (
"github.com/RickLeee/goc-v2/pkg/client"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/RickLeee/goc/v2/pkg/client"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var profileCmd = &cobra.Command{
Use: "profile",
Short: "Get coverage profile from service registry center",
Long: `Get code coverage profile for the services under test at runtime.`,
//Run: profile,
Use: "profile",
Short: "Get coverage profile from service registry center",
Long: `Get code coverage profile for the services under test at runtime.`,
//Run: profile,
}
var (
profileHost string
profileOutput string // --output flag
profileIds []string
profileSkipPattern []string
profileExtra string
profileNeedPattern []string
profileHost string
profileOutput string // --output flag
profileIds []string
profileSkipPattern []string
profileExtra string
profileNeedPattern []string
)
func init() {
add1Flags := func(f *pflag.FlagSet) {
f.StringVar(&profileHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
f.StringSliceVar(&profileIds, "id", nil, "specify the ids of the services")
f.StringVar(&profileExtra, "extra", "", "specify the regex expression of extra, only profile with extra information will be downloaded")
}
add1Flags := func(f *pflag.FlagSet) {
f.StringVar(&profileHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
f.StringSliceVar(&profileIds, "id", nil, "specify the ids of the services")
f.StringVar(&profileExtra, "extra", "", "specify the regex expression of extra, only profile with extra information will be downloaded")
}
add2Flags := func(f *pflag.FlagSet) {
f.StringVarP(&profileOutput, "output", "o", "", "download cover profile")
f.StringSliceVar(&profileSkipPattern, "skip", nil, "skip specific packages in the profile")
f.StringSliceVarP(&profileNeedPattern, "need", "n", nil, "find specific packages in the profile")
}
add2Flags := func(f *pflag.FlagSet) {
f.StringVarP(&profileOutput, "output", "o", "", "download cover profile")
f.StringSliceVar(&profileSkipPattern, "skip", nil, "skip specific packages in the profile")
f.StringSliceVarP(&profileNeedPattern, "need", "n", nil, "find specific packages in the profile")
}
add1Flags(getProfileCmd.Flags())
add2Flags(getProfileCmd.Flags())
add1Flags(getProfileCmd.Flags())
add2Flags(getProfileCmd.Flags())
add1Flags(clearProfileCmd.Flags())
add1Flags(clearProfileCmd.Flags())
profileCmd.AddCommand(getProfileCmd)
profileCmd.AddCommand(clearProfileCmd)
rootCmd.AddCommand(profileCmd)
profileCmd.AddCommand(getProfileCmd)
profileCmd.AddCommand(clearProfileCmd)
rootCmd.AddCommand(profileCmd)
}
var getProfileCmd = &cobra.Command{
Use: "get",
Run: getProfile,
Use: "get",
Run: getProfile,
}
func getProfile(cmd *cobra.Command, args []string) {
client.GetProfile(profileHost, profileIds, profileSkipPattern, profileExtra, profileOutput, profileNeedPattern)
client.GetProfile(profileHost, profileIds, profileSkipPattern, profileExtra, profileOutput, profileNeedPattern)
}
var clearProfileCmd = &cobra.Command{
Use: "clear",
Run: clearProfile,
Use: "clear",
Run: clearProfile,
}
func clearProfile(cmd *cobra.Command, args []string) {
client.ClearProfile(profileHost, profileIds, profileExtra)
client.ClearProfile(profileHost, profileIds, profileExtra)
}

View File

@ -14,38 +14,38 @@
package cmd
import (
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/spf13/cobra"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "goc",
Short: "goc is a comprehensive coverage testing tool for go language",
Long: `goc is a comprehensive coverage testing tool for go language.
Use: "goc",
Short: "goc is a comprehensive coverage testing tool for go language",
Long: `goc is a comprehensive coverage testing tool for go language.
Find more information at:
https://github.com/qiniu/goc
`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
//log.DisplayGoc()
// init logger
log.NewLogger(globalDebug)
PersistentPreRun: func(cmd *cobra.Command, args []string) {
//log.DisplayGoc()
// init logger
log.NewLogger(globalDebug)
},
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
log.Sync()
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
log.Sync()
},
}
var globalDebug bool
func init() {
rootCmd.PersistentFlags().BoolVar(&globalDebug, "gocdebug", false, "run goc in debug mode")
rootCmd.PersistentFlags().BoolVar(&globalDebug, "gocdebug", false, "run goc in debug mode")
}
// Execute the goc tool
func Execute() {
if err := rootCmd.Execute(); err != nil {
}
if err := rootCmd.Execute(); err != nil {
}
}

View File

@ -14,34 +14,34 @@
package cmd
import (
"github.com/RickLeee/goc-v2/pkg/build"
"github.com/spf13/cobra"
"github.com/RickLeee/goc/v2/pkg/build"
"github.com/spf13/cobra"
)
var runCmd = &cobra.Command{
Use: "run",
Run: runAction,
Use: "run",
Run: runAction,
DisableFlagParsing: true, // run 命令需要用原生 go 的方式处理 flags
DisableFlagParsing: true, // run 命令需要用原生 go 的方式处理 flags
}
func init() {
runCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch")
runCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever")
rootCmd.AddCommand(runCmd)
runCmd.Flags().StringVarP(&gocmode, "gocmode", "", "count", "coverage mode: set, count, atomic, watch")
runCmd.Flags().StringVarP(&gochost, "gochost", "", "127.0.0.1:7777", "specify the host of the goc sever")
rootCmd.AddCommand(runCmd)
}
func runAction(cmd *cobra.Command, args []string) {
sets := build.CustomParseCmdAndArgs(cmd, args)
sets := build.CustomParseCmdAndArgs(cmd, args)
b := build.NewRun(
build.WithHost(gochost),
build.WithMode(gocmode),
build.WithFlagSets(sets),
build.WithArgs(args),
build.WithBuild(),
build.WithDebug(globalDebug),
)
b.Run()
b := build.NewRun(
build.WithHost(gochost),
build.WithMode(gocmode),
build.WithFlagSets(sets),
build.WithArgs(args),
build.WithBuild(),
build.WithDebug(globalDebug),
)
b.Run()
}

View File

@ -14,36 +14,36 @@
package cmd
import (
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/RickLeee/goc-v2/pkg/server"
"github.com/RickLeee/goc-v2/pkg/server/store"
"github.com/spf13/cobra"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/server"
"github.com/RickLeee/goc/v2/pkg/server/store"
"github.com/spf13/cobra"
)
var serverCmd = &cobra.Command{
Use: "server",
Short: "Start a service registry center",
Example: "",
Use: "server",
Short: "Start a service registry center",
Example: "",
Run: serve,
Run: serve,
}
var (
serverHost string
serverStore string
serverHost string
serverStore string
)
func init() {
serverCmd.Flags().StringVarP(&serverHost, "host", "", "127.0.0.1:7777", "specify the host of the goc server")
serverCmd.Flags().StringVarP(&serverStore, "store", "", ".goc.kvstore", "specify the host of the goc server")
serverCmd.Flags().StringVarP(&serverHost, "host", "", "127.0.0.1:7777", "specify the host of the goc server")
serverCmd.Flags().StringVarP(&serverStore, "store", "", ".goc.kvstore", "specify the host of the goc server")
rootCmd.AddCommand(serverCmd)
rootCmd.AddCommand(serverCmd)
}
func serve(cmd *cobra.Command, args []string) {
s, err := store.NewFileStore(serverStore)
if err != nil {
log.Fatalf("cannot create store for goc server: %v", err)
}
server.RunGocServerUntilExit(serverHost, s)
s, err := store.NewFileStore(serverStore)
if err != nil {
log.Fatalf("cannot create store for goc server: %v", err)
}
server.RunGocServerUntilExit(serverHost, s)
}

View File

@ -14,61 +14,61 @@
package cmd
import (
"github.com/RickLeee/goc-v2/pkg/client"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/RickLeee/goc/v2/pkg/client"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var listCmd = &cobra.Command{
Use: "service",
Short: "Deal with the registered services",
Long: `It can be used to list, remove the registered services.
Use: "service",
Short: "Deal with the registered services",
Long: `It can be used to list, remove the registered services.
For disconnected services, remove will delete these serivces forever,
for connected services remove will force these services register again.`,
}
var (
listHost string
listWide bool
listIds []string
listJson bool
listHost string
listWide bool
listIds []string
listJson bool
)
func init() {
add1Flags := func(f *pflag.FlagSet) {
f.StringVar(&listHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
f.BoolVar(&listWide, "wide", false, "list all services with more information (such as pid)")
f.BoolVar(&listJson, "json", false, "list all services info as json format")
f.StringSliceVar(&listIds, "id", nil, "specify the ids of the services")
}
add1Flags := func(f *pflag.FlagSet) {
f.StringVar(&listHost, "host", "127.0.0.1:7777", "specify the host of the goc server")
f.BoolVar(&listWide, "wide", false, "list all services with more information (such as pid)")
f.BoolVar(&listJson, "json", false, "list all services info as json format")
f.StringSliceVar(&listIds, "id", nil, "specify the ids of the services")
}
add1Flags(getServiceCmd.Flags())
add1Flags(deleteServiceCmd.Flags())
add1Flags(getServiceCmd.Flags())
add1Flags(deleteServiceCmd.Flags())
listCmd.AddCommand(getServiceCmd)
listCmd.AddCommand(deleteServiceCmd)
rootCmd.AddCommand(listCmd)
listCmd.AddCommand(getServiceCmd)
listCmd.AddCommand(deleteServiceCmd)
rootCmd.AddCommand(listCmd)
}
func list(cmd *cobra.Command, args []string) {
client.ListAgents(listHost, listIds, listWide, listJson)
client.ListAgents(listHost, listIds, listWide, listJson)
}
var getServiceCmd = &cobra.Command{
Use: "get",
Run: getAgents,
Use: "get",
Run: getAgents,
}
func getAgents(cmd *cobra.Command, args []string) {
client.ListAgents(listHost, listIds, listWide, listJson)
client.ListAgents(listHost, listIds, listWide, listJson)
}
var deleteServiceCmd = &cobra.Command{
Use: "delete",
Run: deleteAgents,
Use: "delete",
Run: deleteAgents,
}
func deleteAgents(cmd *cobra.Command, args []string) {
client.DeleteAgents(listHost, listIds)
client.DeleteAgents(listHost, listIds)
}

View File

@ -14,28 +14,28 @@
package cmd
import (
cli "github.com/RickLeee/goc-v2/pkg/watch"
"github.com/spf13/cobra"
cli "github.com/RickLeee/goc/v2/pkg/watch"
"github.com/spf13/cobra"
)
var watchCmd = &cobra.Command{
Use: "watch",
Short: "watch for profile's real time update",
Long: "watch for profile's real time update",
Example: "",
Use: "watch",
Short: "watch for profile's real time update",
Long: "watch for profile's real time update",
Example: "",
Run: watch,
Run: watch,
}
var (
watchHost string
watchHost string
)
func init() {
watchCmd.Flags().StringVarP(&watchHost, "host", "", "127.0.0.1:7777", "specify the host of the goc server")
rootCmd.AddCommand(watchCmd)
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(watchHost)
cli.Watch(watchHost)
}

57
go.mod
View File

@ -1,6 +1,6 @@
module github.com/RickLeee/goc-v2
module github.com/RickLeee/goc/v2
go 1.16
go 1.20
require (
github.com/gin-gonic/gin v1.7.2
@ -8,8 +8,6 @@ require (
github.com/gofrs/flock v0.8.1
github.com/gorilla/websocket v1.4.2
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/ginkgo v1.16.4
@ -20,7 +18,6 @@ require (
github.com/tongjingran/copy v1.4.2
go.uber.org/zap v1.17.0
golang.org/x/mod v0.4.2
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d
golang.org/x/tools v0.1.3
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
@ -28,4 +25,54 @@ require (
k8s.io/test-infra v0.0.0-20210618100605-34aa2f2aa75b
)
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/gofuzz v1.2.1-0.20210504230335-f78f29fc09ea // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.26.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.21.2 // indirect
k8s.io/apimachinery v0.21.2 // indirect
k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible // indirect
k8s.io/klog/v2 v2.8.0 // indirect
k8s.io/utils v0.0.0-20210527160623-6fdb442a123b // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.0 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)
replace k8s.io/client-go => k8s.io/client-go v0.21.1

6
go.sum
View File

@ -1195,7 +1195,6 @@ github.com/trivago/tgo v1.0.1/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkF
github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible/go.mod h1:Smz/ZWfhKRcyDDChZkG3CyTHdj87lHzio/HOCkbndXM=
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
@ -1236,7 +1235,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
@ -1442,7 +1440,6 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1526,7 +1523,6 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1956,7 +1952,6 @@ k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
@ -2047,7 +2042,6 @@ sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4
sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
sigs.k8s.io/structured-merge-diff v1.0.1 h1:LOs1LZWMsz1xs77Phr/pkB4LFaavH7IVq/3+WTN9XTA=
sigs.k8s.io/structured-merge-diff v1.0.1/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=

View File

@ -10,7 +10,7 @@ RELEASE_VERSION=$(echo $EVENT_DATA | jq -r .release.tag_name)
PROJECT_NAME=$(basename $GITHUB_REPOSITORY)
NAME="${NAME:-${PROJECT_NAME}-${RELEASE_VERSION}}-${GOOS}-${GOARCH}"
CGO_ENABLED=0 go build -o goc -ldflags "-X 'github.com/RickLeee/goc-v2/cmd.Version=${RELEASE_VERSION}'" .
CGO_ENABLED=0 go build -o goc -ldflags "-X 'github.com/RickLeee/goc/v2/cmd.Version=${RELEASE_VERSION}'" .
ARCHIVE=tmp.tar.gz
FILE_LIST=goc

View File

@ -14,9 +14,9 @@
package main
import (
"github.com/RickLeee/goc-v2/cmd"
"github.com/RickLeee/goc/v2/cmd"
)
func main() {
cmd.Execute()
cmd.Execute()
}

View File

@ -14,62 +14,62 @@
package build
import (
"os"
"os/exec"
"strings"
"os"
"os/exec"
"strings"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/spf13/pflag"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/spf13/pflag"
)
// Build struct a build
type Build struct {
Args []string // all goc + go command line args + flags
FlagSets *pflag.FlagSet
BuildType int
Args []string // all goc + go command line args + flags
FlagSets *pflag.FlagSet
BuildType int
Debug bool
Host string
Mode string // cover mode
Debug bool
Host string
Mode string // cover mode
GOPATH string
GOBIN string
CurWd string
TmpWd string
CurModProjectDir string
TmpModProjectDir string
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]
Goflags []string // go command line flags
GoArgs []string // go command line args
Packages []string // go command line [packages]
IsVendorMod bool // vendor, readonly, or mod?
IsModEdit bool // is mod file edited?
IsVendorMod bool // vendor, readonly, or mod?
IsModEdit bool // is mod file edited?
ImportPath string // the whole import path of the project
Pkgs map[string]*Package
GlobalCoverVarImportPath string
GlobalCoverVarImportPathDir string
ImportPath string // the whole import path of the project
Pkgs map[string]*Package
GlobalCoverVarImportPath string
GlobalCoverVarImportPathDir string
}
// NewBuild creates a Build struct
func NewBuild(opts ...gocOption) *Build {
b := &Build{}
b := &Build{}
for _, opt := range opts {
opt(b)
}
for _, opt := range opts {
opt(b)
}
// 1. 解析 goc 命令行和 go 命令行
b.buildCmdArgsParse()
// 2. 解析 go 包位置
b.getPackagesDir()
// 3. 读取工程元信息go.mod, pkgs list ...
b.readProjectMetaInfo()
// 4. 展示元信息
b.displayProjectMetaInfo()
// 1. 解析 goc 命令行和 go 命令行
b.buildCmdArgsParse()
// 2. 解析 go 包位置
b.getPackagesDir()
// 3. 读取工程元信息go.mod, pkgs list ...
b.readProjectMetaInfo()
// 4. 展示元信息
b.displayProjectMetaInfo()
return b
return b
}
// Build starts go build
@ -78,69 +78,69 @@ func NewBuild(opts ...gocOption) *Build {
// 2. inject cover variables and functions into the project,
// 3. build the project in temp.
func (b *Build) Build() {
// 1. 拷贝至临时目录
b.copyProjectToTmp()
defer b.clean()
// 1. 拷贝至临时目录
b.copyProjectToTmp()
defer b.clean()
log.Donef("project copied to temporary directory")
log.Donef("project copied to temporary directory")
// 2. update go.mod file if needed
b.updateGoModFile()
// 3. inject cover vars
b.Inject()
// 2. update go.mod file if needed
b.updateGoModFile()
// 3. inject cover vars
b.Inject()
if b.IsVendorMod && b.IsModEdit {
b.reVendor()
}
if b.IsVendorMod && b.IsModEdit {
b.reVendor()
}
// 4. build in the temp project
b.doBuildInTemp()
// 4. build in the temp project
b.doBuildInTemp()
}
func (b *Build) doBuildInTemp() {
log.StartWait("building the injected project")
log.StartWait("building the injected project")
goflags := b.Goflags
// 检查用户是否自定义了 -o
oSet := false
for _, flag := range goflags {
if flag == "-o" {
oSet = true
}
}
goflags := b.Goflags
// 检查用户是否自定义了 -o
oSet := false
for _, flag := range goflags {
if flag == "-o" {
oSet = true
}
}
// 如果没被设置就加一个至原命令执行的目录
if !oSet {
goflags = append(goflags, "-o", b.CurWd)
}
// 如果没被设置就加一个至原命令执行的目录
if !oSet {
goflags = append(goflags, "-o", b.CurWd)
}
if b.IsVendorMod && b.IsModEdit {
b.reVendor()
}
if b.IsVendorMod && b.IsModEdit {
b.reVendor()
}
pacakges := b.Packages
pacakges := b.Packages
goflags = append(goflags, pacakges...)
goflags = append(goflags, pacakges...)
args := []string{"build"}
args = append(args, goflags...)
// go 命令行由 go build [-o output] [build flags] [packages] 组成
cmd := exec.Command("go", args...)
cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
args := []string{"build"}
args = append(args, goflags...)
// go 命令行由 go build [-o output] [build flags] [packages] 组成
cmd := exec.Command("go", args...)
cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
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)
}
if err := cmd.Wait(); err != nil {
log.Fatalf("fail to execute go build: %v", err)
}
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)
}
if err := cmd.Wait(); err != nil {
log.Fatalf("fail to execute go build: %v", err)
}
// done
log.StopWait()
log.Donef("go build done")
// done
log.StopWait()
log.Donef("go build done")
}
// nicePrintArgs 优化 args 打印内容
@ -149,32 +149,32 @@ func (b *Build) doBuildInTemp() {
//
// 实际输出会变为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)
}
}
output := make([]string, 0)
for _, arg := range args {
if strings.Contains(arg, " ") {
output = append(output, "\""+arg+"\"")
} else {
output = append(output, arg)
}
}
return output
return output
}
func (b *Build) reVendor() {
log.StartWait("re-vendoring the project")
cmd := exec.Command("go", "mod", "vendor")
cmd.Dir = b.TmpModProjectDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.StartWait("re-vendoring the project")
cmd := exec.Command("go", "mod", "vendor")
cmd.Dir = b.TmpModProjectDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
log.Fatalf("fail to execute go vendor: %v", err)
}
if err := cmd.Wait(); err != nil {
log.Fatalf("fail to execute go vendor: %v", err)
}
if err := cmd.Start(); err != nil {
log.Fatalf("fail to execute go vendor: %v", err)
}
if err := cmd.Wait(); err != nil {
log.Fatalf("fail to execute go vendor: %v", err)
}
log.StopWait()
log.Donef("re-vendor the project done")
log.StopWait()
log.Donef("re-vendor the project done")
}

View File

@ -14,15 +14,15 @@
package build
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var buildUsage string = `Usage:
@ -53,23 +53,23 @@ However, other flags' order are same with the go official command.
`
const (
GO_BUILD = iota
GO_INSTALL
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)
// 首先解析 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
return allFlagSets
}
// buildCmdArgsParse parse both go flags and goc flags, it rewrite go flags if
@ -77,246 +77,246 @@ func CustomParseCmdAndArgs(cmd *cobra.Command, args []string) *pflag.FlagSet {
//
// 吞下 [packages] 之前所有的 flags.
func (b *Build) buildCmdArgsParse() {
args := b.Args
cmdType := b.BuildType
allFlagSets := b.FlagSets
args := b.Args
cmdType := b.BuildType
allFlagSets := b.FlagSets
// 重写 help
helpFlag := allFlagSets.Lookup("help")
// 重写 help
helpFlag := allFlagSets.Lookup("help")
if helpFlag.Changed {
if cmdType == GO_BUILD {
printGoHelp(buildUsage)
} else if cmdType == GO_INSTALL {
printGoHelp(installUsage)
}
if helpFlag.Changed {
if cmdType == GO_BUILD {
printGoHelp(buildUsage)
} else if cmdType == GO_INSTALL {
printGoHelp(installUsage)
}
os.Exit(0)
}
// 删除 help flag
args = findAndDelHelpFlag(args)
os.Exit(0)
}
// 删除 help flag
args = findAndDelHelpFlag(args)
// 必须手动调用
// 由于关闭了 cobra 的 flag parseroot PersistentPreRun 调用时log.NewLogger 并没有拿到 debug 值
log.NewLogger(b.Debug)
// 必须手动调用
// 由于关闭了 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())
})
// 删除 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
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 {
if _, ok := booleanFlags[f.Name]; !ok {
flags = append(flags, "-"+f.Name, f.Value.String())
} else {
flags = append(flags, "-"+f.Name)
}
if f.Name == "mod" {
if f.Value.String() == "vendor" {
b.IsVendorMod = true
} else {
b.IsVendorMod = false
}
}
}
})
// 找出设置的 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 {
if _, ok := booleanFlags[f.Name]; !ok {
flags = append(flags, "-"+f.Name, f.Value.String())
} else {
flags = append(flags, "-"+f.Name)
}
if f.Name == "mod" {
if f.Value.String() == "vendor" {
b.IsVendorMod = true
} else {
b.IsVendorMod = false
}
}
}
})
b.Goflags = flags
b.CurWd = curWd
b.GoArgs = goFlagSets.Args()
return
b.Goflags = flags
b.CurWd = curWd
b.GoArgs = goFlagSets.Args()
return
}
func (b *Build) runCmdArgsParse() {
args := b.Args
allFlagSets := b.FlagSets
args := b.Args
allFlagSets := b.FlagSets
// 重写 help
helpFlag := allFlagSets.Lookup("help")
// 重写 help
helpFlag := allFlagSets.Lookup("help")
if helpFlag.Changed {
printGoHelp(runUsage)
os.Exit(0)
}
if helpFlag.Changed {
printGoHelp(runUsage)
os.Exit(0)
}
// 删除 help flag
args = findAndDelHelpFlag(args)
// 删除 help flag
args = findAndDelHelpFlag(args)
// 必须手动调用
// 由于关闭了 cobra 的 flag parseroot PersistentPreRun 调用时log.NewLogger 并没有拿到 debug 值
log.NewLogger(b.Debug)
// 必须手动调用
// 由于关闭了 cobra 的 flag parseroot PersistentPreRun 调用时log.NewLogger 并没有拿到 debug 值
log.NewLogger(b.Debug)
curWd, err := os.Getwd()
if err != nil {
log.Fatalf("fail to get current working directory: %v", err)
}
b.CurWd = curWd
curWd, err := os.Getwd()
if err != nil {
log.Fatalf("fail to get current working directory: %v", err)
}
b.CurWd = curWd
// 获取除 goc flags 之外的 args
// 删除 cobra 定义的 flag
allFlagSets.Visit(func(f *pflag.Flag) {
args = findAndDelGocFlag(args, f.Name, f.Value.String())
})
// 获取除 goc flags 之外的 args
// 删除 cobra 定义的 flag
allFlagSets.Visit(func(f *pflag.Flag) {
args = findAndDelGocFlag(args, f.Name, f.Value.String())
})
b.GoArgs = args
b.GoArgs = 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])
}
}
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
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)
}
}
new := make([]string, 0, len(a))
for _, v := range a {
if v == "--help" || v == "-h" {
continue
} else {
new = append(new, v)
}
}
return new
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
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
// mod related
ModCacheRW bool
ModFile string
}
var goflags goConfig
var booleanFlags map[string]struct{} = make(map[string]struct{})
func addBuildFlags(cmdSet *flag.FlagSet) {
cmdSet.BoolVar(&goflags.BuildA, "a", false, "")
booleanFlags["a"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildN, "n", false, "")
booleanFlags["n"] = struct{}{}
cmdSet.IntVar(&goflags.BuildP, "p", 4, "")
cmdSet.BoolVar(&goflags.BuildV, "v", false, "")
booleanFlags["v"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildX, "x", false, "")
booleanFlags["x"] = struct{}{}
cmdSet.StringVar(&goflags.BuildBuildmode, "buildmode", "default", "")
cmdSet.StringVar(&goflags.BuildMod, "mod", "", "")
cmdSet.StringVar(&goflags.Installsuffix, "installsuffix", "", "")
cmdSet.BoolVar(&goflags.BuildA, "a", false, "")
booleanFlags["a"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildN, "n", false, "")
booleanFlags["n"] = struct{}{}
cmdSet.IntVar(&goflags.BuildP, "p", 4, "")
cmdSet.BoolVar(&goflags.BuildV, "v", false, "")
booleanFlags["v"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildX, "x", false, "")
booleanFlags["x"] = struct{}{}
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, "")
booleanFlags["modcacherw"] = struct{}{}
cmdSet.StringVar(&goflags.ModFile, "modfile", "", "")
cmdSet.StringVar(&goflags.BuildLdflags, "ldflags", "", "")
cmdSet.BoolVar(&goflags.BuildLinkshared, "linkshared", false, "")
booleanFlags["linkshared"] = struct{}{}
cmdSet.StringVar(&goflags.BuildPkgdir, "pkgdir", "", "")
cmdSet.BoolVar(&goflags.BuildRace, "race", false, "")
booleanFlags["race"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildMSan, "msan", false, "")
booleanFlags["msan"] = struct{}{}
cmdSet.StringVar(&goflags.BuildTags, "tags", "", "")
cmdSet.StringVar(&goflags.BuildToolexec, "toolexec", "", "")
cmdSet.BoolVar(&goflags.BuildTrimpath, "trimpath", false, "")
booleanFlags["trimpath"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildWork, "work", false, "")
booleanFlags["work"] = struct{}{}
// 类型和 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, "")
booleanFlags["modcacherw"] = struct{}{}
cmdSet.StringVar(&goflags.ModFile, "modfile", "", "")
cmdSet.StringVar(&goflags.BuildLdflags, "ldflags", "", "")
cmdSet.BoolVar(&goflags.BuildLinkshared, "linkshared", false, "")
booleanFlags["linkshared"] = struct{}{}
cmdSet.StringVar(&goflags.BuildPkgdir, "pkgdir", "", "")
cmdSet.BoolVar(&goflags.BuildRace, "race", false, "")
booleanFlags["race"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildMSan, "msan", false, "")
booleanFlags["msan"] = struct{}{}
cmdSet.StringVar(&goflags.BuildTags, "tags", "", "")
cmdSet.StringVar(&goflags.BuildToolexec, "toolexec", "", "")
cmdSet.BoolVar(&goflags.BuildTrimpath, "trimpath", false, "")
booleanFlags["trimpath"] = struct{}{}
cmdSet.BoolVar(&goflags.BuildWork, "work", false, "")
booleanFlags["work"] = struct{}{}
}
func addOutputFlags(cmdSet *flag.FlagSet) {
cmdSet.StringVar(&goflags.BuildO, "o", "", "")
cmdSet.StringVar(&goflags.BuildO, "o", "", "")
}
func printGoHelp(usage string) {
fmt.Println(usage)
fmt.Println(usage)
}
func printGocHelp(cmd *cobra.Command) {
flags := cmd.LocalFlags()
globalFlags := cmd.Parent().PersistentFlags()
flags := cmd.LocalFlags()
globalFlags := cmd.Parent().PersistentFlags()
fmt.Println("Flags:")
fmt.Println(flags.FlagUsages())
fmt.Println("Flags:")
fmt.Println(flags.FlagUsages())
fmt.Println("Global Flags:")
fmt.Println(globalFlags.FlagUsages())
fmt.Println("Global Flags:")
fmt.Println(globalFlags.FlagUsages())
}
// GetPackagesDir parse [pacakges] part of args, it will fatal if error encountered
@ -328,43 +328,43 @@ func printGocHelp(cmd *cobra.Command) {
// 如果 [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)
}
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
// 获取相对于 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
}
}
}
return
}
}
}
// 2. 要么是 import path
b.Packages = patterns
// 2. 要么是 import path
b.Packages = patterns
}
// goFilesPackage 对一组 go 文件解析,判断是否合法
@ -373,40 +373,40 @@ func (b *Build) getPackagesDir() {
// 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)
}
}
// 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
}
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.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 = "./"
}
// 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)
}
}
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
return nil
}
// getDirFromImportPaths return the import path's real abs directory
@ -414,90 +414,90 @@ func goFilesPackage(gofiles []string) error {
// 该函数接收到的只有 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
}
// 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")
}
// 为了简化插桩的逻辑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
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.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 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")
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)
}
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
}
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, "../")
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"
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)
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
}
// 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 ""
return ""
}

View File

@ -14,141 +14,141 @@
package build
import (
"bytes"
"encoding/json"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"bytes"
"encoding/json"
"io"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/log"
)
// readProjectMetaInfo reads all meta informations of the corresponding project
func (b *Build) readProjectMetaInfo() {
// get gopath & gobin
b.GOPATH = b.readGOPATH()
b.GOBIN = b.readGOBIN()
// 获取 [packages] 及其依赖的 package list
pkgs := b.listPackages(b.CurWd)
// get gopath & gobin
b.GOPATH = b.readGOPATH()
b.GOBIN = b.readGOBIN()
// 获取 [packages] 及其依赖的 package list
pkgs := b.listPackages(b.CurWd)
// get mod info
for _, pkg := range pkgs {
// check if go modules is enabled
if pkg.Module == nil {
log.Fatalf("Go module is not enabled, please set GO111MODULE=auto or on")
}
// 工程根目录
b.CurModProjectDir = pkg.Module.Dir
b.ImportPath = pkg.Module.Path
// get mod info
for _, pkg := range pkgs {
// check if go modules is enabled
if pkg.Module == nil {
log.Fatalf("Go module is not enabled, please set GO111MODULE=auto or on")
}
// 工程根目录
b.CurModProjectDir = pkg.Module.Dir
b.ImportPath = pkg.Module.Path
break
}
break
}
// 如果当前目录不是工程根目录,那再次 go list 一次,获取整个工程的包信息
if b.CurWd != b.CurModProjectDir {
b.Pkgs = b.listPackages(b.CurModProjectDir)
} else {
b.Pkgs = pkgs
}
// 如果当前目录不是工程根目录,那再次 go list 一次,获取整个工程的包信息
if b.CurWd != b.CurModProjectDir {
b.Pkgs = b.listPackages(b.CurModProjectDir)
} else {
b.Pkgs = pkgs
}
// check if project is in vendor mod
b.checkIfVendorMod()
// check if project is in vendor mod
b.checkIfVendorMod()
// get tmp folder name
b.TmpModProjectDir = filepath.Join(os.TempDir(), TmpFolderName(b.CurModProjectDir))
// get working dir in the corresponding tmp dir
b.TmpWd = filepath.Join(b.TmpModProjectDir, b.CurWd[len(b.CurModProjectDir):])
// get GlobalCoverVarImportPath
b.GlobalCoverVarImportPath = path.Join(b.ImportPath, TmpFolderName(b.CurModProjectDir))
log.Donef("project meta information parsed")
// get tmp folder name
b.TmpModProjectDir = filepath.Join(os.TempDir(), TmpFolderName(b.CurModProjectDir))
// get working dir in the corresponding tmp dir
b.TmpWd = filepath.Join(b.TmpModProjectDir, b.CurWd[len(b.CurModProjectDir):])
// get GlobalCoverVarImportPath
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", b.GOPATH)
log.Infof("GOBIN: %v", b.GOBIN)
log.Infof("Project Directory: %v", b.CurModProjectDir)
log.Infof("GOC_REGISTER_EXTRA from env: %v", os.Getenv("GOC_REGISTER_EXTRA"))
log.Infof("Temporary Project Directory: %v", b.TmpModProjectDir)
if b.IsVendorMod {
log.Infof("Project in vendor mod")
}
log.Infof("")
log.Infof("GOPATH: %v", b.GOPATH)
log.Infof("GOBIN: %v", b.GOBIN)
log.Infof("Project Directory: %v", b.CurModProjectDir)
log.Infof("GOC_REGISTER_EXTRA from env: %v", os.Getenv("GOC_REGISTER_EXTRA"))
log.Infof("Temporary Project Directory: %v", b.TmpModProjectDir)
if b.IsVendorMod {
log.Infof("Project in vendor mod")
}
log.Infof("")
}
// readGOPATH reads GOPATH use go env GOPATH command
func (b *Build) readGOPATH() string {
out, err := exec.Command("go", "env", "GOPATH").Output()
if err != nil {
log.Fatalf("fail to read GOPATH: %v", err)
}
return strings.TrimSpace(string(out))
out, err := exec.Command("go", "env", "GOPATH").Output()
if err != nil {
log.Fatalf("fail to read GOPATH: %v", err)
}
return strings.TrimSpace(string(out))
}
// readGOBIN reads GOBIN use go env GOBIN command
func (b *Build) readGOBIN() string {
out, err := exec.Command("go", "env", "GOBIN").Output()
if err != nil {
log.Fatalf("fail to read GOBIN: %v", err)
}
return strings.TrimSpace(string(out))
out, err := exec.Command("go", "env", "GOBIN").Output()
if err != nil {
log.Fatalf("fail to read GOBIN: %v", err)
}
return strings.TrimSpace(string(out))
}
// listPackages list all packages under specific via go list command.
func (b *Build) listPackages(dir string) map[string]*Package {
listArgs := []string{"list", "-json"}
if goflags.BuildTags != "" {
listArgs = append(listArgs, "-tags", goflags.BuildTags)
}
listArgs = append(listArgs, "./...")
listArgs := []string{"list", "-json"}
if goflags.BuildTags != "" {
listArgs = append(listArgs, "-tags", goflags.BuildTags)
}
listArgs = append(listArgs, "./...")
cmd := exec.Command("go", listArgs...)
cmd.Dir = dir
cmd := exec.Command("go", listArgs...)
cmd.Dir = dir
var errBuf bytes.Buffer
cmd.Stderr = &errBuf
out, err := cmd.Output()
if err != nil {
log.Fatalf("execute go list -json failed, err: %v, stdout: %v, stderr: %v", err, string(out), errBuf.String())
}
// 有些时候 go 命令会打印一些信息到 stderr但其实命令整体是成功运行了
if errBuf.String() != "" {
log.Errorf("%v", errBuf.String())
}
var errBuf bytes.Buffer
cmd.Stderr = &errBuf
out, err := cmd.Output()
if err != nil {
log.Fatalf("execute go list -json failed, err: %v, stdout: %v, stderr: %v", err, string(out), errBuf.String())
}
// 有些时候 go 命令会打印一些信息到 stderr但其实命令整体是成功运行了
if errBuf.String() != "" {
log.Errorf("%v", errBuf.String())
}
dec := json.NewDecoder(bytes.NewBuffer(out))
pkgs := make(map[string]*Package)
dec := json.NewDecoder(bytes.NewBuffer(out))
pkgs := make(map[string]*Package)
for {
var pkg Package
if err := dec.Decode(&pkg); err != nil {
if err == io.EOF {
break
}
log.Fatalf("reading go list output error: %v", err)
}
if pkg.Error != nil {
log.Fatalf("list package %s failed with output: %v", pkg.ImportPath, pkg.Error)
}
for {
var pkg Package
if err := dec.Decode(&pkg); err != nil {
if err == io.EOF {
break
}
log.Fatalf("reading go list output error: %v", err)
}
if pkg.Error != nil {
log.Fatalf("list package %s failed with output: %v", pkg.ImportPath, pkg.Error)
}
pkgs[pkg.ImportPath] = &pkg
}
pkgs[pkg.ImportPath] = &pkg
}
return pkgs
return pkgs
}
func (b *Build) checkIfVendorMod() {
if b.IsVendorMod == true {
return
}
if b.IsVendorMod == true {
return
}
vendorDir := filepath.Join(b.CurModProjectDir, "vendor")
if _, err := os.Stat(vendorDir); err != nil {
b.IsVendorMod = false
}
vendorDir := filepath.Join(b.CurModProjectDir, "vendor")
if _, err := os.Stat(vendorDir); err != nil {
b.IsVendorMod = false
}
b.IsVendorMod = true
b.IsVendorMod = true
}

View File

@ -14,77 +14,77 @@
package build
import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"github.com/RickLeee/goc-v2/pkg/build/internal/tool"
"github.com/RickLeee/goc-v2/pkg/build/internal/websocket"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/build/internal/tool"
"github.com/RickLeee/goc/v2/pkg/build/internal/websocket"
"github.com/RickLeee/goc/v2/pkg/log"
)
// Inject injects cover variables for all the .go files in the target directory
func (b *Build) Inject() {
log.StartWait("injecting cover variables")
log.StartWait("injecting cover variables")
var seen = make(map[string]*PackageCover)
var seen = make(map[string]*PackageCover)
// 所有插桩变量定义声明
allDecl := ""
// 所有插桩变量定义声明
allDecl := ""
pkgs := b.Pkgs
for _, pkg := range pkgs {
if pkg.Name == "main" {
// 该 main 二进制所关联的所有插桩变量的元信息
// 每个 main 之间是不相关的,需要重新定义
allMainCovers := make([]*PackageCover, 0)
// 注入 main package
mainCover, mainDecl := b.addCounters(pkg)
// 收集插桩变量的定义和元信息
allDecl += mainDecl
allMainCovers = append(allMainCovers, mainCover)
pkgs := b.Pkgs
for _, pkg := range pkgs {
if pkg.Name == "main" {
// 该 main 二进制所关联的所有插桩变量的元信息
// 每个 main 之间是不相关的,需要重新定义
allMainCovers := make([]*PackageCover, 0)
// 注入 main package
mainCover, mainDecl := b.addCounters(pkg)
// 收集插桩变量的定义和元信息
allDecl += mainDecl
allMainCovers = append(allMainCovers, mainCover)
// 向 main package 的依赖注入插桩变量
for _, dep := range pkg.Deps {
if packageCover, ok := seen[dep]; ok {
allMainCovers = append(allMainCovers, packageCover)
continue
}
// 向 main package 的依赖注入插桩变量
for _, dep := range pkg.Deps {
if packageCover, ok := seen[dep]; ok {
allMainCovers = append(allMainCovers, packageCover)
continue
}
// 依赖需要忽略 Go 标准库和 go.mod 引入的第三方
if depPkg, ok := pkgs[dep]; ok {
// 注入依赖的 package
packageCover, depDecl := b.addCounters(depPkg)
// 收集插桩变量的定义和元信息
allDecl += depDecl
allMainCovers = append(allMainCovers, packageCover)
// 避免重复访问
seen[dep] = packageCover
}
}
// 为每个 main 包注入 websocket handler
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)
}
}
}
// 在工程根目录注入所有插桩变量的声明+定义
b.injectGlobalCoverVarFile(allDecl)
// 依赖需要忽略 Go 标准库和 go.mod 引入的第三方
if depPkg, ok := pkgs[dep]; ok {
// 注入依赖的 package
packageCover, depDecl := b.addCounters(depPkg)
// 收集插桩变量的定义和元信息
allDecl += depDecl
allMainCovers = append(allMainCovers, packageCover)
// 避免重复访问
seen[dep] = packageCover
}
}
// 为每个 main 包注入 websocket handler
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)
}
}
}
// 在工程根目录注入所有插桩变量的声明+定义
b.injectGlobalCoverVarFile(allDecl)
// 添加自定义 websocket 依赖
// 用户代码可能有 gorrila/websocket 的依赖,为避免依赖冲突,以及可能的 replace/vendor
// 这里直接注入一份完整的 gorrila/websocket 实现
websocket.AddCustomWebsocketDep(b.GlobalCoverVarImportPathDir)
log.Donef("websocket library injected")
// 添加自定义 websocket 依赖
// 用户代码可能有 gorrila/websocket 的依赖,为避免依赖冲突,以及可能的 replace/vendor
// 这里直接注入一份完整的 gorrila/websocket 实现
websocket.AddCustomWebsocketDep(b.GlobalCoverVarImportPathDir)
log.Donef("websocket library injected")
log.StopWait()
log.Donef("global cover variables injected")
log.StopWait()
log.Donef("global cover variables injected")
}
// addCounters is different from official go tool cover
@ -95,20 +95,20 @@ func (b *Build) Inject() {
//
// 3. return the declarations as string
func (b *Build) addCounters(pkg *Package) (*PackageCover, string) {
mode := b.Mode
gobalCoverVarImportPath := b.GlobalCoverVarImportPath
mode := b.Mode
gobalCoverVarImportPath := b.GlobalCoverVarImportPath
coverVarMap := declareCoverVars(pkg)
coverVarMap := declareCoverVars(pkg)
decl := ""
for file, coverVar := range coverVarMap {
decl += "\n" + tool.Annotate(filepath.Join(b.getPkgTmpDir(pkg.Dir), file), mode, coverVar.Var, coverVar.File, gobalCoverVarImportPath) + "\n"
}
decl := ""
for file, coverVar := range coverVarMap {
decl += "\n" + tool.Annotate(filepath.Join(b.getPkgTmpDir(pkg.Dir), file), mode, coverVar.Var, coverVar.File, gobalCoverVarImportPath) + "\n"
}
return &PackageCover{
Package: pkg,
Vars: coverVarMap,
}, decl
return &PackageCover{
Package: pkg,
Vars: coverVarMap,
}, decl
}
// getPkgTmpDir gets corresponding pkg dir in temporary project
@ -119,12 +119,12 @@ func (b *Build) addCounters(pkg *Package) (*PackageCover, string) {
// 在原工程目录已经做了一次 go list -json在临时目录没有必要再做一遍直接转换一下就能得到
// 临时目录中的 pkg.Dir。
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)
}
relDir, err := filepath.Rel(b.CurModProjectDir, pkgDir)
if err != nil {
log.Fatalf("go json -list meta info wrong: %v", err)
}
return filepath.Join(b.TmpModProjectDir, relDir)
return filepath.Join(b.TmpModProjectDir, relDir)
}
// injectGocAgent inject handlers like following
@ -141,143 +141,143 @@ func (b *Build) getPkgTmpDir(pkgDir string) string {
// 11111_22222_bridge.go 仅仅用于引用 11111_22222_package, where package contains ws agent main logic.
// 使用 bridge.go 文件是为了避免插桩逻辑中的变量名污染 main 包
func (b *Build) injectGocAgent(where string, covers []*PackageCover) {
if len(covers) == 0 {
return
}
if len(covers) == 0 {
return
}
if len(covers[0].Vars) == 0 {
return
}
if len(covers[0].Vars) == 0 {
return
}
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)
err := os.MkdirAll(wherePkg, os.ModePerm)
if err != nil {
log.Fatalf("fail to generate %v directory: %v", injectPkgName, err)
}
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)
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, injectBridgeName)
f1, err := os.Create(whereBridge)
if err != nil {
log.Fatalf("fail to create cover bridge file in temporary project: %v", err)
}
defer f1.Close()
// create bridge file
whereBridge := filepath.Join(where, injectBridgeName)
f1, err := os.Create(whereBridge)
if err != nil {
log.Fatalf("fail to create cover bridge file in temporary project: %v", err)
}
defer f1.Close()
tmplBridgeData := struct {
CoverImportPath string
}{
// covers[0] is the main package
CoverImportPath: covers[0].Package.ImportPath + "/" + injectPkgName,
}
tmplBridgeData := struct {
CoverImportPath string
}{
// covers[0] is the main package
CoverImportPath: covers[0].Package.ImportPath + "/" + injectPkgName,
}
if err := coverBridgeTmpl.Execute(f1, tmplBridgeData); err != nil {
log.Fatalf("fail to generate cover bridge in temporary project: %v", err)
}
if err := coverBridgeTmpl.Execute(f1, tmplBridgeData); err != nil {
log.Fatalf("fail to generate cover bridge in temporary project: %v", err)
}
// create ws agent files
dest := filepath.Join(wherePkg, "rpcagent.go")
// create ws agent files
dest := filepath.Join(wherePkg, "rpcagent.go")
f2, err := os.Create(dest)
if err != nil {
log.Fatalf("fail to create cover agent file in temporary project: %v", err)
}
defer f2.Close()
f2, err := os.Create(dest)
if err != nil {
log.Fatalf("fail to create cover agent file in temporary project: %v", err)
}
defer f2.Close()
var _coverMode string
if b.Mode == "watch" {
_coverMode = "cover"
} else {
_coverMode = b.Mode
}
var commitID string
cmd := exec.Command("git", "rev-parse", "--short=8", "HEAD")
output, err := cmd.Output()
if err != nil {
log.Errorf("git describe Error:", err)
} else {
commitID = strings.TrimRight(string(output), "\n")
}
var branch string
cmd = exec.Command("git", "branch", "--contains", commitID, "-r")
br, err := cmd.Output()
if err != nil {
log.Errorf("get git branch Error:", err)
} else {
log.Infof("[goc][info] raw branch: %v ", br)
branch = strings.Replace(string(br), "\n", "", -1)
branch = strings.TrimLeft(branch, "heads/")
branch = strings.Trim(branch, " ")
}
log.Infof("[goc][info] branch: %v --- commitID: %v", branch, commitID)
tmplData := struct {
Covers []*PackageCover
GlobalCoverVarImportPath string
Package string
Host string
Mode string
CommitID string
Branch string
}{
Covers: covers,
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
Package: injectPkgName,
Host: b.Host,
Mode: _coverMode,
Branch: branch,
CommitID: commitID,
}
var _coverMode string
if b.Mode == "watch" {
_coverMode = "cover"
} else {
_coverMode = b.Mode
}
var commitID string
cmd := exec.Command("git", "rev-parse", "--short=8", "HEAD")
output, err := cmd.Output()
if err != nil {
log.Errorf("git describe Error:", err)
} else {
commitID = strings.TrimRight(string(output), "\n")
}
var branch string
cmd = exec.Command("git", "branch", "--contains", commitID, "-r")
br, err := cmd.Output()
if err != nil {
log.Errorf("get git branch Error:", err)
} else {
log.Infof("[goc][info] raw branch: %v ", br)
branch = strings.Replace(string(br), "\n", "", -1)
branch = strings.TrimLeft(branch, "heads/")
branch = strings.Trim(branch, " ")
}
log.Infof("[goc][info] branch: %v --- commitID: %v", branch, commitID)
tmplData := struct {
Covers []*PackageCover
GlobalCoverVarImportPath string
Package string
Host string
Mode string
CommitID string
Branch string
}{
Covers: covers,
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
Package: injectPkgName,
Host: b.Host,
Mode: _coverMode,
Branch: branch,
CommitID: commitID,
}
if err := coverMainTmpl.Execute(f2, tmplData); err != nil {
log.Fatalf("fail to generate cover agent handlers in temporary project: %v", err)
}
if err := coverMainTmpl.Execute(f2, tmplData); err != nil {
log.Fatalf("fail to generate cover agent handlers in temporary project: %v", err)
}
// 写入 watch
if b.Mode != "watch" {
return
}
f, err := os.Create(filepath.Join(wherePkg, "watchagent.go"))
if err != nil {
log.Fatalf("fail to create watchagent file: %v", err)
}
// 写入 watch
if b.Mode != "watch" {
return
}
f, err := os.Create(filepath.Join(wherePkg, "watchagent.go"))
if err != nil {
log.Fatalf("fail to create watchagent file: %v", err)
}
tmplwatchData := struct {
Random string
Host string
GlobalCoverVarImportPath string
}{
Random: filepath.Base(b.TmpModProjectDir),
Host: b.Host,
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
}
tmplwatchData := struct {
Random string
Host string
GlobalCoverVarImportPath string
}{
Random: filepath.Base(b.TmpModProjectDir),
Host: b.Host,
GlobalCoverVarImportPath: b.GlobalCoverVarImportPath,
}
if err := coverWatchTmpl.Execute(f, tmplwatchData); err != nil {
log.Fatalf("fail to generate watchagent in temporary project: %v", err)
}
if err := coverWatchTmpl.Execute(f, tmplwatchData); err != nil {
log.Fatalf("fail to generate watchagent in temporary project: %v", err)
}
}
// injectGlobalCoverVarFile 写入所有插桩变量的全局定义至一个单独的文件
func (b *Build) injectGlobalCoverVarFile(decl string) {
globalCoverVarPackage := path.Base(b.GlobalCoverVarImportPath)
globalCoverDef := filepath.Join(b.TmpModProjectDir, globalCoverVarPackage)
b.GlobalCoverVarImportPathDir = globalCoverDef
globalCoverVarPackage := path.Base(b.GlobalCoverVarImportPath)
globalCoverDef := filepath.Join(b.TmpModProjectDir, globalCoverVarPackage)
b.GlobalCoverVarImportPathDir = globalCoverDef
err := os.MkdirAll(globalCoverDef, os.ModePerm)
if err != nil {
log.Fatalf("fail to create global cover definition package dir: %v", err)
}
coverFile, err := os.Create(filepath.Join(globalCoverDef, "cover.go"))
if err != nil {
log.Fatalf("fail to create global cover definition file: %v", err)
}
err := os.MkdirAll(globalCoverDef, os.ModePerm)
if err != nil {
log.Fatalf("fail to create global cover definition package dir: %v", err)
}
coverFile, err := os.Create(filepath.Join(globalCoverDef, "cover.go"))
if err != nil {
log.Fatalf("fail to create global cover definition file: %v", err)
}
defer coverFile.Close()
defer coverFile.Close()
packageName := "package coverdef\n\n"
packageName := "package coverdef\n\n"
random := filepath.Base(b.TmpModProjectDir)
varWatchDef := fmt.Sprintf(`
random := filepath.Base(b.TmpModProjectDir)
varWatchDef := fmt.Sprintf(`
var WatchChannel_%v = make(chan *blockInfo, 1024)
var WatchEnabled_%v = false
@ -310,8 +310,8 @@ func UploadCoverChangeEvent_%v(name string, pos []uint32, i int, stmts uint16) {
`, random, random, random, random, random, random)
_, err = coverFile.WriteString(packageName + varWatchDef + decl)
if err != nil {
log.Fatalf("fail to write to global cover definition file: %v", err)
}
_, err = coverFile.WriteString(packageName + varWatchDef + decl)
if err != nil {
log.Fatalf("fail to write to global cover definition file: %v", err)
}
}

View File

@ -14,14 +14,14 @@
package build
import (
"os"
"os/exec"
"os"
"os/exec"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/log"
)
func NewInstall(opts ...gocOption) *Build {
return NewBuild(opts...)
return NewBuild(opts...)
}
// Install starts go install
@ -30,51 +30,51 @@ func NewInstall(opts ...gocOption) *Build {
// 2. inject cover variables and functions into the project,
// 3. install the project in temp.
func (b *Build) Install() {
// 1. 拷贝至临时目录
b.copyProjectToTmp()
defer b.clean()
// 1. 拷贝至临时目录
b.copyProjectToTmp()
defer b.clean()
log.Donef("project copied to temporary directory")
log.Donef("project copied to temporary directory")
// 2. update go.mod file if needed
b.updateGoModFile()
// 3. inject cover vars
b.Inject()
// 2. update go.mod file if needed
b.updateGoModFile()
// 3. inject cover vars
b.Inject()
if b.IsVendorMod && b.IsModEdit {
b.reVendor()
}
if b.IsVendorMod && b.IsModEdit {
b.reVendor()
}
// 4. install in the temp project
b.doInstallInTemp()
// 4. install in the temp project
b.doInstallInTemp()
}
func (b *Build) doInstallInTemp() {
log.StartWait("installing the injected project")
log.StartWait("installing the injected project")
goflags := b.Goflags
goflags := b.Goflags
pacakges := b.Packages
pacakges := b.Packages
goflags = append(goflags, pacakges...)
goflags = append(goflags, pacakges...)
args := []string{"install"}
args = append(args, goflags...)
// go 命令行由 go install [build flags] [packages] 组成
cmd := exec.Command("go", args...)
cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
args := []string{"install"}
args = append(args, goflags...)
// go 命令行由 go install [build flags] [packages] 组成
cmd := exec.Command("go", args...)
cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Infof("go install cmd is: %v, in path [%v]", cmd.Args, cmd.Dir)
if err := cmd.Start(); err != nil {
log.Fatalf("fail to execute go install: %v", err)
}
if err := cmd.Wait(); err != nil {
log.Fatalf("fail to execute go install: %v", err)
}
log.Infof("go install cmd is: %v, in path [%v]", cmd.Args, cmd.Dir)
if err := cmd.Start(); err != nil {
log.Fatalf("fail to execute go install: %v", err)
}
if err := cmd.Wait(); err != nil {
log.Fatalf("fail to execute go install: %v", err)
}
// done
log.StopWait()
log.Donef("go install done")
// done
log.StopWait()
log.Donef("go install done")
}

File diff suppressed because it is too large Load Diff

View File

@ -14,14 +14,14 @@
package websocket
import (
"archive/tar"
"bytes"
"embed"
"io"
"os"
"path/filepath"
"archive/tar"
"bytes"
"embed"
"io"
"os"
"path/filepath"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/log"
)
//go:embed websocket.tar
@ -32,48 +32,48 @@ var depTarFile embed.FS
// 从 embed 文件系统中解压 websocket.tar 文件,并依次写入临时工程中,作为一个单独的包存在。
// gorrila/websocket 是一个无第三方依赖的库,因此其位置可以随处移动,而不影响自身的编译。
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)
}
data, err := depTarFile.ReadFile("websocket.tar")
if err != nil {
log.Fatalf("cannot find the websocket.tar in the embed file: %v", err)
}
buf := bytes.NewBuffer(data)
tr := tar.NewReader(buf)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("cannot untar the websocket.tar: %v", err)
}
buf := bytes.NewBuffer(data)
tr := tar.NewReader(buf)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("cannot untar the websocket.tar: %v", err)
}
fpath := filepath.Join(customWebsocketPath, hdr.Name)
if hdr.FileInfo().IsDir() {
// 处理目录
err := os.MkdirAll(fpath, hdr.FileInfo().Mode())
if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err)
}
} else {
// 处理文件
fdir := filepath.Dir(fpath)
err := os.MkdirAll(fdir, hdr.FileInfo().Mode())
if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err)
}
fpath := filepath.Join(customWebsocketPath, hdr.Name)
if hdr.FileInfo().IsDir() {
// 处理目录
err := os.MkdirAll(fpath, hdr.FileInfo().Mode())
if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err)
}
} else {
// 处理文件
fdir := filepath.Dir(fpath)
err := os.MkdirAll(fdir, hdr.FileInfo().Mode())
if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err)
}
f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, hdr.FileInfo().Mode())
if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err)
}
defer f.Close()
f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, hdr.FileInfo().Mode())
if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err)
}
defer f.Close()
_, err = io.Copy(f, tr)
_, err = io.Copy(f, tr)
if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err)
}
}
}
if err != nil {
log.Fatalf("fail to untar the websocket.tar: %v", err)
}
}
}
}

View File

@ -14,33 +14,33 @@
package build
import (
"os"
"os/exec"
"os/signal"
"os"
"os/exec"
"os/signal"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/RickLeee/goc-v2/pkg/server"
"github.com/RickLeee/goc-v2/pkg/server/store"
"github.com/gin-gonic/gin"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/server"
"github.com/RickLeee/goc/v2/pkg/server/store"
"github.com/gin-gonic/gin"
)
func NewRun(opts ...gocOption) *Build {
b := &Build{}
b := &Build{}
for _, opt := range opts {
opt(b)
}
for _, opt := range opts {
opt(b)
}
// 1. 解析 goc 命令行和 go 命令行
b.runCmdArgsParse()
// 2. 解析 go 包位置
// b.getPackagesDir()
// 3. 读取工程元信息go.mod, pkgs list ...
b.readProjectMetaInfo()
// 4. 展示元信息
b.displayProjectMetaInfo()
// 1. 解析 goc 命令行和 go 命令行
b.runCmdArgsParse()
// 2. 解析 go 包位置
// b.getPackagesDir()
// 3. 读取工程元信息go.mod, pkgs list ...
b.readProjectMetaInfo()
// 4. 展示元信息
b.displayProjectMetaInfo()
return b
return b
}
// Run starts go run
@ -49,58 +49,58 @@ func NewRun(opts ...gocOption) *Build {
// 2. inject cover variables and functions into the project,
// 3. run the project in temp.
func (b *Build) Run() {
// 1. 拷贝至临时目录
b.copyProjectToTmp()
defer b.clean()
// 1. 拷贝至临时目录
b.copyProjectToTmp()
defer b.clean()
log.Donef("project copied to temporary directory")
log.Donef("project copied to temporary directory")
// 2. update go.mod file if needed
b.updateGoModFile()
// 3. inject cover vars
b.Inject()
// 2. update go.mod file if needed
b.updateGoModFile()
// 3. inject cover vars
b.Inject()
if b.IsVendorMod && b.IsModEdit {
b.reVendor()
}
if b.IsVendorMod && b.IsModEdit {
b.reVendor()
}
// 4. run in the temp project
go func() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
<-ch
b.clean()
}()
b.doRunInTemp()
// 4. run in the temp project
go func() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
<-ch
b.clean()
}()
b.doRunInTemp()
}
func (b *Build) doRunInTemp() {
log.Infof("running the injected project")
log.Infof("running the injected project")
s := store.NewFakeStore()
go func() {
gin.SetMode(gin.ReleaseMode)
err := server.RunGocServerUntilExit(b.Host, s)
if err != nil {
log.Fatalf("goc server fail to run: %v", err)
}
}()
s := store.NewFakeStore()
go func() {
gin.SetMode(gin.ReleaseMode)
err := server.RunGocServerUntilExit(b.Host, s)
if err != nil {
log.Fatalf("goc server fail to run: %v", err)
}
}()
args := []string{"run"}
args = append(args, b.GoArgs...)
cmd := exec.Command("go", args...)
cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
args := []string{"run"}
args = append(args, b.GoArgs...)
cmd := exec.Command("go", args...)
cmd.Dir = b.TmpWd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Infof("go run cmd is: %v, in path [%v]", nicePrintArgs(cmd.Args), cmd.Dir)
if err := cmd.Start(); err != nil {
log.Errorf("fail to execute go run: %v", err)
}
if err := cmd.Wait(); err != nil {
log.Errorf("fail to execute go run: %v", err)
}
log.Infof("go run cmd is: %v, in path [%v]", nicePrintArgs(cmd.Args), cmd.Dir)
if err := cmd.Start(); err != nil {
log.Errorf("fail to execute go run: %v", err)
}
if err := cmd.Wait(); err != nil {
log.Errorf("fail to execute go run: %v", err)
}
// done
log.Donef("go run done")
// done
log.Donef("go run done")
}

View File

@ -14,79 +14,79 @@
package build
import (
"crypto/sha256"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"crypto/sha256"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/tongjingran/copy"
"golang.org/x/mod/modfile"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/tongjingran/copy"
"golang.org/x/mod/modfile"
)
// copyProjectToTmp copies project files to the temporary directory
//
// It will ignore .git and irregular files, only copy source(text) files
func (b *Build) copyProjectToTmp() {
curProject := b.CurModProjectDir
tmpProject := b.TmpModProjectDir
curProject := b.CurModProjectDir
tmpProject := b.TmpModProjectDir
if _, err := os.Stat(tmpProject); !os.IsNotExist(err) {
log.Infof("find previous temporary directory, delete")
err := os.RemoveAll(tmpProject)
if err != nil {
log.Fatalf("fail to remove preivous temporary directory: %v", err)
}
}
if _, err := os.Stat(tmpProject); !os.IsNotExist(err) {
log.Infof("find previous temporary directory, delete")
err := os.RemoveAll(tmpProject)
if err != nil {
log.Fatalf("fail to remove preivous temporary directory: %v", err)
}
}
log.StartWait("coping project")
err := os.MkdirAll(tmpProject, os.ModePerm)
if err != nil {
log.Fatalf("fail to create temporary directory: %v", err)
}
log.StartWait("coping project")
err := os.MkdirAll(tmpProject, os.ModePerm)
if err != nil {
log.Fatalf("fail to create temporary directory: %v", err)
}
// copy
if err := copy.Copy(curProject, tmpProject, copy.Options{Skip: skipCopy}); err != nil {
log.Fatalf("fail to copy the folder from %v to %v, the err: %v", curProject, tmpProject, err)
}
// copy
if err := copy.Copy(curProject, tmpProject, copy.Options{Skip: skipCopy}); err != nil {
log.Fatalf("fail to copy the folder from %v to %v, the err: %v", curProject, tmpProject, err)
}
log.StopWait()
log.StopWait()
}
// TmpFolderName generates a directory name according to the path
func TmpFolderName(path string) string {
sum := sha256.Sum256([]byte(path))
h := fmt.Sprintf("%x", sum[:6])
sum := sha256.Sum256([]byte(path))
h := fmt.Sprintf("%x", sum[:6])
return "gocbuild" + h
return "gocbuild" + h
}
// skipCopy skip copy .git dir and irregular files
func skipCopy(src string, info os.FileInfo) (bool, error) {
irregularModeType := os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
if strings.HasSuffix(src, "/.git") {
log.Debugf("skip .git dir [%s]", src)
return true, nil
}
if info.Mode()&irregularModeType != 0 {
log.Debugf("skip file [%s], the file mode is [%s]", src, info.Mode().String())
return true, nil
}
return false, nil
irregularModeType := os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular
if strings.HasSuffix(src, "/.git") {
log.Debugf("skip .git dir [%s]", src)
return true, nil
}
if info.Mode()&irregularModeType != 0 {
log.Debugf("skip file [%s], the file mode is [%s]", src, info.Mode().String())
return true, nil
}
return false, nil
}
// clean clears the temporary project
func (b *Build) clean() {
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")
} else {
log.Debugf("--debug is enabled, keep the temporary project")
}
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")
} else {
log.Debugf("--debug is enabled, keep the temporary project")
}
}
// updateGoModFile rewrites the go.mod file in the temporary directory,
@ -102,50 +102,50 @@ 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(b.TmpModProjectDir, "go.mod")
buf, err := ioutil.ReadFile(tempModfile)
if err != nil {
log.Fatalf("cannot find go.mod file in temporary directory: %v", err)
}
oriGoModFile, err := modfile.Parse(tempModfile, buf, nil)
if err != nil {
log.Fatalf("cannot parse go.mod: %v", err)
}
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)
}
oriGoModFile, err := modfile.Parse(tempModfile, buf, nil)
if err != nil {
log.Fatalf("cannot parse go.mod: %v", err)
}
updateFlag = false
for index := range oriGoModFile.Replace {
replace := oriGoModFile.Replace[index]
oldPath := replace.Old.Path
oldVersion := replace.Old.Version
newPath := replace.New.Path
newVersion := replace.New.Version
// replace to a local filesystem does not have a version
// absolute path no need to rewrite
if newVersion == "" && !filepath.IsAbs(newPath) {
var absPath string
fullPath := filepath.Join(b.CurModProjectDir, newPath)
absPath, _ = filepath.Abs(fullPath)
// DropReplace & AddReplace will not return error
// so no need to check the error
_ = oriGoModFile.DropReplace(oldPath, oldVersion)
_ = oriGoModFile.AddReplace(oldPath, oldVersion, absPath, newVersion)
updateFlag = true
}
}
oriGoModFile.Cleanup()
// Format will not return error, so ignore the returned error
// func (f *File) Format() ([]byte, error) {
// return Format(f.Syntax), nil
// }
newModFile, _ = oriGoModFile.Format()
updateFlag = false
for index := range oriGoModFile.Replace {
replace := oriGoModFile.Replace[index]
oldPath := replace.Old.Path
oldVersion := replace.Old.Version
newPath := replace.New.Path
newVersion := replace.New.Version
// replace to a local filesystem does not have a version
// absolute path no need to rewrite
if newVersion == "" && !filepath.IsAbs(newPath) {
var absPath string
fullPath := filepath.Join(b.CurModProjectDir, newPath)
absPath, _ = filepath.Abs(fullPath)
// DropReplace & AddReplace will not return error
// so no need to check the error
_ = oriGoModFile.DropReplace(oldPath, oldVersion)
_ = oriGoModFile.AddReplace(oldPath, oldVersion, absPath, newVersion)
updateFlag = true
}
}
oriGoModFile.Cleanup()
// Format will not return error, so ignore the returned error
// func (f *File) Format() ([]byte, error) {
// 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)
}
b.IsModEdit = true
}
return
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)
}
b.IsModEdit = true
}
return
}

View File

@ -14,83 +14,83 @@
package client
import (
"encoding/json"
"fmt"
"os"
"encoding/json"
"fmt"
"os"
"github.com/RickLeee/goc-v2/pkg/client/rest"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/olekukonko/tablewriter"
"github.com/RickLeee/goc/v2/pkg/client/rest"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/olekukonko/tablewriter"
)
const (
DISCONNECT = 1 << iota
RPCCONNECT = 1 << iota
WATCHCONNECT = 1 << iota
DISCONNECT = 1 << iota
RPCCONNECT = 1 << iota
WATCHCONNECT = 1 << iota
)
func ListAgents(host string, ids []string, wide, isJson bool) {
gocClient := rest.NewV2Client(host)
gocClient := rest.NewV2Client(host)
agents, err := gocClient.Agent().Get(ids)
agents, err := gocClient.Agent().Get(ids)
if err != nil {
log.Fatalf("cannot get agent list from goc server: %v", err)
}
table := tablewriter.NewWriter(os.Stdout)
if isJson {
goto asJson
}
if err != nil {
log.Fatalf("cannot get agent list from goc server: %v", err)
}
table := tablewriter.NewWriter(os.Stdout)
if isJson {
goto asJson
}
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding(" ") // pad with 3 blank spaces
table.SetNoWhiteSpace(true)
table.SetReflowDuringAutoWrap(false)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoWrapText(false)
if wide {
table.SetHeader([]string{"ID", "STATUS", "REMOTEIP", "HOSTNAME", "PID", "CMD", "EXTRA"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
} else {
table.SetHeader([]string{"ID", "STATUS", "REMOTEIP", "CMD"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
}
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding(" ") // pad with 3 blank spaces
table.SetNoWhiteSpace(true)
table.SetReflowDuringAutoWrap(false)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoWrapText(false)
if wide {
table.SetHeader([]string{"ID", "STATUS", "REMOTEIP", "HOSTNAME", "PID", "CMD", "EXTRA"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
} else {
table.SetHeader([]string{"ID", "STATUS", "REMOTEIP", "CMD"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
}
asJson:
for _, agent := range agents {
var status string
if agent.Status == DISCONNECT {
status = "DISCONNECT"
} else if agent.Status&(RPCCONNECT|WATCHCONNECT) > 0 {
status = "CONNECT"
}
agent.StatusStr = status
if !isJson {
if wide {
table.Append([]string{agent.Id, status, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine, agent.Extra})
} else {
preLen := len(agent.Id) + len(agent.RemoteIP) + 9
table.Append([]string{agent.Id, status, agent.RemoteIP, getSimpleCmdLine(preLen, agent.CmdLine)})
}
}
}
if !isJson {
table.Render()
} else {
b, _ := json.Marshal(agents)
fmt.Fprint(os.Stdout, string(b))
}
for _, agent := range agents {
var status string
if agent.Status == DISCONNECT {
status = "DISCONNECT"
} else if agent.Status&(RPCCONNECT|WATCHCONNECT) > 0 {
status = "CONNECT"
}
agent.StatusStr = status
if !isJson {
if wide {
table.Append([]string{agent.Id, status, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine, agent.Extra})
} else {
preLen := len(agent.Id) + len(agent.RemoteIP) + 9
table.Append([]string{agent.Id, status, agent.RemoteIP, getSimpleCmdLine(preLen, agent.CmdLine)})
}
}
}
if !isJson {
table.Render()
} else {
b, _ := json.Marshal(agents)
fmt.Fprint(os.Stdout, string(b))
}
}
func DeleteAgents(host string, ids []string) {
gocClient := rest.NewV2Client(host)
gocClient := rest.NewV2Client(host)
err := gocClient.Agent().Delete(ids)
err := gocClient.Agent().Delete(ids)
if err != nil {
log.Fatalf("cannot delete agents from goc server: %v", err)
}
if err != nil {
log.Fatalf("cannot delete agents from goc server: %v", err)
}
}

View File

@ -14,198 +14,198 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"golang.org/x/term"
"golang.org/x/term"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/olekukonko/tablewriter"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/olekukonko/tablewriter"
)
// Action provides methods to contact with the covered agent under test
type Action interface {
ListAgents(bool)
Profile(string)
ListAgents(bool)
Profile(string)
}
const (
// CoverAgentsListAPI list all the registered agents
CoverAgentsListAPI = "/v2/rpcagents"
//CoverProfileAPI is provided by the covered service to get profiles
CoverProfileAPI = "/v2/cover/profile"
// CoverAgentsListAPI list all the registered agents
CoverAgentsListAPI = "/v2/rpcagents"
//CoverProfileAPI is provided by the covered service to get profiles
CoverProfileAPI = "/v2/cover/profile"
)
type client struct {
Host string
client *http.Client
Host string
client *http.Client
}
// gocListAgents response of the list request
type gocListAgents struct {
Items []gocCoveredAgent `json:"items"`
Items []gocCoveredAgent `json:"items"`
}
// gocCoveredAgent represents a covered client
type gocCoveredAgent struct {
Id string `json:"id"`
RemoteIP string `json:"remoteip"`
Hostname string `json:"hostname"`
CmdLine string `json:"cmdline"`
Pid string `json:"pid"`
Id string `json:"id"`
RemoteIP string `json:"remoteip"`
Hostname string `json:"hostname"`
CmdLine string `json:"cmdline"`
Pid string `json:"pid"`
}
type gocProfile struct {
Profile string `json:"profile"`
Profile string `json:"profile"`
}
// NewWorker creates a worker to contact with host
func NewWorker(host string) Action {
_, err := url.ParseRequestURI(host)
if err != nil {
log.Fatalf("parse url %s failed, err: %v", host, err)
}
return &client{
Host: host,
client: http.DefaultClient,
}
_, err := url.ParseRequestURI(host)
if err != nil {
log.Fatalf("parse url %s failed, err: %v", host, err)
}
return &client{
Host: host,
client: http.DefaultClient,
}
}
// ListAgents Deprecated
func (c *client) ListAgents(wide bool) {
u := fmt.Sprintf("%s%s", c.Host, CoverAgentsListAPI)
_, body, err := c.do("GET", u, "", nil)
if err != nil && isNetworkError(err) {
_, body, err = c.do("GET", u, "", nil)
}
if err != nil {
log.Fatalf("goc list failed: %v", err)
}
agents := gocListAgents{}
err = json.Unmarshal(body, &agents)
if err != nil {
log.Fatalf("goc list failed: json unmarshal failed: %v", err)
}
table := tablewriter.NewWriter(os.Stdout)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding(" ") // pad with 3 blank spaces
table.SetNoWhiteSpace(true)
table.SetReflowDuringAutoWrap(false)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoWrapText(false)
if wide {
table.SetHeader([]string{"ID", "REMOTEIP", "HOSTNAME", "PID", "CMD"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
} else {
table.SetHeader([]string{"ID", "REMOTEIP", "CMD"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
}
for _, agent := range agents.Items {
if wide {
table.Append([]string{agent.Id, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine})
} else {
preLen := len(agent.Id) + len(agent.RemoteIP) + 9
table.Append([]string{agent.Id, agent.RemoteIP, getSimpleCmdLine(preLen, agent.CmdLine)})
}
}
table.Render()
return
u := fmt.Sprintf("%s%s", c.Host, CoverAgentsListAPI)
_, body, err := c.do("GET", u, "", nil)
if err != nil && isNetworkError(err) {
_, body, err = c.do("GET", u, "", nil)
}
if err != nil {
log.Fatalf("goc list failed: %v", err)
}
agents := gocListAgents{}
err = json.Unmarshal(body, &agents)
if err != nil {
log.Fatalf("goc list failed: json unmarshal failed: %v", err)
}
table := tablewriter.NewWriter(os.Stdout)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding(" ") // pad with 3 blank spaces
table.SetNoWhiteSpace(true)
table.SetReflowDuringAutoWrap(false)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoWrapText(false)
if wide {
table.SetHeader([]string{"ID", "REMOTEIP", "HOSTNAME", "PID", "CMD"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
} else {
table.SetHeader([]string{"ID", "REMOTEIP", "CMD"})
table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT})
}
for _, agent := range agents.Items {
if wide {
table.Append([]string{agent.Id, agent.RemoteIP, agent.Hostname, agent.Pid, agent.CmdLine})
} else {
preLen := len(agent.Id) + len(agent.RemoteIP) + 9
table.Append([]string{agent.Id, agent.RemoteIP, getSimpleCmdLine(preLen, agent.CmdLine)})
}
}
table.Render()
return
}
func (c *client) Profile(output string) {
u := fmt.Sprintf("%s%s", c.Host, CoverProfileAPI)
u := fmt.Sprintf("%s%s", c.Host, CoverProfileAPI)
res, profile, err := c.do("GET", u, "application/json", nil)
if err != nil && isNetworkError(err) {
res, profile, err = c.do("GET", u, "application/json", nil)
}
res, profile, err := c.do("GET", u, "application/json", nil)
if err != nil && isNetworkError(err) {
res, profile, err = c.do("GET", u, "application/json", nil)
}
if err == nil && res.StatusCode != 200 {
log.Fatalf(string(profile))
}
var profileText gocProfile
err = json.Unmarshal(profile, &profileText)
if err != nil {
log.Fatalf("profile unmarshal failed: %v", err)
}
if output == "" {
fmt.Fprint(os.Stdout, profileText.Profile)
} else {
var dir, filename string = filepath.Split(output)
if dir != "" {
err = os.MkdirAll(dir, os.ModePerm)
if err != nil {
log.Fatalf("failed to create directory %s, err:%v", dir, err)
}
}
if filename == "" {
output += "coverage.cov"
}
if err == nil && res.StatusCode != 200 {
log.Fatalf(string(profile))
}
var profileText gocProfile
err = json.Unmarshal(profile, &profileText)
if err != nil {
log.Fatalf("profile unmarshal failed: %v", err)
}
if output == "" {
fmt.Fprint(os.Stdout, profileText.Profile)
} else {
var dir, filename string = filepath.Split(output)
if dir != "" {
err = os.MkdirAll(dir, os.ModePerm)
if err != nil {
log.Fatalf("failed to create directory %s, err:%v", dir, err)
}
}
if filename == "" {
output += "coverage.cov"
}
f, err := os.Create(output)
if err != nil {
log.Fatalf("failed to create file %s, err:%v", output, err)
}
defer f.Close()
_, err = io.Copy(f, bytes.NewReader([]byte(profileText.Profile)))
if err != nil {
log.Fatalf("failed to write file: %v, err: %v", output, err)
}
}
f, err := os.Create(output)
if err != nil {
log.Fatalf("failed to create file %s, err:%v", output, err)
}
defer f.Close()
_, err = io.Copy(f, bytes.NewReader([]byte(profileText.Profile)))
if err != nil {
log.Fatalf("failed to write file: %v, err: %v", output, err)
}
}
}
// getSimpleCmdLine
func getSimpleCmdLine(preLen int, cmdLine string) string {
pathLen := len(cmdLine)
width, _, err := term.GetSize(int(os.Stdin.Fd()))
if err != nil || width <= preLen+16 {
width = 16 + preLen // show at least 16 words of the command
}
if pathLen > width-preLen {
return cmdLine[:width-preLen]
}
return cmdLine
pathLen := len(cmdLine)
width, _, err := term.GetSize(int(os.Stdin.Fd()))
if err != nil || width <= preLen+16 {
width = 16 + preLen // show at least 16 words of the command
}
if pathLen > width-preLen {
return cmdLine[:width-preLen]
}
return cmdLine
}
func (c *client) do(method, url, contentType string, body io.Reader) (*http.Response, []byte, error) {
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, nil, err
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, nil, err
}
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
res, err := c.client.Do(req)
if err != nil {
return nil, nil, err
}
defer res.Body.Close()
res, err := c.client.Do(req)
if err != nil {
return nil, nil, err
}
defer res.Body.Close()
responseBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return res, nil, err
}
return res, responseBody, nil
responseBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return res, nil, err
}
return res, responseBody, nil
}
func isNetworkError(err error) bool {
if err == io.EOF {
return true
}
_, ok := err.(net.Error)
return ok
if err == io.EOF {
return true
}
_, ok := err.(net.Error)
return ok
}

View File

@ -14,61 +14,61 @@
package client
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"github.com/RickLeee/goc-v2/pkg/client/rest"
"github.com/RickLeee/goc-v2/pkg/client/rest/profile"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/client/rest"
"github.com/RickLeee/goc/v2/pkg/client/rest/profile"
"github.com/RickLeee/goc/v2/pkg/log"
)
func GetProfile(host string, ids []string, skips []string, extra string, output string, need []string) {
gocClient := rest.NewV2Client(host)
gocClient := rest.NewV2Client(host)
profiles, err := gocClient.Profile().Get(ids,
profile.WithPackagePattern(skips),
profile.WithExtraPattern(extra),
profile.WithNeed(need))
if err != nil {
log.Fatalf("fail to get profile from the goc server: %v, response: %v", err, profiles)
}
profiles, err := gocClient.Profile().Get(ids,
profile.WithPackagePattern(skips),
profile.WithExtraPattern(extra),
profile.WithNeed(need))
if err != nil {
log.Fatalf("fail to get profile from the goc server: %v, response: %v", err, profiles)
}
if output == "" {
fmt.Fprint(os.Stdout, profiles)
} else {
var dir, filename string = filepath.Split(output)
if dir != "" {
err = os.MkdirAll(dir, os.ModePerm)
if err != nil {
log.Fatalf("failed to create directory %s, err:%v", dir, err)
}
}
if filename == "" {
output += "coverage.cov"
}
if output == "" {
fmt.Fprint(os.Stdout, profiles)
} else {
var dir, filename string = filepath.Split(output)
if dir != "" {
err = os.MkdirAll(dir, os.ModePerm)
if err != nil {
log.Fatalf("failed to create directory %s, err:%v", dir, err)
}
}
if filename == "" {
output += "coverage.cov"
}
f, err := os.Create(output)
if err != nil {
log.Fatalf("failed to create file %s, err:%v", output, err)
}
defer f.Close()
_, err = io.Copy(f, bytes.NewReader([]byte(profiles)))
if err != nil {
log.Fatalf("failed to write file: %v, err: %v", output, err)
}
}
f, err := os.Create(output)
if err != nil {
log.Fatalf("failed to create file %s, err:%v", output, err)
}
defer f.Close()
_, err = io.Copy(f, bytes.NewReader([]byte(profiles)))
if err != nil {
log.Fatalf("failed to write file: %v, err: %v", output, err)
}
}
}
func ClearProfile(host string, ids []string, extra string) {
gocClient := rest.NewV2Client(host)
gocClient := rest.NewV2Client(host)
err := gocClient.Profile().Delete(ids,
profile.WithExtraPattern(extra))
err := gocClient.Profile().Delete(ids,
profile.WithExtraPattern(extra))
if err != nil {
log.Fatalf("fail to clear the profile: %v", err)
}
if err != nil {
log.Fatalf("fail to clear the profile: %v", err)
}
}

View File

@ -14,26 +14,26 @@
package rest
import (
"github.com/RickLeee/goc-v2/pkg/client/rest/agent"
"github.com/RickLeee/goc-v2/pkg/client/rest/profile"
"github.com/go-resty/resty/v2"
"github.com/RickLeee/goc/v2/pkg/client/rest/agent"
"github.com/RickLeee/goc/v2/pkg/client/rest/profile"
"github.com/go-resty/resty/v2"
)
// V2Client provides methods contact with the covered agent under test
type V2Client struct {
rest *resty.Client
rest *resty.Client
}
func NewV2Client(host string) *V2Client {
return &V2Client{
rest: resty.New().SetHostURL("http://" + host),
}
return &V2Client{
rest: resty.New().SetHostURL("http://" + host),
}
}
func (c *V2Client) Agent() agent.AgentInterface {
return agent.NewAgentsClient(c.rest)
return agent.NewAgentsClient(c.rest)
}
func (c *V2Client) Profile() profile.ProfileInterface {
return profile.NewProfileClient(c.rest)
return profile.NewProfileClient(c.rest)
}

View File

@ -14,390 +14,390 @@
package server
import (
"bytes"
"fmt"
"net/http"
"regexp"
"strings"
"sync"
"time"
"bytes"
"fmt"
"net/http"
"regexp"
"strings"
"sync"
"time"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/gin-gonic/gin"
"golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/gin-gonic/gin"
"golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov"
)
// listAgents return all service informations
func (gs *gocServer) listAgents(c *gin.Context) {
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
agents := make([]*gocCoveredAgent, 0)
agents := make([]*gocCoveredAgent, 0)
gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids
if !ifInIdMap(key.(string)) {
return true
}
gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids
if !ifInIdMap(key.(string)) {
return true
}
agent, ok := value.(*gocCoveredAgent)
if !ok {
return false
}
agents = append(agents, agent)
return true
})
agent, ok := value.(*gocCoveredAgent)
if !ok {
return false
}
agents = append(agents, agent)
return true
})
c.JSON(http.StatusOK, gin.H{
"items": agents,
})
c.JSON(http.StatusOK, gin.H{
"items": agents,
})
}
func (gs *gocServer) removeAgents(c *gin.Context) {
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
errs := ""
gs.agents.Range(func(key, value interface{}) bool {
errs := ""
gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids
id := key.(string)
if !ifInIdMap(id) {
return true
}
// check if id is in the query ids
id := key.(string)
if !ifInIdMap(id) {
return true
}
agent, ok := value.(*gocCoveredAgent)
if !ok {
return false
}
agent, ok := value.(*gocCoveredAgent)
if !ok {
return false
}
err := gs.removeAgentFromStore(id)
if err != nil {
log.Errorf("fail to remove agent: %v", id)
err := fmt.Errorf("fail to remove agent: %v, err: %v", id, err)
errs = errs + err.Error()
return true
}
agent.closeConnection()
gs.agents.Delete(key)
err := gs.removeAgentFromStore(id)
if err != nil {
log.Errorf("fail to remove agent: %v", id)
err := fmt.Errorf("fail to remove agent: %v, err: %v", id, err)
errs = errs + err.Error()
return true
}
agent.closeConnection()
gs.agents.Delete(key)
return true
})
return true
})
if errs != "" {
c.JSON(http.StatusInternalServerError, gin.H{
"msg": errs,
})
} else {
c.JSON(http.StatusOK, nil)
}
if errs != "" {
c.JSON(http.StatusInternalServerError, gin.H{
"msg": errs,
})
} else {
c.JSON(http.StatusOK, nil)
}
}
// getProfiles get and merge all agents' informations
//
// it is synchronous
func (gs *gocServer) getProfiles(c *gin.Context) {
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
skippatternRaw := c.Query("skippattern")
var skippattern []string
if skippatternRaw != "" {
skippattern = strings.Split(skippatternRaw, ",")
}
neerpatternRaw := c.Query("needpattern")
var neerpattern []string
if neerpatternRaw != "" {
neerpattern = strings.Split(neerpatternRaw, ",")
}
skippatternRaw := c.Query("skippattern")
var skippattern []string
if skippatternRaw != "" {
skippattern = strings.Split(skippatternRaw, ",")
}
neerpatternRaw := c.Query("needpattern")
var neerpattern []string
if neerpatternRaw != "" {
neerpattern = strings.Split(neerpatternRaw, ",")
}
extra := c.Query("extra")
isExtra := filterExtra(extra)
extra := c.Query("extra")
isExtra := filterExtra(extra)
var mu sync.Mutex
var wg sync.WaitGroup
var mu sync.Mutex
var wg sync.WaitGroup
mergedProfiles := make([][]*cover.Profile, 0)
mergedProfiles := make([][]*cover.Profile, 0)
gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids
if !ifInIdMap(key.(string)) {
// not in
return true
}
gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids
if !ifInIdMap(key.(string)) {
// not in
return true
}
agent, ok := value.(*gocCoveredAgent)
if !ok {
return false
}
agent, ok := value.(*gocCoveredAgent)
if !ok {
return false
}
// check if extra matches
if !isExtra(agent.Extra) {
// not match
return true
}
// check if extra matches
if !isExtra(agent.Extra) {
// not match
return true
}
wg.Add(1)
// 并发 rpc且每个 rpc 设超时时间 10 second
go func() {
defer wg.Done()
wg.Add(1)
// 并发 rpc且每个 rpc 设超时时间 10 second
go func() {
defer wg.Done()
timeout := time.Duration(10 * time.Second)
done := make(chan error, 1)
timeout := time.Duration(10 * time.Second)
done := make(chan error, 1)
var req ProfileReq = "getprofile"
var res ProfileRes
go func() {
// lock-free
rpc := agent.rpc
if rpc == nil || agent.Status == DISCONNECT {
done <- nil
return
}
err := agent.rpc.Call("GocAgent.GetProfile", req, &res)
if err != nil {
log.Errorf("fail to get profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
}
done <- err
}()
var req ProfileReq = "getprofile"
var res ProfileRes
go func() {
// lock-free
rpc := agent.rpc
if rpc == nil || agent.Status == DISCONNECT {
done <- nil
return
}
err := agent.rpc.Call("GocAgent.GetProfile", req, &res)
if err != nil {
log.Errorf("fail to get profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
}
done <- err
}()
select {
// rpc 超时
case <-time.After(timeout):
log.Warnf("rpc call timeout: %v", agent.Hostname)
// 关闭链接
agent.closeRpcConnOnce()
case err := <-done:
// 调用 rpc 发生错误
if err != nil {
// 关闭链接
agent.closeRpcConnOnce()
}
}
// append profile
profile, err := convertProfile([]byte(res))
if err != nil {
log.Errorf("fail to convert the received profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
// 关闭链接
agent.closeRpcConnOnce()
return
}
select {
// rpc 超时
case <-time.After(timeout):
log.Warnf("rpc call timeout: %v", agent.Hostname)
// 关闭链接
agent.closeRpcConnOnce()
case err := <-done:
// 调用 rpc 发生错误
if err != nil {
// 关闭链接
agent.closeRpcConnOnce()
}
}
// append profile
profile, err := convertProfile([]byte(res))
if err != nil {
log.Errorf("fail to convert the received profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
// 关闭链接
agent.closeRpcConnOnce()
return
}
// check if skippattern matches
newProfile := filterProfileByPattern(skippattern, neerpattern, profile)
// check if skippattern matches
newProfile := filterProfileByPattern(skippattern, neerpattern, profile)
mu.Lock()
defer mu.Unlock()
mergedProfiles = append(mergedProfiles, newProfile)
}()
mu.Lock()
defer mu.Unlock()
mergedProfiles = append(mergedProfiles, newProfile)
}()
return true
})
return true
})
// 一直等待并发的 rpc 都回应
wg.Wait()
// 一直等待并发的 rpc 都回应
wg.Wait()
merged, err := cov.MergeMultipleProfiles(mergedProfiles)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
})
return
}
merged, err := cov.MergeMultipleProfiles(mergedProfiles)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
})
return
}
var buff bytes.Buffer
err = cov.DumpProfile(merged, &buff)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
})
return
}
var buff bytes.Buffer
err = cov.DumpProfile(merged, &buff)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"profile": buff.String(),
})
c.JSON(http.StatusOK, gin.H{
"profile": buff.String(),
})
}
// resetProfiles reset all profiles in agent
//
// it is async, the function will return immediately
func (gs *gocServer) resetProfiles(c *gin.Context) {
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
idQuery := c.Query("id")
ifInIdMap := idMaps(idQuery)
extra := c.Query("extra")
isExtra := filterExtra(extra)
extra := c.Query("extra")
isExtra := filterExtra(extra)
gs.agents.Range(func(key, value interface{}) bool {
gs.agents.Range(func(key, value interface{}) bool {
// check if id is in the query ids
if !ifInIdMap(key.(string)) {
// not in
return true
}
// check if id is in the query ids
if !ifInIdMap(key.(string)) {
// not in
return true
}
agent, ok := value.(*gocCoveredAgent)
if !ok {
return false
}
agent, ok := value.(*gocCoveredAgent)
if !ok {
return false
}
// check if extra matches
if !isExtra(agent.Extra) {
// not match
return true
}
// check if extra matches
if !isExtra(agent.Extra) {
// not match
return true
}
var req ProfileReq = "resetprofile"
var res ProfileRes
go func() {
// lock-free
rpc := agent.rpc
if rpc == nil || agent.Status == DISCONNECT {
return
}
err := rpc.Call("GocAgent.ResetProfile", req, &res)
if err != nil {
log.Errorf("fail to reset profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
// 关闭链接
agent.closeRpcConnOnce()
}
}()
var req ProfileReq = "resetprofile"
var res ProfileRes
go func() {
// lock-free
rpc := agent.rpc
if rpc == nil || agent.Status == DISCONNECT {
return
}
err := rpc.Call("GocAgent.ResetProfile", req, &res)
if err != nil {
log.Errorf("fail to reset profile from: %v, reasson: %v. let's close the connection", agent.Id, err)
// 关闭链接
agent.closeRpcConnOnce()
}
}()
return true
})
return true
})
}
// watchProfileUpdate watch the profile change
//
// any profile change will be updated on this websocket connection.
func (gs *gocServer) watchProfileUpdate(c *gin.Context) {
// upgrade to websocket
ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Errorf("fail to establish websocket connection with watch client: %v", err)
c.JSON(http.StatusInternalServerError, nil)
}
// upgrade to websocket
ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Errorf("fail to establish websocket connection with watch client: %v", err)
c.JSON(http.StatusInternalServerError, nil)
}
log.Infof("watch client connected")
log.Infof("watch client connected")
id := time.Now().String()
gwc := &gocWatchClient{
ws: ws,
exitCh: make(chan int),
}
gs.watchClients.Store(id, gwc)
// send close msg and close ws connection
defer func() {
gs.watchClients.Delete(id)
ws.Close()
gwc.once.Do(func() { close(gwc.exitCh) })
log.Infof("watch client disconnected")
}()
id := time.Now().String()
gwc := &gocWatchClient{
ws: ws,
exitCh: make(chan int),
}
gs.watchClients.Store(id, gwc)
// send close msg and close ws connection
defer func() {
gs.watchClients.Delete(id)
ws.Close()
gwc.once.Do(func() { close(gwc.exitCh) })
log.Infof("watch client disconnected")
}()
// set pong handler
ws.SetReadDeadline(time.Now().Add(PongWait))
ws.SetPongHandler(func(string) error {
ws.SetReadDeadline(time.Now().Add(PongWait))
return nil
})
// set pong handler
ws.SetReadDeadline(time.Now().Add(PongWait))
ws.SetPongHandler(func(string) error {
ws.SetReadDeadline(time.Now().Add(PongWait))
return nil
})
// set ping goroutine to ping every PingWait time
go func() {
ticker := time.NewTicker(PingWait)
defer ticker.Stop()
// set ping goroutine to ping every PingWait time
go func() {
ticker := time.NewTicker(PingWait)
defer ticker.Stop()
for range ticker.C {
if err := gs.wsping(ws, PongWait); err != nil {
break
}
}
for range ticker.C {
if err := gs.wsping(ws, PongWait); err != nil {
break
}
}
gwc.once.Do(func() { close(gwc.exitCh) })
}()
gwc.once.Do(func() { close(gwc.exitCh) })
}()
<-gwc.exitCh
<-gwc.exitCh
}
func filterProfileByPattern(skippattern []string, needpattern []string, profiles []*cover.Profile) []*cover.Profile {
var out = make([]*cover.Profile, 0)
var skipOut = make([]*cover.Profile, 0)
if len(skippattern) == 0 && len(needpattern) == 0 {
return profiles
}
if len(skippattern) != 0 {
for _, profile := range profiles {
skip := false
for _, pattern := range skippattern {
if strings.Contains(profile.FileName, pattern) {
skip = true
break
}
}
var out = make([]*cover.Profile, 0)
var skipOut = make([]*cover.Profile, 0)
if len(skippattern) == 0 && len(needpattern) == 0 {
return profiles
}
if len(skippattern) != 0 {
for _, profile := range profiles {
skip := false
for _, pattern := range skippattern {
if strings.Contains(profile.FileName, pattern) {
skip = true
break
}
}
if !skip {
skipOut = append(skipOut, profile)
}
}
} else {
skipOut = profiles
}
log.Infof("skipOut len: %v", len(skipOut))
if len(needpattern) == 0 {
return skipOut
}
if !skip {
skipOut = append(skipOut, profile)
}
}
} else {
skipOut = profiles
}
log.Infof("skipOut len: %v", len(skipOut))
if len(needpattern) == 0 {
return skipOut
}
for _, profile := range skipOut {
need := false
for _, pattern := range needpattern {
if strings.Contains(profile.FileName, pattern) {
need = true
break
}
}
if need {
out = append(out, profile)
}
}
log.Infof("need out len: %v", len(out))
for _, profile := range skipOut {
need := false
for _, pattern := range needpattern {
if strings.Contains(profile.FileName, pattern) {
need = true
break
}
}
if need {
out = append(out, profile)
}
}
log.Infof("need out len: %v", len(out))
return out
return out
}
func idMaps(idQuery string) func(key string) bool {
idMap := make(map[string]bool)
if len(strings.TrimSpace(idQuery)) == 0 {
} else {
ids := strings.Split(idQuery, ",")
for _, id := range ids {
idMap[id] = true
}
}
idMap := make(map[string]bool)
if len(strings.TrimSpace(idQuery)) == 0 {
} else {
ids := strings.Split(idQuery, ",")
for _, id := range ids {
idMap[id] = true
}
}
inIdMaps := func(key string) bool {
// if no id in query, then all id agent will be return
if len(idMap) == 0 {
return true
}
// other
_, ok := idMap[key]
if !ok {
return false
} else {
return true
}
}
inIdMaps := func(key string) bool {
// if no id in query, then all id agent will be return
if len(idMap) == 0 {
return true
}
// other
_, ok := idMap[key]
if !ok {
return false
} else {
return true
}
}
return inIdMaps
return inIdMaps
}
func filterExtra(extraPattern string) func(string) bool {
re := regexp.MustCompile(extraPattern)
re := regexp.MustCompile(extraPattern)
return func(extra string) bool {
return re.Match([]byte(extra))
}
return func(extra string) bool {
return re.Match([]byte(extra))
}
}

View File

@ -14,16 +14,16 @@
package server
import (
"crypto/sha256"
"fmt"
"net/http"
"net/rpc"
"net/rpc/jsonrpc"
"sync"
"time"
"crypto/sha256"
"fmt"
"net/http"
"net/rpc"
"net/rpc/jsonrpc"
"sync"
"time"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/gin-gonic/gin"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/gin-gonic/gin"
)
// serveRpcStream holds connection between goc server and agent.
@ -32,114 +32,114 @@ import (
//
// 2. 每个链接的 goc agent 作为 rpc 服务端
func (gs *gocServer) serveRpcStream(c *gin.Context) {
// 检查插桩服务上报的信息
rpcRemoteIP, _ := c.RemoteIP()
id := c.Query("id")
token := c.Query("token")
// 检查插桩服务上报的信息
rpcRemoteIP, _ := c.RemoteIP()
id := c.Query("id")
token := c.Query("token")
rawagent, ok := gs.agents.Load(id)
if !ok {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "agent not registered",
"code": 1,
})
return
}
rawagent, ok := gs.agents.Load(id)
if !ok {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "agent not registered",
"code": 1,
})
return
}
agent := rawagent.(*gocCoveredAgent)
if agent.Token != token {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "register token not match",
"code": 1,
})
return
}
agent := rawagent.(*gocCoveredAgent)
if agent.Token != token {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "register token not match",
"code": 1,
})
return
}
// 更新 agent 信息
agent.RpcRemoteIP = rpcRemoteIP.String()
agent.exitCh = make(chan int)
agent.Status &= ^DISCONNECT // 取消 DISCONNECT 的状态
agent.Status |= RPCCONNECT // 设置为 RPC CONNECT 状态
// 注册销毁函数
var once sync.Once
agent.closeRpcConnOnce = func() {
once.Do(func() {
// 为什么只是关闭 channel其它资源如何释放
// close channel 后,本 goroutine 会进入到 defer
close(agent.exitCh)
})
}
// 更新 agent 信息
agent.RpcRemoteIP = rpcRemoteIP.String()
agent.exitCh = make(chan int)
agent.Status &= ^DISCONNECT // 取消 DISCONNECT 的状态
agent.Status |= RPCCONNECT // 设置为 RPC CONNECT 状态
// 注册销毁函数
var once sync.Once
agent.closeRpcConnOnce = func() {
once.Do(func() {
// 为什么只是关闭 channel其它资源如何释放
// close channel 后,本 goroutine 会进入到 defer
close(agent.exitCh)
})
}
// upgrade to websocket
ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Errorf("fail to establish websocket connection with rpc agent: %v", err)
c.JSON(http.StatusInternalServerError, nil)
}
// upgrade to websocket
ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Errorf("fail to establish websocket connection with rpc agent: %v", err)
c.JSON(http.StatusInternalServerError, nil)
}
// send close msg and close ws connection
defer func() {
deadline := 1 * time.Second
// 发送 close msg
gs.wsclose(ws, deadline)
time.Sleep(deadline)
// send close msg and close ws connection
defer func() {
deadline := 1 * time.Second
// 发送 close msg
gs.wsclose(ws, deadline)
time.Sleep(deadline)
// 取消 RPC CONNECT 状态
agent.Status &= ^RPCCONNECT
if agent.Status == 0 {
agent.Status = DISCONNECT
}
// 取消 RPC CONNECT 状态
agent.Status &= ^RPCCONNECT
if agent.Status == 0 {
agent.Status = DISCONNECT
}
ws.Close()
log.Infof("close rpc connection, %v", agent.Hostname)
// reset rpc client
agent.rpc = nil
}()
ws.Close()
log.Infof("close rpc connection, %v", agent.Hostname)
// reset rpc client
agent.rpc = nil
}()
// set pong handler
ws.SetReadDeadline(time.Now().Add(PongWait))
ws.SetPongHandler(func(string) error {
ws.SetReadDeadline(time.Now().Add(PongWait))
return nil
})
// set pong handler
ws.SetReadDeadline(time.Now().Add(PongWait))
ws.SetPongHandler(func(string) error {
ws.SetReadDeadline(time.Now().Add(PongWait))
return nil
})
// set ping goroutine to ping every PingWait time
go func() {
ticker := time.NewTicker(PingWait)
defer ticker.Stop()
// set ping goroutine to ping every PingWait time
go func() {
ticker := time.NewTicker(PingWait)
defer ticker.Stop()
for range ticker.C {
if err := gs.wsping(ws, PongWait); err != nil {
log.Errorf("rpc ping to %v failed: %v", agent.Hostname, err)
break
}
}
for range ticker.C {
if err := gs.wsping(ws, PongWait); err != nil {
log.Errorf("rpc ping to %v failed: %v", agent.Hostname, err)
break
}
}
agent.closeRpcConnOnce()
}()
agent.closeRpcConnOnce()
}()
log.Infof("one rpc agent established, %v, cmdline: %v, pid: %v, hostname: %v", ws.RemoteAddr(), agent.CmdLine, agent.Pid, agent.Hostname)
// new rpc agent
// 在这里 websocket server 作为 rpc 的客户端,
// 发送 rpc 请求,
// 由被插桩服务返回 rpc 应答
rwc := &ReadWriteCloser{ws: ws}
codec := jsonrpc.NewClientCodec(rwc)
log.Infof("one rpc agent established, %v, cmdline: %v, pid: %v, hostname: %v", ws.RemoteAddr(), agent.CmdLine, agent.Pid, agent.Hostname)
// new rpc agent
// 在这里 websocket server 作为 rpc 的客户端,
// 发送 rpc 请求,
// 由被插桩服务返回 rpc 应答
rwc := &ReadWriteCloser{ws: ws}
codec := jsonrpc.NewClientCodec(rwc)
agent.rpc = rpc.NewClientWithCodec(codec)
agent.rpc = rpc.NewClientWithCodec(codec)
// wait for exit
<-agent.exitCh
// wait for exit
<-agent.exitCh
}
// generateAgentId generate id based on agent's meta infomation
func (gs *gocServer) generateAgentId(args ...string) gocCliendId {
var path string
for _, arg := range args {
path += arg
}
sum := sha256.Sum256([]byte(path))
h := fmt.Sprintf("%x", sum[:6])
var path string
for _, arg := range args {
path += arg
}
sum := sha256.Sum256([]byte(path))
h := fmt.Sprintf("%x", sum[:6])
return gocCliendId(h)
return gocCliendId(h)
}

View File

@ -14,236 +14,236 @@
package server
import (
"crypto/sha256"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"net/rpc"
"strconv"
"sync"
"sync/atomic"
"time"
"crypto/sha256"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"net/rpc"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/RickLeee/goc-v2/pkg/server/store"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/RickLeee/goc/v2/pkg/server/store"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
// gocServer represents a goc server
type gocServer struct {
port int
store store.Store
port int
store store.Store
upgrader websocket.Upgrader
upgrader websocket.Upgrader
agents sync.Map
agents sync.Map
watchCh chan []byte
watchClients sync.Map
watchCh chan []byte
watchClients sync.Map
idCount int64
idL sync.Mutex
idCount int64
idL sync.Mutex
}
type gocCliendId string
const (
DISCONNECT = 1 << iota
RPCCONNECT = 1 << iota
WATCHCONNECT = 1 << iota
DISCONNECT = 1 << iota
RPCCONNECT = 1 << iota
WATCHCONNECT = 1 << iota
)
// gocCoveredAgent represents a covered client
type gocCoveredAgent struct {
Id string `json:"id"`
RpcRemoteIP string `json:"rpc_remoteip"`
WatchRemoteIP string `json:"watch_remoteip"`
Hostname string `json:"hostname"`
CmdLine string `json:"cmdline"`
Pid string `json:"pid"`
Id string `json:"id"`
RpcRemoteIP string `json:"rpc_remoteip"`
WatchRemoteIP string `json:"watch_remoteip"`
Hostname string `json:"hostname"`
CmdLine string `json:"cmdline"`
Pid string `json:"pid"`
// 用户可以选择上报一些定制信息
// 比如不同 namespace 的 statefulset POD它们的 hostname/cmdline/pid 都是一样的,
// 这时候将 extra 设置为 namespace 并上报,这个额外的信息在展示时将更友好
Extra string `json:"extra"`
// 用户可以选择上报一些定制信息
// 比如不同 namespace 的 statefulset POD它们的 hostname/cmdline/pid 都是一样的,
// 这时候将 extra 设置为 namespace 并上报,这个额外的信息在展示时将更友好
Extra string `json:"extra"`
Token string `json:"token"`
Status int `json:"status"` // 表示该 agent 是否处于 connected 状态
Token string `json:"token"`
Status int `json:"status"` // 表示该 agent 是否处于 connected 状态
rpc *rpc.Client `json:"-"`
rpc *rpc.Client `json:"-"`
exitCh chan int `json:"-"`
closeRpcConnOnce func() `json:"-"` // close rpc conn 只执行一次
closeWatchConnOnce func() `json:"-"` // close watch conn 只执行一次
exitCh chan int `json:"-"`
closeRpcConnOnce func() `json:"-"` // close rpc conn 只执行一次
closeWatchConnOnce func() `json:"-"` // close watch conn 只执行一次
}
func (agent *gocCoveredAgent) closeConnection() {
if agent.closeRpcConnOnce != nil {
agent.closeRpcConnOnce()
}
if agent.closeRpcConnOnce != nil {
agent.closeRpcConnOnce()
}
if agent.closeWatchConnOnce != nil {
agent.closeWatchConnOnce()
}
if agent.closeWatchConnOnce != nil {
agent.closeWatchConnOnce()
}
}
// api 客户端,不是 agent
type gocWatchClient struct {
ws *websocket.Conn
exitCh chan int
once sync.Once
ws *websocket.Conn
exitCh chan int
once sync.Once
}
func RunGocServerUntilExit(host string, s store.Store) error {
gs := gocServer{
store: s,
upgrader: websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
HandshakeTimeout: 45 * time.Second,
CheckOrigin: func(r *http.Request) bool {
return true
},
},
watchCh: make(chan []byte, 4096),
}
gs := gocServer{
store: s,
upgrader: websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
HandshakeTimeout: 45 * time.Second,
CheckOrigin: func(r *http.Request) bool {
return true
},
},
watchCh: make(chan []byte, 4096),
}
// 从持久化存储上恢复 agent 列表
gs.restoreAgents()
// 从持久化存储上恢复 agent 列表
gs.restoreAgents()
r := gin.Default()
v2 := r.Group("/v2")
{
v2.GET("/cover/profile", gs.getProfiles)
v2.DELETE("/cover/profile", gs.resetProfiles)
v2.GET("/agents", gs.listAgents)
v2.DELETE("/agents", gs.removeAgents)
r := gin.Default()
v2 := r.Group("/v2")
{
v2.GET("/cover/profile", gs.getProfiles)
v2.DELETE("/cover/profile", gs.resetProfiles)
v2.GET("/agents", gs.listAgents)
v2.DELETE("/agents", gs.removeAgents)
v2.GET("/cover/ws/watch", gs.watchProfileUpdate)
v2.GET("/cover/ws/watch", gs.watchProfileUpdate)
// internal use only
v2.GET("/internal/register", gs.register)
v2.GET("/internal/ws/rpcstream", gs.serveRpcStream)
v2.GET("/internal/ws/watchstream", gs.serveWatchInternalStream)
}
// internal use only
v2.GET("/internal/register", gs.register)
v2.GET("/internal/ws/rpcstream", gs.serveRpcStream)
v2.GET("/internal/ws/watchstream", gs.serveWatchInternalStream)
}
go gs.watchLoop()
return r.Run(host)
go gs.watchLoop()
return r.Run(host)
}
func (gs *gocServer) register(c *gin.Context) {
// 检查插桩服务上报的信息
hostname := c.Query("hostname")
pid := c.Query("pid")
cmdline := c.Query("cmdline")
extra := c.Query("extra")
// 检查插桩服务上报的信息
hostname := c.Query("hostname")
pid := c.Query("pid")
cmdline := c.Query("cmdline")
extra := c.Query("extra")
if hostname == "" || pid == "" || cmdline == "" {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "missing some params",
})
return
}
if hostname == "" || pid == "" || cmdline == "" {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "missing some params",
})
return
}
gs.idL.Lock()
gs.idCount++
globalId := gs.idCount
gs.idL.Unlock()
gs.idL.Lock()
gs.idCount++
globalId := gs.idCount
gs.idL.Unlock()
genToken := func(i int64) string {
now := time.Now().UnixNano()
random := rand.Int()
genToken := func(i int64) string {
now := time.Now().UnixNano()
random := rand.Int()
raw := fmt.Sprintf("%v-%v-%v", i, random, now)
sum := sha256.Sum256([]byte(raw))
h := fmt.Sprintf("%x", sum[:16])
raw := fmt.Sprintf("%v-%v-%v", i, random, now)
sum := sha256.Sum256([]byte(raw))
h := fmt.Sprintf("%x", sum[:16])
return h
}
return h
}
token := genToken(globalId)
id := strconv.Itoa(int(globalId))
token := genToken(globalId)
id := strconv.Itoa(int(globalId))
agent := &gocCoveredAgent{
Id: id,
Hostname: hostname,
Pid: pid,
CmdLine: cmdline,
Token: token,
Status: DISCONNECT,
Extra: extra,
}
agent := &gocCoveredAgent{
Id: id,
Hostname: hostname,
Pid: pid,
CmdLine: cmdline,
Token: token,
Status: DISCONNECT,
Extra: extra,
}
// 持久化
err := gs.saveAgentToStore(agent)
if err != nil {
log.Errorf("fail to save to store: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
})
}
// 维护 agent 连接
gs.agents.Store(id, agent)
// 持久化
err := gs.saveAgentToStore(agent)
if err != nil {
log.Errorf("fail to save to store: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"msg": err.Error(),
})
}
// 维护 agent 连接
gs.agents.Store(id, agent)
log.Infof("one agent registered, id: %v, cmdline: %v, pid: %v, hostname: %v", id, agent.CmdLine, agent.Pid, agent.Hostname)
log.Infof("one agent registered, id: %v, cmdline: %v, pid: %v, hostname: %v", id, agent.CmdLine, agent.Pid, agent.Hostname)
c.JSON(http.StatusOK, gin.H{
"id": id,
"token": token,
})
c.JSON(http.StatusOK, gin.H{
"id": id,
"token": token,
})
}
func (gs *gocServer) saveAgentToStore(agent *gocCoveredAgent) error {
value, err := json.Marshal(agent)
if err != nil {
return err
}
return gs.store.Set("/goc/agents/"+agent.Id, string(value))
value, err := json.Marshal(agent)
if err != nil {
return err
}
return gs.store.Set("/goc/agents/"+agent.Id, string(value))
}
func (gs *gocServer) removeAgentFromStore(id string) error {
return gs.store.Remove("/goc/agents/" + id)
return gs.store.Remove("/goc/agents/" + id)
}
func (gs *gocServer) removeAllAgentsFromStore() error {
return gs.store.RangeRemove("/goc/agents/")
return gs.store.RangeRemove("/goc/agents/")
}
func (gs *gocServer) restoreAgents() {
pattern := "/goc/agents/"
pattern := "/goc/agents/"
// ignore err, 这个 err 不需要处理,直接忽略
rawagents, _ := gs.store.Range(pattern)
// ignore err, 这个 err 不需要处理,直接忽略
rawagents, _ := gs.store.Range(pattern)
var maxId int
for _, rawagent := range rawagents {
var agent gocCoveredAgent
err := json.Unmarshal([]byte(rawagent), &agent)
if err != nil {
log.Fatalf("fail to unmarshal restore agents: %v", err)
}
var maxId int
for _, rawagent := range rawagents {
var agent gocCoveredAgent
err := json.Unmarshal([]byte(rawagent), &agent)
if err != nil {
log.Fatalf("fail to unmarshal restore agents: %v", err)
}
id, err := strconv.Atoi(agent.Id)
if err != nil {
log.Fatalf("fail to transform id to number: %v", err)
}
if maxId < id {
maxId = id
}
id, err := strconv.Atoi(agent.Id)
if err != nil {
log.Fatalf("fail to transform id to number: %v", err)
}
if maxId < id {
maxId = id
}
gs.agents.Store(agent.Id, &agent)
log.Infof("restore one agent: %v, %v from store", id, agent.RpcRemoteIP)
gs.agents.Store(agent.Id, &agent)
log.Infof("restore one agent: %v, %v from store", id, agent.RpcRemoteIP)
agent.RpcRemoteIP = ""
agent.WatchRemoteIP = ""
agent.Status = DISCONNECT
}
agent.RpcRemoteIP = ""
agent.WatchRemoteIP = ""
agent.Status = DISCONNECT
}
// 更新全局 id
atomic.StoreInt64(&gs.idCount, int64(maxId))
// 更新全局 id
atomic.StoreInt64(&gs.idCount, int64(maxId))
}

View File

@ -14,120 +14,120 @@
package server
import (
"net/http"
"sync"
"time"
"net/http"
"sync"
"time"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
func (gs *gocServer) serveWatchInternalStream(c *gin.Context) {
// 检查插桩服务上报的信息
watchRemoteIP, _ := c.RemoteIP()
id := c.Query("id")
token := c.Query("token")
// 检查插桩服务上报的信息
watchRemoteIP, _ := c.RemoteIP()
id := c.Query("id")
token := c.Query("token")
rawagent, ok := gs.agents.Load(id)
if !ok {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "agent not registered",
"code": 1,
})
return
}
rawagent, ok := gs.agents.Load(id)
if !ok {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "agent not registered",
"code": 1,
})
return
}
agent := rawagent.(*gocCoveredAgent)
if agent.Token != token {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "register token not match",
"code": 1,
})
return
}
agent := rawagent.(*gocCoveredAgent)
if agent.Token != token {
c.JSON(http.StatusBadRequest, gin.H{
"msg": "register token not match",
"code": 1,
})
return
}
// 更新 agent 信息
agent.WatchRemoteIP = watchRemoteIP.String()
agent.Status &= ^DISCONNECT // 取消 DISCONNECT 的状态
agent.Status |= WATCHCONNECT // 设置为 RPC CONNECT 状态
var once sync.Once
// 更新 agent 信息
agent.WatchRemoteIP = watchRemoteIP.String()
agent.Status &= ^DISCONNECT // 取消 DISCONNECT 的状态
agent.Status |= WATCHCONNECT // 设置为 RPC CONNECT 状态
var once sync.Once
// upgrade to websocket
ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Errorf("fail to establish websocket connection with watch agent: %v", err)
c.JSON(http.StatusInternalServerError, nil)
}
// upgrade to websocket
ws, err := gs.upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Errorf("fail to establish websocket connection with watch agent: %v", err)
c.JSON(http.StatusInternalServerError, nil)
}
// 注册销毁函数
agent.closeWatchConnOnce = func() {
once.Do(func() {
// 关闭 ws 连接后ws.ReadMessage() 会出错退出 goroutine进入 defer
ws.Close()
})
}
// 注册销毁函数
agent.closeWatchConnOnce = func() {
once.Do(func() {
// 关闭 ws 连接后ws.ReadMessage() 会出错退出 goroutine进入 defer
ws.Close()
})
}
// send close msg and close ws connection
defer func() {
// 取消 WATCH CONNECT 状态
agent.Status &= ^WATCHCONNECT
if agent.Status == 0 {
agent.Status = DISCONNECT
}
// send close msg and close ws connection
defer func() {
// 取消 WATCH CONNECT 状态
agent.Status &= ^WATCHCONNECT
if agent.Status == 0 {
agent.Status = DISCONNECT
}
agent.closeWatchConnOnce()
agent.closeWatchConnOnce()
log.Infof("close watch connection, %v", agent.Hostname)
}()
log.Infof("close watch connection, %v", agent.Hostname)
}()
// set pong handler
ws.SetReadDeadline(time.Now().Add(PongWait))
ws.SetPongHandler(func(string) error {
ws.SetReadDeadline(time.Now().Add(PongWait))
return nil
})
// set pong handler
ws.SetReadDeadline(time.Now().Add(PongWait))
ws.SetPongHandler(func(string) error {
ws.SetReadDeadline(time.Now().Add(PongWait))
return nil
})
// set ping goroutine to ping every PingWait time
go func() {
ticker := time.NewTicker(PingWait)
defer ticker.Stop()
// set ping goroutine to ping every PingWait time
go func() {
ticker := time.NewTicker(PingWait)
defer ticker.Stop()
for range ticker.C {
if err := gs.wsping(ws, PongWait); err != nil {
log.Errorf("watch ping to %v failed: %v", agent.Hostname, err)
break
}
}
}()
for range ticker.C {
if err := gs.wsping(ws, PongWait); err != nil {
log.Errorf("watch ping to %v failed: %v", agent.Hostname, err)
break
}
}
}()
log.Infof("one watch agent established, %v, cmdline: %v, pid: %v, hostname: %v", ws.RemoteAddr(), agent.CmdLine, agent.Pid, agent.Hostname)
log.Infof("one watch agent established, %v, cmdline: %v, pid: %v, hostname: %v", ws.RemoteAddr(), agent.CmdLine, agent.Pid, agent.Hostname)
for {
mt, message, err := ws.ReadMessage()
if err != nil {
log.Errorf("read from %v: %v", agent.Hostname, err)
break
}
if mt == websocket.TextMessage {
gs.watchCh <- message
}
}
for {
mt, message, err := ws.ReadMessage()
if err != nil {
log.Errorf("read from %v: %v", agent.Hostname, err)
break
}
if mt == websocket.TextMessage {
gs.watchCh <- message
}
}
}
func (gs *gocServer) watchLoop() {
for {
msg := <-gs.watchCh
gs.watchClients.Range(func(key, value interface{}) bool {
// 这里是客户端的 ws 连接,不是 agent ws 连接
gwc := value.(*gocWatchClient)
err := gwc.ws.WriteMessage(websocket.TextMessage, msg)
if err != nil {
gwc.ws.Close()
gwc.once.Do(func() { close(gwc.exitCh) })
}
for {
msg := <-gs.watchCh
gs.watchClients.Range(func(key, value interface{}) bool {
// 这里是客户端的 ws 连接,不是 agent ws 连接
gwc := value.(*gocWatchClient)
err := gwc.ws.WriteMessage(websocket.TextMessage, msg)
if err != nil {
gwc.ws.Close()
gwc.once.Do(func() { close(gwc.exitCh) })
}
return true
})
}
return true
})
}
}

View File

@ -14,26 +14,26 @@
package watch
import (
"fmt"
"fmt"
"github.com/RickLeee/goc-v2/pkg/log"
"github.com/gorilla/websocket"
"github.com/RickLeee/goc/v2/pkg/log"
"github.com/gorilla/websocket"
)
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)
}
defer c.Close()
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)
}
defer c.Close()
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Fatalf("cannot read message: %v", err)
}
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Fatalf("cannot read message: %v", err)
}
log.Infof("profile update: %v", string(message))
}
log.Infof("profile update: %v", string(message))
}
}