nixbuild/exec.go
Ophestra 8d502f1574
context: expose more internals
This makes overriding the Nix method easier.
2025-09-13 18:32:12 +09:00

103 lines
2.5 KiB
Go

package nix
import (
"context"
"errors"
"io"
"iter"
"os"
"os/exec"
"time"
)
const (
DefaultWaitDelay = 30 * time.Second
)
// Nix is the name of the nix program.
var Nix = "nix"
type nix struct {
name string
store Store
ctx context.Context
extra []string
stdout, stderr io.Writer
}
func (n *nix) Unwrap() context.Context { return n.ctx }
func (n *nix) Streams() (stdout, stderr io.Writer) { return n.stdout, n.stderr }
func (n *nix) Extra() (v []string) { v = make([]string, len(n.extra)); copy(v, n.extra); return }
func (n *nix) StoreEnv() (v []string) {
if n.store == nil {
return nil
}
return n.store.Environ()
}
const (
ExtraExperimentalFeatures = "--extra-experimental-features"
ExperimentalFeaturesFlakes = "nix-command flakes"
)
/*
New returns a new [Context].
A non-nil stderr implies verbose.
Streams will not be connected for commands outputting JSON.
*/
func New(ctx context.Context, store Store, extraArgs []string, stdout, stderr io.Writer) Context {
// since flakes are supposedly experimental
extra := []string{ExtraExperimentalFeatures, ExperimentalFeaturesFlakes}
if store != nil {
extra = append(extraArgs, FlagStore, store.String())
}
return &nix{
name: Nix,
store: store,
ctx: ctx,
extra: append(extraArgs, extra...),
stdout: stdout,
stderr: stderr,
}
}
// ExecCmd wraps [exec.Cmd] and implements extra [Cmd] methods.
type ExecCmd struct{ *exec.Cmd }
func (cmd ExecCmd) Stdin(r io.Reader) { cmd.Cmd.Stdin = r }
func (cmd ExecCmd) Stdout(w io.Writer) { cmd.Cmd.Stdout = w }
func (cmd ExecCmd) Stderr(w io.Writer) { cmd.Cmd.Stderr = w }
func (cmd ExecCmd) AppendArgs(a ...string) { cmd.Cmd.Args = append(cmd.Cmd.Args, a...) }
func (cmd ExecCmd) ReplaceEnv(env []string) { cmd.Cmd.Env = env }
func (n *nix) Nix(ctx context.Context, arg ...string) Cmd {
cmd := exec.CommandContext(ctx, n.name, append(n.extra, arg...)...)
cmd.Cancel = func() error { return cmd.Process.Signal(os.Interrupt) }
cmd.WaitDelay = DefaultWaitDelay
if n.store != nil {
cmd.Env = append(cmd.Env, n.store.Environ()...)
}
return ExecCmd{cmd}
}
func (n *nix) WriteStdin(cmd Cmd, installables iter.Seq[string], f func() error) (int, error) {
w, err := cmd.StdinPipe()
if err != nil {
return 0, err
}
if err = cmd.Start(); err != nil {
return 0, err
}
count, writeErr := WriteStdin(w, installables)
closeErr := w.Close()
var fErr error
if f != nil && writeErr == nil && closeErr == nil {
fErr = f()
}
return count, errors.Join(writeErr, closeErr, fErr, cmd.Wait())
}