package main import ( "fmt" "os" "os/exec" "path/filepath" "sort" ) type codegenTarget struct { label string workdir string stage string binary string args []string } type codegenFailure struct { target string stage string output string } func discoverCodegenTargets(repoRoot, backendDir string, backendTargets []backendScanTarget, pluginTargets []pluginScanTarget, includeCoreTargets bool) []codegenTarget { var targets []codegenTarget seen := make(map[string]bool) addTarget := func(workdir, label, stage, binary string, args ...string) { key := workdir + "|" + stage if workdir == "" || seen[key] { return } seen[key] = true targets = append(targets, codegenTarget{ label: label, workdir: workdir, stage: stage, binary: binary, args: append([]string{}, args...), }) } if includeCoreTargets && samePath(repoRoot, blockNinjaRepoRoot()) { if fileExists(filepath.Join(backendDir, "sqlc.yaml")) { addTarget(backendDir, "backend", "sqlc compile", "sqlc", "compile") } if fileExists(filepath.Join(repoRoot, "buf.yaml")) { addTarget(repoRoot, "repo", "buf generate", "buf", "generate", "--path", "proto/blockninja/v1", "--path", "proto/orchestrator/v1") } } for _, target := range pluginTargets { if fileExists(filepath.Join(target.root, "sqlc.yaml")) { addTarget(target.root, target.display, "sqlc compile", "sqlc", "compile") } if fileExists(filepath.Join(target.root, "buf.yaml")) { addTarget(target.root, target.display, "buf generate", "buf", "generate") } } for _, target := range backendTargets { if !shouldCheckStandalonePluginImports(target.root) { continue } if fileExists(filepath.Join(target.root, "sqlc.yaml")) { addTarget(target.root, target.displayOrRoot(), "sqlc compile", "sqlc", "compile") } if fileExists(filepath.Join(target.root, "buf.yaml")) { addTarget(target.root, target.displayOrRoot(), "buf generate", "buf", "generate") } } sort.Slice(targets, func(i, j int) bool { if targets[i].label == targets[j].label { return targets[i].stage < targets[j].stage } return targets[i].label < targets[j].label }) return targets } func runCodegenChecks(targets []codegenTarget) (completed []string, failures []codegenFailure, err error) { sqlcBin, err := exec.LookPath("sqlc") if err != nil { return nil, nil, fmt.Errorf("sqlc not found in PATH") } bufBin, err := exec.LookPath("buf") if err != nil { return nil, nil, fmt.Errorf("buf not found in PATH") } return runCodegenChecksWithBinaries(targets, sqlcBin, bufBin) } func runCodegenChecksWithBinaries(targets []codegenTarget, sqlcBin, bufBin string) (completed []string, failures []codegenFailure, err error) { for _, target := range targets { binary := target.binary args := append([]string{}, target.args...) cleanup := func() {} switch target.binary { case "sqlc": binary = sqlcBin case "buf": binary = bufBin tempOutputDir, mkErr := os.MkdirTemp("", "check-safety-buf-*") if mkErr != nil { return nil, nil, fmt.Errorf("create temp output dir for %s: %w", target.label, mkErr) } args = append(args, "-o", tempOutputDir) cleanup = func() { _ = os.RemoveAll(tempOutputDir) } } output, runErr := runCommand(target.workdir, binary, args...) cleanup() if runErr != nil { failures = append(failures, codegenFailure{ target: target.label, stage: target.stage, output: output, }) continue } completed = append(completed, target.label+" ["+target.stage+"]") } sort.Strings(completed) return completed, failures, nil }