Skip to content

Commit 955b801

Browse files
authored
feat: support goctl --module to set go module (#5135)
1 parent d728a3b commit 955b801

File tree

14 files changed

+944
-6
lines changed

14 files changed

+944
-6
lines changed

tools/goctl/api/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func init() {
9090
newCmdFlags.StringVar(&new.VarStringHome, "home")
9191
newCmdFlags.StringVar(&new.VarStringRemote, "remote")
9292
newCmdFlags.StringVar(&new.VarStringBranch, "branch")
93+
newCmdFlags.StringVar(&new.VarStringModule, "module")
9394
newCmdFlags.StringVarWithDefaultValue(&new.VarStringStyle, "style", config.DefaultFormat)
9495

9596
pluginCmdFlags.StringVarP(&plugin.VarStringPlugin, "plugin", "p")

tools/goctl/api/gogen/gen.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ func GoCommand(_ *cobra.Command, _ []string) error {
7575

7676
// DoGenProject gen go project files with api file
7777
func DoGenProject(apiFile, dir, style string, withTest bool) error {
78+
return DoGenProjectWithModule(apiFile, dir, "", style, withTest)
79+
}
80+
81+
// DoGenProjectWithModule gen go project files with api file using custom module name
82+
func DoGenProjectWithModule(apiFile, dir, moduleName, style string, withTest bool) error {
7883
api, err := parser.Parse(apiFile)
7984
if err != nil {
8085
return err
@@ -90,7 +95,13 @@ func DoGenProject(apiFile, dir, style string, withTest bool) error {
9095
}
9196

9297
logx.Must(pathx.MkdirIfNotExist(dir))
93-
rootPkg, projectPkg, err := golang.GetParentPackage(dir)
98+
99+
var rootPkg, projectPkg string
100+
if len(moduleName) > 0 {
101+
rootPkg, projectPkg, err = golang.GetParentPackageWithModule(dir, moduleName)
102+
} else {
103+
rootPkg, projectPkg, err = golang.GetParentPackage(dir)
104+
}
94105
if err != nil {
95106
return err
96107
}

tools/goctl/api/new/newservice.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ var (
2727
VarStringBranch string
2828
// VarStringStyle describes the style of output files.
2929
VarStringStyle string
30+
// VarStringModule describes the module name for go.mod.
31+
VarStringModule string
3032
)
3133

3234
// CreateServiceCommand fast create service
@@ -83,6 +85,6 @@ func CreateServiceCommand(_ *cobra.Command, args []string) error {
8385
return err
8486
}
8587

86-
err = gogen.DoGenProject(apiFilePath, abs, VarStringStyle, false)
88+
err = gogen.DoGenProjectWithModule(apiFilePath, abs, VarStringModule, VarStringStyle, false)
8789
return err
8890
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package new
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
"github.com/zeromicro/go-zero/tools/goctl/api/gogen"
11+
"github.com/zeromicro/go-zero/tools/goctl/config"
12+
)
13+
14+
func TestDoGenProjectWithModule_Integration(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
moduleName string
18+
serviceName string
19+
expectedMod string
20+
}{
21+
{
22+
name: "with custom module",
23+
moduleName: "github.com/test/customapi",
24+
serviceName: "myservice",
25+
expectedMod: "github.com/test/customapi",
26+
},
27+
{
28+
name: "with empty module",
29+
moduleName: "",
30+
serviceName: "myservice",
31+
expectedMod: "myservice",
32+
},
33+
{
34+
name: "with simple module",
35+
moduleName: "simpleapi",
36+
serviceName: "testapi",
37+
expectedMod: "simpleapi",
38+
},
39+
}
40+
41+
for _, tt := range tests {
42+
t.Run(tt.name, func(t *testing.T) {
43+
// Create temporary directory
44+
tempDir, err := os.MkdirTemp("", "goctl-api-module-test-*")
45+
require.NoError(t, err)
46+
defer os.RemoveAll(tempDir)
47+
48+
// Create service directory
49+
serviceDir := filepath.Join(tempDir, tt.serviceName)
50+
err = os.MkdirAll(serviceDir, 0755)
51+
require.NoError(t, err)
52+
53+
// Create a simple API file for testing
54+
apiContent := `syntax = "v1"
55+
56+
type Request {
57+
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
58+
}
59+
60+
type Response {
61+
Message string ` + "`" + `json:"message"` + "`" + `
62+
}
63+
64+
service ` + tt.serviceName + `-api {
65+
@handler ` + tt.serviceName + `Handler
66+
get /from/:name(Request) returns (Response)
67+
}
68+
`
69+
apiFile := filepath.Join(serviceDir, tt.serviceName+".api")
70+
err = os.WriteFile(apiFile, []byte(apiContent), 0644)
71+
require.NoError(t, err)
72+
73+
// Call the module-aware service creation function
74+
err = gogen.DoGenProjectWithModule(apiFile, serviceDir, tt.moduleName, config.DefaultFormat, false)
75+
assert.NoError(t, err)
76+
77+
// Check go.mod file
78+
goModPath := filepath.Join(serviceDir, "go.mod")
79+
assert.FileExists(t, goModPath)
80+
81+
// Verify module name in go.mod
82+
content, err := os.ReadFile(goModPath)
83+
require.NoError(t, err)
84+
assert.Contains(t, string(content), "module "+tt.expectedMod)
85+
86+
// Check basic directory structure was created
87+
assert.DirExists(t, filepath.Join(serviceDir, "etc"))
88+
assert.DirExists(t, filepath.Join(serviceDir, "internal"))
89+
assert.DirExists(t, filepath.Join(serviceDir, "internal", "handler"))
90+
assert.DirExists(t, filepath.Join(serviceDir, "internal", "logic"))
91+
assert.DirExists(t, filepath.Join(serviceDir, "internal", "svc"))
92+
assert.DirExists(t, filepath.Join(serviceDir, "internal", "types"))
93+
assert.DirExists(t, filepath.Join(serviceDir, "internal", "config"))
94+
95+
// Check that main.go imports use correct module
96+
mainGoPath := filepath.Join(serviceDir, tt.serviceName+".go")
97+
if _, err := os.Stat(mainGoPath); err == nil {
98+
mainContent, err := os.ReadFile(mainGoPath)
99+
require.NoError(t, err)
100+
// Check for import of internal packages with correct module path
101+
assert.Contains(t, string(mainContent), `"`+tt.expectedMod+"/internal/")
102+
}
103+
})
104+
}
105+
}
106+
107+
func TestCreateServiceCommand_Integration(t *testing.T) {
108+
tests := []struct {
109+
name string
110+
moduleName string
111+
serviceName string
112+
expectedMod string
113+
shouldError bool
114+
}{
115+
{
116+
name: "valid service with custom module",
117+
moduleName: "github.com/example/testapi",
118+
serviceName: "myapi",
119+
expectedMod: "github.com/example/testapi",
120+
shouldError: false,
121+
},
122+
{
123+
name: "valid service with no module",
124+
moduleName: "",
125+
serviceName: "simpleapi",
126+
expectedMod: "simpleapi",
127+
shouldError: false,
128+
},
129+
{
130+
name: "invalid service name with hyphens",
131+
moduleName: "github.com/test/api",
132+
serviceName: "my-api",
133+
expectedMod: "",
134+
shouldError: true,
135+
},
136+
}
137+
138+
for _, tt := range tests {
139+
t.Run(tt.name, func(t *testing.T) {
140+
if tt.shouldError && tt.serviceName == "my-api" {
141+
// Test that service names with hyphens are rejected
142+
// This is tested in the actual command function, not the generate function
143+
assert.Contains(t, tt.serviceName, "-")
144+
return
145+
}
146+
147+
// Create temporary directory
148+
tempDir, err := os.MkdirTemp("", "goctl-create-service-test-*")
149+
require.NoError(t, err)
150+
defer os.RemoveAll(tempDir)
151+
152+
// Change to temp directory
153+
oldDir, _ := os.Getwd()
154+
defer os.Chdir(oldDir)
155+
os.Chdir(tempDir)
156+
157+
// Set the module variable as the command would
158+
VarStringModule = tt.moduleName
159+
VarStringStyle = config.DefaultFormat
160+
161+
// Create the service directory manually since we're testing the core functionality
162+
serviceDir := filepath.Join(tempDir, tt.serviceName)
163+
164+
// Simulate what CreateServiceCommand does - create API file and call DoGenProjectWithModule
165+
err = os.MkdirAll(serviceDir, 0755)
166+
require.NoError(t, err)
167+
168+
// Create API file
169+
apiContent := `syntax = "v1"
170+
171+
type Request {
172+
Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
173+
}
174+
175+
type Response {
176+
Message string ` + "`" + `json:"message"` + "`" + `
177+
}
178+
179+
service ` + tt.serviceName + `-api {
180+
@handler ` + tt.serviceName + `Handler
181+
get /from/:name(Request) returns (Response)
182+
}
183+
`
184+
apiFile := filepath.Join(serviceDir, tt.serviceName+".api")
185+
err = os.WriteFile(apiFile, []byte(apiContent), 0644)
186+
require.NoError(t, err)
187+
188+
// Call DoGenProjectWithModule as CreateServiceCommand does
189+
err = gogen.DoGenProjectWithModule(apiFile, serviceDir, VarStringModule, VarStringStyle, false)
190+
191+
if tt.shouldError {
192+
assert.Error(t, err)
193+
} else {
194+
assert.NoError(t, err)
195+
196+
// Verify go.mod
197+
goModPath := filepath.Join(serviceDir, "go.mod")
198+
assert.FileExists(t, goModPath)
199+
content, err := os.ReadFile(goModPath)
200+
require.NoError(t, err)
201+
assert.Contains(t, string(content), "module "+tt.expectedMod)
202+
}
203+
})
204+
}
205+
}

tools/goctl/internal/flags/default_en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"home": "{{.global.home}}",
4848
"remote": "{{.global.remote}}",
4949
"branch": "{{.global.branch}}",
50+
"module": "Custom module name for go.mod (default: directory name)",
5051
"style": "{{.global.style}}"
5152
},
5253
"validate": {
@@ -238,6 +239,7 @@
238239
"home": "{{.global.home}}",
239240
"remote": "{{.global.remote}}",
240241
"branch": "{{.global.branch}}",
242+
"module": "Custom module name for go.mod (default: directory name)",
241243
"verbose": "Enable log output",
242244
"client": "Whether to generate rpc client"
243245
},

tools/goctl/pkg/golang/path.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,30 @@ import (
99
)
1010

1111
func GetParentPackage(dir string) (string, string, error) {
12+
return GetParentPackageWithModule(dir, "")
13+
}
14+
15+
func GetParentPackageWithModule(dir, moduleName string) (string, string, error) {
1216
abs, err := filepath.Abs(dir)
1317
if err != nil {
1418
return "", "", err
1519
}
1620

17-
projectCtx, err := ctx.Prepare(abs)
21+
var projectCtx *ctx.ProjectContext
22+
if len(moduleName) > 0 {
23+
projectCtx, err = ctx.PrepareWithModule(abs, moduleName)
24+
} else {
25+
projectCtx, err = ctx.Prepare(abs)
26+
}
1827
if err != nil {
1928
return "", "", err
2029
}
2130

22-
// fix https://github.com/zeromicro/go-zero/issues/1058
31+
return buildParentPackage(projectCtx)
32+
}
33+
34+
// buildParentPackage extracts the common logic for building parent package paths
35+
func buildParentPackage(projectCtx *ctx.ProjectContext) (string, string, error) {
2336
wd := projectCtx.WorkDir
2437
d := projectCtx.Dir
2538
same, err := pathx.SameFile(wd, d)

0 commit comments

Comments
 (0)