From e62870e2680ba8248cb662e61c6167659fbeb168 Mon Sep 17 00:00:00 2001 From: Fyn <53678344+fynxiu@users.noreply.github.com> Date: Mon, 18 Apr 2022 20:36:41 +0800 Subject: [PATCH] feat(goctl): go work multi-module support (#1800) * feat(goctl): go work multi-module support Resolve: #1793 * chore: print log when getting project ctx fails --- tools/goctl/rpc/execx/execx.go | 3 ++ tools/goctl/util/ctx/context.go | 2 + tools/goctl/util/ctx/gomod.go | 43 ++++++++++++++--- tools/goctl/util/ctx/gomod_test.go | 74 ++++++++++++++++++++++++++++++ tools/goctl/util/ctx/modcheck.go | 13 ++---- 5 files changed, 118 insertions(+), 17 deletions(-) diff --git a/tools/goctl/rpc/execx/execx.go b/tools/goctl/rpc/execx/execx.go index 5b5e10bb..3e87923f 100644 --- a/tools/goctl/rpc/execx/execx.go +++ b/tools/goctl/rpc/execx/execx.go @@ -12,6 +12,9 @@ import ( "github.com/zeromicro/go-zero/tools/goctl/vars" ) +// RunFunc defines a function type of Run function +type RunFunc func(string, string, ...*bytes.Buffer) (string, error) + // Run provides the execution of shell scripts in golang, // which can support macOS, Windows, and Linux operating systems. // Other operating systems are currently not supported diff --git a/tools/goctl/util/ctx/context.go b/tools/goctl/util/ctx/context.go index 25f8d5a8..1eba01f6 100644 --- a/tools/goctl/util/ctx/context.go +++ b/tools/goctl/util/ctx/context.go @@ -2,6 +2,7 @@ package ctx import ( "errors" + "fmt" "path/filepath" "github.com/zeromicro/go-zero/tools/goctl/rpc/execx" @@ -31,6 +32,7 @@ func Prepare(workDir string) (*ProjectContext, error) { if err == nil { return ctx, nil } + fmt.Printf("get project context from workdir[%s] failed: %s\n", workDir, err) name := filepath.Base(workDir) _, err = execx.Run("go mod init "+name, workDir) diff --git a/tools/goctl/util/ctx/gomod.go b/tools/goctl/util/ctx/gomod.go index a81f7347..4c526b09 100644 --- a/tools/goctl/util/ctx/gomod.go +++ b/tools/goctl/util/ctx/gomod.go @@ -1,11 +1,14 @@ package ctx import ( + "encoding/json" "errors" + "fmt" + "io" "os" "path/filepath" + "strings" - "github.com/zeromicro/go-zero/core/jsonx" "github.com/zeromicro/go-zero/tools/goctl/rpc/execx" "github.com/zeromicro/go-zero/tools/goctl/util/pathx" ) @@ -36,16 +39,11 @@ func projectFromGoMod(workDir string) (*ProjectContext, error) { return nil, err } - data, err := execx.Run("go list -json -m", workDir) + m, err := getRealModule(workDir, execx.Run) if err != nil { return nil, err } - var m Module - err = jsonx.Unmarshal([]byte(data), &m) - if err != nil { - return nil, err - } var ret ProjectContext ret.WorkDir = workDir ret.Name = filepath.Base(m.Dir) @@ -58,3 +56,34 @@ func projectFromGoMod(workDir string) (*ProjectContext, error) { ret.Path = m.Path return &ret, nil } + +func getRealModule(workDir string, execRun execx.RunFunc) (*Module, error) { + data, err := execRun("go list -json -m", workDir) + if err != nil { + return nil, err + } + modules, err := decodePackages(strings.NewReader(data)) + if err != nil { + return nil, err + } + for _, m := range modules { + if strings.HasPrefix(workDir, m.Dir) { + return &m, nil + } + } + return nil, errors.New("no matched module") +} + +func decodePackages(rc io.Reader) ([]Module, error) { + var modules []Module + decoder := json.NewDecoder(rc) + for decoder.More() { + var m Module + if err := decoder.Decode(&m); err != nil { + return nil, fmt.Errorf("invalid module: %v", err) + } + modules = append(modules, m) + } + + return modules, nil +} diff --git a/tools/goctl/util/ctx/gomod_test.go b/tools/goctl/util/ctx/gomod_test.go index 6a433352..11c419ca 100644 --- a/tools/goctl/util/ctx/gomod_test.go +++ b/tools/goctl/util/ctx/gomod_test.go @@ -1,9 +1,11 @@ package ctx import ( + "bytes" "go/build" "os" "path/filepath" + "reflect" "testing" "github.com/stretchr/testify/assert" @@ -36,3 +38,75 @@ func TestProjectFromGoMod(t *testing.T) { assert.Equal(t, projectName, ctx.Path) assert.Equal(t, dir, ctx.Dir) } + +func Test_getRealModule(t *testing.T) { + type args struct { + workDir string + execRun execx.RunFunc + } + tests := []struct { + name string + args args + want *Module + wantErr bool + }{ + { + name: "single module", + args: args{ + workDir: "/home/foo", + execRun: func(arg, dir string, in ...*bytes.Buffer) (string, error) { + return `{ + "Path":"foo", + "Dir":"/home/foo", + "GoMod":"/home/foo/go.mod", + "GoVersion":"go1.16"}`, nil + }, + }, + want: &Module{ + Path: "foo", + Dir: "/home/foo", + GoMod: "/home/foo/go.mod", + GoVersion: "go1.16", + }, + }, + { + name: "go work multiple modules", + args: args{ + workDir: "/home/bar", + execRun: func(arg, dir string, in ...*bytes.Buffer) (string, error) { + return ` + { + "Path":"foo", + "Dir":"/home/foo", + "GoMod":"/home/foo/go.mod", + "GoVersion":"go1.18" + } + { + "Path":"bar", + "Dir":"/home/bar", + "GoMod":"/home/bar/go.mod", + "GoVersion":"go1.18" + }`, nil + }, + }, + want: &Module{ + Path: "bar", + Dir: "/home/bar", + GoMod: "/home/bar/go.mod", + GoVersion: "go1.18", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getRealModule(tt.args.workDir, tt.args.execRun) + if (err != nil) != tt.wantErr { + t.Errorf("getRealModule() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("getRealModule() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/tools/goctl/util/ctx/modcheck.go b/tools/goctl/util/ctx/modcheck.go index 9f74c04f..b2f37d2d 100644 --- a/tools/goctl/util/ctx/modcheck.go +++ b/tools/goctl/util/ctx/modcheck.go @@ -4,7 +4,6 @@ import ( "errors" "os" - "github.com/zeromicro/go-zero/core/jsonx" "github.com/zeromicro/go-zero/tools/goctl/rpc/execx" ) @@ -17,16 +16,10 @@ func IsGoMod(workDir string) (bool, error) { return false, err } - data, err := execx.Run("go list -json -m", workDir) - if err != nil { + data, err := execx.Run("go list -m -f '{{.GoMod}}'", workDir) + if err != nil || len(data) == 0 { return false, nil } - var m Module - err = jsonx.Unmarshal([]byte(data), &m) - if err != nil { - return false, err - } - - return len(m.GoMod) > 0, nil + return true, nil }