package main import ( "fmt" "os" "os/exec" "path/filepath" "strings" "fn-registry/registry" ) func buildCppCommand(fn *registry.Function, registryRoot, absPath string, args []string) (*exec.Cmd, error) { cppRoot := filepath.Join(registryRoot, "cpp") buildDir := filepath.Join(cppRoot, "build", "linux") // Ensure build directory exists and cmake is configured if _, err := os.Stat(filepath.Join(buildDir, "CMakeCache.txt")); os.IsNotExist(err) { fmt.Fprintf(os.Stderr, "[fn run] configuring cmake for cpp...\n") configure := exec.Command("cmake", "-B", buildDir, "-S", cppRoot) configure.Dir = registryRoot configure.Stdout = os.Stderr configure.Stderr = os.Stderr if err := configure.Run(); err != nil { return nil, fmt.Errorf("cmake configure failed: %w", err) } } dir := filepath.Dir(absPath) // Check if the function's directory has its own CMakeLists.txt (app with main) localCMake := filepath.Join(dir, "CMakeLists.txt") hasMain := false if _, err := os.Stat(localCMake); err == nil { hasMain = true } // Also check for main.cpp in the same directory mainCpp := filepath.Join(dir, "main.cpp") if _, err := os.Stat(mainCpp); err == nil { hasMain = true } if hasMain { // Build and run the app binary targetName := filepath.Base(dir) build := exec.Command("cmake", "--build", buildDir, "--target", targetName) build.Dir = registryRoot build.Stdout = os.Stderr build.Stderr = os.Stderr fmt.Fprintf(os.Stderr, "[fn run] building target %s...\n", targetName) if err := build.Run(); err != nil { return nil, fmt.Errorf("cmake build failed: %w", err) } // Find the built binary binaryPath := findBinary(buildDir, targetName) if binaryPath == "" { return nil, fmt.Errorf("built binary %q not found in %s", targetName, buildDir) } cmd := exec.Command(binaryPath, args...) cmd.Dir = dir return cmd, nil } // Library code: compile-check only (like go vet) build := exec.Command("cmake", "--build", buildDir) build.Dir = registryRoot build.Stdout = os.Stderr build.Stderr = os.Stderr fmt.Fprintf(os.Stderr, "[fn run] %s is library code — running compile check\n", fn.ID) if err := build.Run(); err != nil { return nil, fmt.Errorf("compile check failed: %w", err) } // Return a no-op command that just prints success cmd := exec.Command("echo", fmt.Sprintf("[fn run] %s compiled successfully", fn.ID)) return cmd, nil } // findBinary searches for an executable in the build tree. func findBinary(buildDir, name string) string { // Common locations cmake puts binaries candidates := []string{ filepath.Join(buildDir, name), filepath.Join(buildDir, "apps", name, name), } for _, c := range candidates { if info, err := os.Stat(c); err == nil && !info.IsDir() { // Check if executable if info.Mode()&0111 != 0 { return c } } } // Walk the build directory as fallback var found string filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil } if info.Name() == name && info.Mode()&0111 != 0 { found = path return filepath.SkipAll } return nil }) // Also try without extension match for paths with subdirectories if found == "" { filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil } base := strings.TrimSuffix(info.Name(), filepath.Ext(info.Name())) if base == name && info.Mode()&0111 != 0 { found = path return filepath.SkipAll } return nil }) } return found }