Something went wrong. Try again.
Tangled-compatible personal CI server based on Nix
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788package engine
import ( "context" "errors" "io" "os/exec" "syscall" "time")
// Spec is one action execution.type Spec struct { Root string // main checkout: where the devenv shell is activated Dir string // absolute working directory for the command Command string Env []string Stdout io.Writer Stderr io.Writer}
// Executor runs an action's command and returns its exit code. An error// means the command could not be run at all (or was cancelled).type Executor interface { Exec(ctx context.Context, s Spec) (int, error)}
// script runs the command in its working directory under bash's strict mode.// The directory comes in through CI_ACTION_DIR so it needs no quoting.func script(command string) string { return "cd -- \"$CI_ACTION_DIR\"\n" + command}
// Devenv runs actions in the main checkout's devenv shell: `devenv shell// bash -euo pipefail -c <script>` with the main checkout as working// directory, so the workflow's repo pins the toolchain (and the engine) even// when the action's dir is another repo.type Devenv struct { // Binary is the devenv executable; default "devenv". Binary string}
func (d Devenv) Exec(ctx context.Context, s Spec) (int, error) { bin := d.Binary if bin == "" { bin = "devenv" } return run(ctx, s, s.Root, bin, "shell", "bash", "-euo", "pipefail", "-c", script(s.Command))}
// Bash runs actions with plain bash, without devenv: for tests, and for// debugging a recipe outside its shell (ci run --shell bash).type Bash struct{}
func (Bash) Exec(ctx context.Context, s Spec) (int, error) { return run(ctx, s, s.Dir, "bash", "-euo", "pipefail", "-c", script(s.Command))}
// run starts the command in its own process group, so cancelling kills the// whole tree (devenv, bash and whatever the recipe started).func run(ctx context.Context, s Spec, dir, name string, args ...string) (int, error) { cmd := exec.CommandContext(ctx, name, args...) cmd.Dir = dir cmd.Env = append(s.Env, "CI_ACTION_DIR="+s.Dir) cmd.Stdout = s.Stdout cmd.Stderr = s.Stderr cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} cmd.Cancel = func() error { return syscall.Kill(-cmd.Process.Pid, syscall.SIGTERM) } cmd.WaitDelay = 10 * time.Second err := cmd.Run() if ctx.Err() != nil { return -1, ctx.Err() } var ee *exec.ExitError if errors.As(err, &ee) { if ee.ExitCode() >= 0 { return ee.ExitCode(), nil } return -1, err } if err != nil { return -1, err } return 0, nil}