//go:build unix package tools import ( "os" "os/exec" "syscall" ) // shellProgram is the shell every Unix has. func shellProgram() string { return "/bin/sh" } // shellArgs makes the shell run one command line and exit. func shellArgs(command string) []string { return []string{"-c", command} } // childAttributes puts a command in a process group of its own. // // Without it, stopping a command kills only the shell. `go test ./...` spawns a // test binary per package, `make` spawns compilers: those keep running, and — // worse — they inherit the output pipe, so the goroutine reading it blocks // until they finish rather than when the command was stopped. func childAttributes(*exec.Cmd, string) *syscall.SysProcAttr { return &syscall.SysProcAttr{Setpgid: true} } // processGroup is the group a Unix command runs in, addressed by its // leader's pid. type processGroup struct{ process *os.Process } // newGroup returns the group a started command belongs to. func newGroup(process *os.Process) group { return processGroup{process: process} } // kill stops the process and everything it started. // // The negative pid is what makes it the group rather than the one process. A // group that has already gone gives an error nobody can act on, so it is // dropped: stopping something twice, or stopping something that just ended, is // not a failure. func (g processGroup) kill() { if err := syscall.Kill(-g.process.Pid, syscall.SIGKILL); err != nil { _ = g.process.Kill() // it may not have got its own group; kill it alone } } // release has nothing to give back: a process group is not a resource. func (processGroup) release() {}