Skip to content

Commit c20c05a

Browse files
committed
cleanups
1 parent e53c76e commit c20c05a

File tree

4 files changed

+393
-273
lines changed

4 files changed

+393
-273
lines changed

codegen/preflight/common.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package preflight
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"strings"
11+
)
12+
13+
const preflightTempDirPattern = "grafana-app-sdk-generate-preflight-*"
14+
15+
func getWorkingDirectory() (string, error) {
16+
return os.Getwd()
17+
}
18+
19+
func useOverlayCompilationPreflight(goModule, currentModule string) bool {
20+
if goModule == "" {
21+
return true
22+
}
23+
return currentModule != "" && goModule == currentModule
24+
}
25+
26+
func normalizeAbsolutePath(path, cwd string) string {
27+
if path == "" {
28+
path = "."
29+
}
30+
if !filepath.IsAbs(path) {
31+
path = filepath.Join(cwd, path)
32+
}
33+
return filepath.Clean(path)
34+
}
35+
36+
func normalizeRelativePath(path string) string {
37+
if path == "" {
38+
return "."
39+
}
40+
return filepath.Clean(path)
41+
}
42+
43+
func runPreflightGoBuild(tempDir, dir string, args ...string) ([]byte, error) {
44+
buildCmd := exec.Command("go", args...)
45+
buildCmd.Dir = dir
46+
buildCmd.Env = append(os.Environ(),
47+
"GOSUMDB=off",
48+
fmt.Sprintf("GOCACHE=%s", filepath.Join(tempDir, "gocache")),
49+
)
50+
return buildCmd.CombinedOutput()
51+
}
52+
53+
func preflightBuildError(out []byte, err error) error {
54+
return fmt.Errorf("generated code contains compilation errors, this is likely a bug in sdk. If you'd like to bypass the compilation check, please set skipPreflightCompilationCheck to true.\n\n%s\n%w", strings.TrimSpace(string(out)), err)
55+
}
56+
57+
type goModJSON struct {
58+
Module struct {
59+
Path string `json:"Path"`
60+
} `json:"Module"`
61+
}
62+
63+
func getGoModule(goModPath string) (string, error) {
64+
cmd := exec.Command("go", "mod", "edit", "-json")
65+
cmd.Dir = filepath.Dir(goModPath)
66+
out, err := cmd.Output()
67+
if err != nil {
68+
return "", fmt.Errorf("unable to run go mod edit --json: %w", err)
69+
}
70+
71+
var mod goModJSON
72+
if err := json.Unmarshal(out, &mod); err == nil {
73+
return mod.Module.Path, nil
74+
}
75+
76+
return "", errors.New("unable to locate module in go.mod file")
77+
}
Lines changed: 13 additions & 273 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
11
package preflight
22

33
import (
4-
"encoding/json"
5-
"errors"
6-
"fmt"
7-
"os"
8-
"os/exec"
9-
"path/filepath"
10-
"sort"
11-
"strings"
12-
134
"github.com/grafana/codejen"
145

156
"github.com/grafana/grafana-app-sdk/codegen/config"
@@ -18,289 +9,38 @@ import (
189
// GeneratedGoCodeCompiles runs a preflight compilation check on generated Go files.
1910
func GeneratedGoCodeCompiles(cfg *config.Config, files codejen.Files) error {
2011
if cfg == nil {
21-
return generatedGoCodeCompilesWithOverlay(files)
12+
return compileGeneratedGoCodeWithOverlay(files)
2213
}
2314

24-
cwd, err := os.Getwd()
15+
cwd, err := getWorkingDirectory()
2516
if err != nil {
2617
return err
2718
}
2819

2920
currentModule, _ := getGoModule("go.mod")
30-
goModule := cfg.Codegen.GoModule
31-
if goModule == "" {
32-
goModule = currentModule
33-
}
21+
goModule := resolveGoModule(cfg, currentModule)
3422
if useOverlayCompilationPreflight(goModule, currentModule) {
35-
return generatedGoCodeCompilesWithOverlay(files)
36-
}
37-
38-
goGenRoot := normalizeAbsolutePath(cfg.Codegen.GoGenPath, cwd)
39-
40-
moduleGenRoot := cfg.Codegen.GoModGenPath
41-
if moduleGenRoot == "" {
42-
moduleGenRoot = cfg.Codegen.GoGenPath
43-
}
44-
moduleGenRoot = normalizeRelativePath(moduleGenRoot)
45-
if filepath.IsAbs(moduleGenRoot) {
46-
return generatedGoCodeCompilesWithOverlay(files)
47-
}
48-
49-
generatedFiles := make([]generatedGoFile, 0, len(files))
50-
generatedPackageDirs := make(map[string]struct{})
51-
manifestFileCountByDir := make(map[string]int)
52-
53-
for _, f := range files {
54-
if filepath.Ext(f.RelativePath) != ".go" {
55-
continue
56-
}
57-
58-
absTargetPath := normalizeAbsolutePath(f.RelativePath, cwd)
59-
60-
relPath, err := filepath.Rel(goGenRoot, absTargetPath)
61-
if err != nil || strings.HasPrefix(relPath, "..") {
62-
return generatedGoCodeCompilesWithOverlay(files)
63-
}
64-
65-
generatedFiles = append(generatedFiles, generatedGoFile{
66-
absDir: filepath.Dir(absTargetPath),
67-
relPath: filepath.Join(moduleGenRoot, relPath),
68-
data: f.Data,
69-
})
70-
generatedPackageDirs[filepath.Dir(absTargetPath)] = struct{}{}
71-
if strings.HasSuffix(absTargetPath, "_manifest.go") {
72-
manifestFileCountByDir[filepath.Dir(absTargetPath)]++
73-
}
74-
}
75-
76-
if len(generatedFiles) == 0 {
77-
return nil
78-
}
79-
80-
skipPackages := make(map[string]struct{})
81-
for dir, count := range manifestFileCountByDir {
82-
// Multiple generated manifest files share package-level identifiers by design.
83-
// Skip those packages in preflight and continue validating generated resource code.
84-
if count > 1 {
85-
skipPackages[dir] = struct{}{}
86-
}
87-
}
88-
if len(skipPackages) > 0 {
89-
filtered := generatedFiles[:0]
90-
for _, f := range generatedFiles {
91-
if _, skip := skipPackages[f.absDir]; skip {
92-
continue
93-
}
94-
filtered = append(filtered, f)
95-
}
96-
generatedFiles = filtered
97-
for dir := range skipPackages {
98-
delete(generatedPackageDirs, dir)
99-
}
100-
if len(generatedFiles) == 0 {
101-
return nil
102-
}
103-
}
104-
105-
tempDir, err := os.MkdirTemp("", "grafana-app-sdk-generate-preflight-*")
106-
if err != nil {
107-
return err
108-
}
109-
defer os.RemoveAll(tempDir)
110-
111-
moduleRoot := filepath.Join(tempDir, "module")
112-
if err := os.MkdirAll(moduleRoot, 0o755); err != nil {
113-
return err
114-
}
115-
116-
// Copy existing on-disk files from generated package directories, then overwrite with in-memory generated files.
117-
for packageDir := range generatedPackageDirs {
118-
relDir, err := filepath.Rel(goGenRoot, packageDir)
119-
if err != nil || strings.HasPrefix(relDir, "..") {
120-
return generatedGoCodeCompilesWithOverlay(files)
121-
}
122-
tempPackageDir := filepath.Join(moduleRoot, moduleGenRoot, relDir)
123-
if err := os.MkdirAll(tempPackageDir, 0o755); err != nil {
124-
return err
125-
}
126-
127-
entries, err := os.ReadDir(packageDir)
128-
if err != nil {
129-
if os.IsNotExist(err) {
130-
continue
131-
}
132-
return err
133-
}
134-
for _, entry := range entries {
135-
if entry.IsDir() || filepath.Ext(entry.Name()) != ".go" {
136-
continue
137-
}
138-
src := filepath.Join(packageDir, entry.Name())
139-
data, err := os.ReadFile(src)
140-
if err != nil {
141-
return err
142-
}
143-
dst := filepath.Join(tempPackageDir, entry.Name())
144-
if err := os.WriteFile(dst, data, 0o600); err != nil {
145-
return err
146-
}
147-
}
148-
}
149-
150-
for _, f := range generatedFiles {
151-
tempFilePath := filepath.Join(moduleRoot, f.relPath)
152-
if err := os.MkdirAll(filepath.Dir(tempFilePath), 0o755); err != nil {
153-
return err
154-
}
155-
if err := os.WriteFile(tempFilePath, f.data, 0o600); err != nil {
156-
return err
157-
}
158-
}
159-
160-
goModContents := fmt.Sprintf("module %s\n\ngo 1.24.0\n", goModule)
161-
if currentModule != "" && currentModule != goModule {
162-
goModContents += fmt.Sprintf("\nrequire %s v0.0.0\nreplace %s => %s\n", currentModule, currentModule, cwd)
163-
}
164-
if err := os.WriteFile(filepath.Join(moduleRoot, "go.mod"), []byte(goModContents), 0o600); err != nil {
165-
return err
23+
return compileGeneratedGoCodeWithOverlay(files)
16624
}
16725

168-
out, err := runPreflightGoBuild(tempDir, moduleRoot, "build", "-mod=mod", "./...")
169-
if err != nil {
170-
return preflightBuildError(out, err)
171-
}
172-
173-
return nil
174-
}
175-
176-
func generatedGoCodeCompilesWithOverlay(files codejen.Files) error {
177-
overlay := goBuildOverlay{
178-
Replace: make(map[string]string),
179-
}
180-
generatedPackages := make(map[string]struct{})
181-
cwd, err := os.Getwd()
26+
ctx, shouldUseTempModule, err := buildTempModuleContext(cfg, cwd, currentModule, goModule, files)
18227
if err != nil {
18328
return err
18429
}
185-
tempDir, err := os.MkdirTemp("", "grafana-app-sdk-generate-preflight-*")
186-
if err != nil {
187-
return err
30+
if !shouldUseTempModule {
31+
return compileGeneratedGoCodeWithOverlay(files)
18832
}
189-
defer os.RemoveAll(tempDir)
190-
191-
fileIndex := 0
192-
for _, f := range files {
193-
if filepath.Ext(f.RelativePath) != ".go" {
194-
continue
195-
}
196-
197-
absTargetPath := normalizeAbsolutePath(f.RelativePath, cwd)
198-
199-
generatedPackages[filepath.Dir(absTargetPath)] = struct{}{}
200-
201-
tempFilePath := filepath.Join(tempDir, fmt.Sprintf("file-%06d.go", fileIndex))
202-
fileIndex++
203-
204-
if err := os.WriteFile(tempFilePath, f.Data, 0o600); err != nil {
205-
return err
206-
}
207-
overlay.Replace[absTargetPath] = tempFilePath
208-
}
209-
210-
if len(generatedPackages) == 0 {
33+
if len(ctx.generatedFiles) == 0 {
21134
return nil
21235
}
21336

214-
packages := make([]string, 0, len(generatedPackages))
215-
for pkg := range generatedPackages {
216-
packages = append(packages, pkg)
217-
}
218-
sort.Strings(packages)
219-
220-
overlayBytes, err := json.Marshal(overlay)
221-
if err != nil {
222-
return err
223-
}
224-
overlayPath := filepath.Join(tempDir, "overlay.json")
225-
if err := os.WriteFile(overlayPath, overlayBytes, 0o600); err != nil {
226-
return err
227-
}
228-
229-
buildArgs := append([]string{"build", "-overlay", overlayPath}, packages...)
230-
out, err := runPreflightGoBuild(tempDir, "", buildArgs...)
231-
if err != nil {
232-
return preflightBuildError(out, err)
233-
}
234-
235-
return nil
236-
}
237-
238-
type generatedGoFile struct {
239-
absDir string
240-
relPath string
241-
data []byte
242-
}
243-
244-
type goBuildOverlay struct {
245-
Replace map[string]string `json:"Replace"`
37+
return compileGeneratedGoCodeWithTempModule(ctx)
24638
}
24739

248-
func useOverlayCompilationPreflight(goModule, currentModule string) bool {
40+
func resolveGoModule(cfg *config.Config, currentModule string) string {
41+
goModule := cfg.Codegen.GoModule
24942
if goModule == "" {
250-
return true
251-
}
252-
return currentModule != "" && goModule == currentModule
253-
}
254-
255-
func normalizeAbsolutePath(path, cwd string) string {
256-
if path == "" {
257-
path = "."
258-
}
259-
if !filepath.IsAbs(path) {
260-
path = filepath.Join(cwd, path)
43+
return currentModule
26144
}
262-
return filepath.Clean(path)
263-
}
264-
265-
func normalizeRelativePath(path string) string {
266-
if path == "" {
267-
return "."
268-
}
269-
return filepath.Clean(path)
270-
}
271-
272-
func runPreflightGoBuild(tempDir, dir string, args ...string) ([]byte, error) {
273-
buildCmd := exec.Command("go", args...)
274-
buildCmd.Dir = dir
275-
buildCmd.Env = append(os.Environ(),
276-
"GOSUMDB=off",
277-
fmt.Sprintf("GOCACHE=%s", filepath.Join(tempDir, "gocache")),
278-
)
279-
return buildCmd.CombinedOutput()
280-
}
281-
282-
func preflightBuildError(out []byte, err error) error {
283-
return fmt.Errorf("generated code contains compilation errors, this is likely a bug in sdk. If you'd like to bypass the compilation check, please set skipPreflightCompilationCheck to true.\n\n%s\n%w", strings.TrimSpace(string(out)), err)
284-
}
285-
286-
type goModJSON struct {
287-
Module struct {
288-
Path string `json:"Path"`
289-
} `json:"Module"`
290-
}
291-
292-
func getGoModule(goModPath string) (string, error) {
293-
cmd := exec.Command("go", "mod", "edit", "-json")
294-
cmd.Dir = filepath.Dir(goModPath)
295-
out, err := cmd.Output()
296-
if err != nil {
297-
return "", fmt.Errorf("unable to run go mod edit --json: %w", err)
298-
}
299-
300-
var mod goModJSON
301-
if err := json.Unmarshal(out, &mod); err == nil {
302-
return mod.Module.Path, nil
303-
}
304-
305-
return "", errors.New("unable to locate module in go.mod file")
45+
return goModule
30646
}

0 commit comments

Comments
 (0)