Files
fn_registry/functions/infra/process_spawn.go
T
egutierrez 35aca86541 fix(infra): build tag !windows en process_kill/spawn/wait
Estas funciones usan syscall.Kill, Setpgid y ProcessKill (no disponibles
en Windows). Sin el build tag, el paquete functions/infra no cross-compila
para Windows desde apps que solo usan otras funciones del paquete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 18:41:49 +02:00

77 lines
2.0 KiB
Go

//go:build !windows
package infra
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"syscall"
"time"
)
// ProcessSpawn launches a subprocess using the given shell.
// If shell is empty, "sh" is used. If command contains newlines it is treated
// as a multi-line script: the content is written to a temp file and executed
// with `shell <tempfile>`. Otherwise it is executed with `shell -c <command>`.
// dir sets the working directory (empty = inherit). env sets the environment
// (nil = inherit parent env). The process group is created with Setpgid so
// that ProcessKill can target the whole group.
func ProcessSpawn(command string, dir string, env []string, shell string) (*ProcessHandle, error) {
if shell == "" {
shell = "sh"
}
var cmd *exec.Cmd
if strings.Contains(command, "\n") {
// Multi-line script: write to a temp file and execute it.
tmp, err := os.CreateTemp("", "fn-proc-*.sh")
if err != nil {
return nil, fmt.Errorf("process_spawn: create temp file: %w", err)
}
if _, err := tmp.WriteString(command); err != nil {
_ = os.Remove(tmp.Name())
return nil, fmt.Errorf("process_spawn: write temp file: %w", err)
}
if err := tmp.Close(); err != nil {
_ = os.Remove(tmp.Name())
return nil, fmt.Errorf("process_spawn: close temp file: %w", err)
}
cmd = exec.Command(shell, tmp.Name())
} else {
cmd = exec.Command(shell, "-c", command)
}
if dir != "" {
cmd.Dir = dir
}
if len(env) > 0 {
cmd.Env = env
}
// New process group so we can kill all children as a group.
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
// Use buffers instead of pipes to avoid race between Wait() and ReadAll().
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
start := time.Now()
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("process_spawn: start: %w", err)
}
return &ProcessHandle{
Cmd: cmd,
Pid: cmd.Process.Pid,
StartTime: start,
Dir: dir,
stdout: &stdoutBuf,
stderr: &stderrBuf,
}, nil
}