Skip to content

Commit f7d463c

Browse files
tadeleshtadelesh
andauthored
Add build cmd into generator tool and add back force stable flag (#25570)
* add build cmd * addback force stable version flag * add changelog * add cli config * update readme * copilot review --------- Co-authored-by: tadelesh <[email protected]>
1 parent ae02be6 commit f7d463c

File tree

10 files changed

+355
-22
lines changed

10 files changed

+355
-22
lines changed

eng/swagger_to_sdk_config.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
},
3131
"packageOptions": {
3232
"breakingChangeLabel": "CI-BreakingChange-Go",
33-
"breakingChangesLabel": "BreakingChange-Go-Sdk"
33+
"breakingChangesLabel": "BreakingChange-Go-Sdk",
34+
"buildScript": {
35+
"command": "generator build {packagePath}",
36+
"path": ""
37+
}
3438
}
3539
}

eng/tools/generator/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Release History
22

3+
## 0.3.0 (2025-11-07)
4+
5+
### Features Added
6+
7+
- Add `build` command to build the SDK package.
8+
- Add back `force-stable-version` flag to `release-v2` command to support generating stable version even if input-files contains preview version.
9+
310
## 0.2.2 (2025-10-14)
411

512
### Other Changes

eng/tools/generator/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ generator release-v2 <azure-sdk-for-go directory> <azure-rest-api-specs director
257257
- `--skip-generate-example`: Skip generating examples
258258
- `--package-config`: Additional package configuration
259259
- `-t, --token`: Personal access token for GitHub operations
260+
- `--force-stable-version`: Force generation of stable SDK versions even when input files contain preview API versions. The tag must not contain preview when using this flag
260261
- `--tsp-config`: Path to TypeSpec tspconfig.yaml
261262
- `--tsp-option`: TypeSpec-go emit options (format: option1=value1;option2=value2)
262263
- `--tsp-client-option`: tsp-client options (e.g., --save-inputs, --debug)
@@ -283,6 +284,10 @@ generator release-v2 /path/to/azure-sdk-for-go /path/to/azure-rest-api-specs con
283284
# Skip branch creation and examples
284285
generator release-v2 /path/to/azure-sdk-for-go /path/to/azure-rest-api-specs compute \
285286
--skip-create-branch --skip-generate-example
287+
288+
# Force stable version generation even with preview input files
289+
generator release-v2 /path/to/azure-sdk-for-go /path/to/azure-rest-api-specs network \
290+
--force-stable-version
286291
```
287292

288293
### The `refresh-v2` command
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
package build
5+
6+
import (
7+
"encoding/json"
8+
"fmt"
9+
"os"
10+
"os/exec"
11+
"path/filepath"
12+
13+
"github.com/spf13/cobra"
14+
)
15+
16+
// BuildResult represents the result of a build operation
17+
type BuildResult struct {
18+
Success bool `json:"success"`
19+
Message string `json:"message"`
20+
Path string `json:"path,omitempty"`
21+
BuildOutput string `json:"build_output,omitempty"`
22+
VetOutput string `json:"vet_output,omitempty"`
23+
}
24+
25+
// Command returns the build command
26+
func Command() *cobra.Command {
27+
var outputFormat string
28+
var verbose bool
29+
30+
buildCmd := &cobra.Command{
31+
Use: "build <folder-path>",
32+
Short: "Build and vet Go packages in the specified folder",
33+
Long: `Build and vet Go packages in the specified folder using 'go build' and 'go vet'.
34+
35+
This command will:
36+
1. Run 'go build' to compile the Go packages in the specified folder
37+
2. Run 'go vet' to check for common Go programming errors
38+
3. Report any issues found during build or vet process
39+
40+
The command recursively processes all Go packages found in the specified folder.
41+
42+
Examples:
43+
# Build and vet packages in a specific folder
44+
generator build /path/to/generated/sdk/folder
45+
46+
# Build with verbose output
47+
generator build /path/to/generated/sdk/folder --verbose
48+
49+
# Build with JSON output
50+
generator build /path/to/generated/sdk/folder --output json`,
51+
Args: cobra.ExactArgs(1),
52+
RunE: func(cmd *cobra.Command, args []string) error {
53+
folderPath := args[0]
54+
55+
// Validate that the path exists and is a directory
56+
if err := validatePath(folderPath); err != nil {
57+
return fmt.Errorf("path validation error: %v", err)
58+
}
59+
60+
// Perform build and vet
61+
result, err := buildAndVet(folderPath)
62+
if err != nil {
63+
return fmt.Errorf("build operation failed: %v", err)
64+
}
65+
66+
// Output result
67+
switch outputFormat {
68+
case "json":
69+
jsonResult, err := json.MarshalIndent(result, "", " ")
70+
if err != nil {
71+
return fmt.Errorf("failed to marshal result: %v", err)
72+
}
73+
fmt.Println(string(jsonResult))
74+
default:
75+
// Human-readable output
76+
if result.Success {
77+
fmt.Printf("✓ Build and vet completed successfully!\n\n")
78+
fmt.Printf("Path: %s\n", result.Path)
79+
if verbose && result.BuildOutput != "" {
80+
fmt.Printf("\nBuild Output:\n%s\n", result.BuildOutput)
81+
}
82+
if verbose && result.VetOutput != "" {
83+
fmt.Printf("\nVet Output:\n%s\n", result.VetOutput)
84+
}
85+
} else {
86+
fmt.Printf("✗ Build and vet failed: %s\n", result.Message)
87+
if result.BuildOutput != "" {
88+
fmt.Printf("\nBuild Output:\n%s\n", result.BuildOutput)
89+
}
90+
if result.VetOutput != "" {
91+
fmt.Printf("\nVet Output:\n%s\n", result.VetOutput)
92+
}
93+
return fmt.Errorf("build or vet failed")
94+
}
95+
}
96+
97+
return nil
98+
},
99+
}
100+
101+
buildCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "Output format (text|json)")
102+
buildCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output")
103+
104+
return buildCmd
105+
}
106+
107+
// validatePath validates that the provided path exists and is a directory
108+
func validatePath(path string) error {
109+
if info, err := os.Stat(path); err != nil || !info.IsDir() {
110+
return fmt.Errorf("path '%s' does not exist or is not a directory", path)
111+
}
112+
return nil
113+
}
114+
115+
// buildAndVet performs go build and go vet operations on the specified folder
116+
func buildAndVet(folderPath string) (*BuildResult, error) {
117+
result := &BuildResult{
118+
Path: folderPath,
119+
}
120+
121+
// Convert to absolute path
122+
absPath, err := filepath.Abs(folderPath)
123+
if err != nil {
124+
result.Success = false
125+
result.Message = fmt.Sprintf("Failed to get absolute path: %v", err)
126+
return result, nil
127+
}
128+
result.Path = absPath
129+
130+
// Run go build
131+
buildSuccess, buildOutput := runGoBuild(absPath)
132+
result.BuildOutput = buildOutput
133+
134+
// Run go vet
135+
vetSuccess, vetOutput := runGoVet(absPath)
136+
result.VetOutput = vetOutput
137+
138+
// Determine overall success
139+
result.Success = buildSuccess && vetSuccess
140+
141+
if !result.Success {
142+
if !buildSuccess && !vetSuccess {
143+
result.Message = "Both build and vet failed"
144+
} else if !buildSuccess {
145+
result.Message = "Build failed"
146+
} else {
147+
result.Message = "Vet failed"
148+
}
149+
} else {
150+
result.Message = "Build and vet completed successfully"
151+
}
152+
153+
return result, nil
154+
}
155+
156+
// runGoBuild executes go build and returns success status and output
157+
func runGoBuild(path string) (bool, string) {
158+
cmd := exec.Command("go", "build", "./...")
159+
cmd.Dir = path
160+
161+
output, err := cmd.CombinedOutput()
162+
outputStr := string(output)
163+
164+
if err != nil {
165+
return false, outputStr
166+
}
167+
168+
return true, outputStr
169+
}
170+
171+
// runGoVet executes go vet and returns success status and output
172+
func runGoVet(path string) (bool, string) {
173+
cmd := exec.Command("go", "vet", "./...")
174+
cmd.Dir = path
175+
176+
output, err := cmd.CombinedOutput()
177+
outputStr := string(output)
178+
179+
if err != nil {
180+
return false, outputStr
181+
}
182+
183+
return true, outputStr
184+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
package build
5+
6+
import (
7+
"encoding/json"
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
)
12+
13+
func TestValidatePath(t *testing.T) {
14+
// Test with current directory (should exist)
15+
if err := validatePath("."); err != nil {
16+
t.Errorf("validatePath failed for current directory: %v", err)
17+
}
18+
19+
// Test with non-existent directory
20+
if err := validatePath("/non/existent/path"); err == nil {
21+
t.Error("validatePath should fail for non-existent path")
22+
}
23+
}
24+
25+
func TestBuildResultJSON(t *testing.T) {
26+
result := &BuildResult{
27+
Success: true,
28+
Message: "Build successful",
29+
Path: "/test/path",
30+
}
31+
32+
jsonData, err := json.Marshal(result)
33+
if err != nil {
34+
t.Errorf("Failed to marshal BuildResult: %v", err)
35+
}
36+
37+
var unmarshaled BuildResult
38+
if err := json.Unmarshal(jsonData, &unmarshaled); err != nil {
39+
t.Errorf("Failed to unmarshal BuildResult: %v", err)
40+
}
41+
42+
if unmarshaled.Success != result.Success {
43+
t.Error("JSON marshaling/unmarshaling failed for Success field")
44+
}
45+
if unmarshaled.Message != result.Message {
46+
t.Error("JSON marshaling/unmarshaling failed for Message field")
47+
}
48+
}
49+
50+
// TestBuildAndVetIntegration tests the actual build and vet functionality
51+
// This test requires a valid Go module to work properly
52+
func TestBuildAndVetIntegration(t *testing.T) {
53+
// Create a temporary directory with a simple Go file
54+
tempDir, err := os.MkdirTemp("", "build_test")
55+
if err != nil {
56+
t.Fatalf("Failed to create temp dir: %v", err)
57+
}
58+
defer os.RemoveAll(tempDir)
59+
60+
// Create a simple go.mod file
61+
goModContent := `module test
62+
63+
go 1.21
64+
`
65+
if err := os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte(goModContent), 0644); err != nil {
66+
t.Fatalf("Failed to create go.mod: %v", err)
67+
}
68+
69+
// Create a valid Go file
70+
goFileContent := `package main
71+
72+
import "fmt"
73+
74+
func main() {
75+
fmt.Println("Hello, world!")
76+
}
77+
`
78+
if err := os.WriteFile(filepath.Join(tempDir, "main.go"), []byte(goFileContent), 0644); err != nil {
79+
t.Fatalf("Failed to create main.go: %v", err)
80+
}
81+
82+
// Test build and vet
83+
result, err := buildAndVet(tempDir)
84+
if err != nil {
85+
t.Errorf("buildAndVet failed: %v", err)
86+
}
87+
88+
if !result.Success {
89+
t.Errorf("buildAndVet should succeed for valid Go code, but got: %s", result.Message)
90+
}
91+
}
92+
93+
// TestBuildAndVetWithErrors tests the build and vet functionality with intentional errors
94+
func TestBuildAndVetWithErrors(t *testing.T) {
95+
// Create a temporary directory with an invalid Go file
96+
tempDir, err := os.MkdirTemp("", "build_test_errors")
97+
if err != nil {
98+
t.Fatalf("Failed to create temp dir: %v", err)
99+
}
100+
defer os.RemoveAll(tempDir)
101+
102+
// Create a simple go.mod file
103+
goModContent := `module test
104+
105+
go 1.21
106+
`
107+
if err := os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte(goModContent), 0644); err != nil {
108+
t.Fatalf("Failed to create go.mod: %v", err)
109+
}
110+
111+
// Create an invalid Go file (syntax error)
112+
goFileContent := `package main
113+
114+
import "fmt"
115+
116+
func main() {
117+
fmt.Println("Hello, world!"
118+
// Missing closing parenthesis
119+
}
120+
`
121+
if err := os.WriteFile(filepath.Join(tempDir, "main.go"), []byte(goFileContent), 0644); err != nil {
122+
t.Fatalf("Failed to create main.go: %v", err)
123+
}
124+
125+
// Test build and vet
126+
result, err := buildAndVet(tempDir)
127+
if err != nil {
128+
t.Errorf("buildAndVet failed: %v", err)
129+
}
130+
131+
if result.Success {
132+
t.Error("buildAndVet should fail for invalid Go code")
133+
}
134+
135+
if result.BuildOutput == "" {
136+
t.Error("buildAndVet should report build output for invalid Go code")
137+
}
138+
}

eng/tools/generator/cmd/v2/common/cmdProcessor.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -187,17 +187,6 @@ func ExecuteGo(dir string, args ...string) error {
187187
return nil
188188
}
189189

190-
func ExecuteGoFmt(dir string, args ...string) error {
191-
cmd := exec.Command("gofmt", args...)
192-
cmd.Dir = dir
193-
combinedOutput, err := cmd.CombinedOutput()
194-
if err != nil {
195-
return fmt.Errorf("failed to execute `gofmt %s` '%s': %+v", strings.Join(args, " "), string(combinedOutput), err)
196-
}
197-
198-
return nil
199-
}
200-
201190
// execute tsp-client command
202191
func ExecuteTspClient(path string, args ...string) error {
203192
// Use pinned tsp-client from eng/common/tsp-client instead of global npx

0 commit comments

Comments
 (0)