diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30a64f3..a6c019e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,3 +104,6 @@ jobs: with: go-version: stable - run: go run main.go ${{ github.workspace }} ${{ matrix.program }} + + - uses: mxschmitt/action-tmate@v3 + if: ${{ failure() }} diff --git a/cargo-generate.toml b/cargo-generate.toml index efa3ccc..8dcf2a3 100644 --- a/cargo-generate.toml +++ b/cargo-generate.toml @@ -1,6 +1,6 @@ [template] cargo_generate_version = ">=0.10.0" -ignore = [".github", "test.py"] +ignore = [".github", "main.go"] [placeholders.program_type] type = "string" diff --git a/main.go b/main.go index 3578a6f..6fc9f5c 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,8 @@ import ( "os/exec" "path/filepath" "runtime" + "strconv" "strings" - "syscall" "time" ) @@ -157,9 +157,9 @@ func run() error { cmd.Env = cmdSpec.Env cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - fmt.Printf("%+v\n", cmdSpec) + fmt.Printf("%#v\n", cmdSpec) if err := cmd.Run(); err != nil { - return fmt.Errorf("%+v failed: %w", cmdSpec, err) + return fmt.Errorf("%#v failed: %w", cmdSpec, err) } } @@ -170,11 +170,6 @@ func run() error { cmd := exec.CommandContext(ctx, "cargo", "xtask", "run") cmd.Dir = projectDir cmd.Stderr = os.Stderr - // Prevent the child process from being in our process group so that we can send it a SIGINT - // without sending one to ourselves. - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setpgid: true, - } stdoutPipe, err := cmd.StdoutPipe() if err != nil { @@ -192,8 +187,11 @@ func run() error { if _, err := fmt.Fprintln(os.Stdout, text); err != nil { panic(err) } + if strings.Contains(text, "Waiting for Ctrl-C") { - syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) + if err := signalChildren(cmd.Process); err != nil { + return fmt.Errorf("failed to signal children of %d: %w", cmd.Process.Pid, err) + } } } if err := scanner.Err(); err != nil { @@ -207,3 +205,42 @@ func run() error { return nil } + +func signalChildren(process *os.Process) error { + fmt.Printf("Sending SIGINT to children of pid %d\n", process.Pid) + taskPath := fmt.Sprintf("/proc/%d/task", process.Pid) + entries, err := os.ReadDir(taskPath) + if err != nil { + return fmt.Errorf("failed to readdir %s: %w", taskPath, err) + } + for _, entry := range entries { + if !entry.IsDir() { + continue + } + childrenPath := fmt.Sprintf("%s/%s/children", taskPath, entry.Name()) + childrenContent, err := os.ReadFile(childrenPath) + if err != nil { + return fmt.Errorf("failed to read %s: %w", childrenPath, err) + } + for _, childField := range strings.Fields(string(childrenContent)) { + childPid, err := strconv.Atoi(childField) + if err != nil { + return fmt.Errorf("failed to parse %s: %w", childField, err) + } + child, err := os.FindProcess(childPid) + if err != nil { + return fmt.Errorf("failed to find process %d: %w", childPid, err) + } + if err := signalChildren(child); err != nil { + return fmt.Errorf("failed to signal children of %d: %w", child.Pid, err) + } + } + } + fmt.Printf("Sending SIGINT to pid %d\n", process.Pid) + if err := process.Signal(os.Interrupt); err != nil { + if _, err := fmt.Fprintf(os.Stderr, "failed to interrupt %d: %s", process, err); err != nil { + panic(err) + } + } + return nil +}