Browse Source

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
Fyn 3 years ago
parent
commit
e62870e268

+ 3 - 0
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

+ 2 - 0
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)

+ 36 - 7
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
+}

+ 74 - 0
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)
+			}
+		})
+	}
+}

+ 3 - 10
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
 }