treewide: use internal pipe for nix command

This essentially does the same thing underneath the hood but the API is less painful to use, and it makes more sense in this use case.
This commit is contained in:
Yonah 2025-07-15 01:14:27 +09:00
parent be2b069edf
commit 68ae0efe19
Signed by: yonah
SSH Key Fingerprint: SHA256:vnQvK8+XXH9Tbni2AV1a/8qdVK/zPcXw52GM0ruQvwA
2 changed files with 18 additions and 33 deletions

View File

@ -2,6 +2,7 @@ package nixbuild
import (
"context"
"io"
"iter"
"strings"
)
@ -24,12 +25,10 @@ func Build(ctx context.Context, installables iter.Seq[string]) error {
cmd.Args = append(cmd.Args, FlagVerbose)
}
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
ir, iw := io.Pipe()
cmd.Stdin = ir
if err = cmd.Start(); err != nil {
if err := cmd.Start(); err != nil {
return err
}
@ -38,12 +37,12 @@ func Build(ctx context.Context, installables iter.Seq[string]) error {
// this is just what nix requires now :c
drv += "^*"
}
if _, err = stdin.Write([]byte(drv + "\n")); err != nil {
if _, err := iw.Write([]byte(drv + "\n")); err != nil {
return err
}
}
if err = stdin.Close(); err != nil {
if err := iw.Close(); err != nil {
return err
}

View File

@ -8,10 +8,9 @@ import (
"iter"
"os/exec"
"path"
"runtime"
"slices"
"strings"
"syscall"
"sync"
)
const (
@ -132,16 +131,18 @@ func (d *InstantiatedDecoder) Decode() ([]string, error) {
type InstantiatedEvaluator struct {
// underlying nix program
cmd *exec.Cmd
// kills the nix command
cancel context.CancelFunc
// populated by Close
waitErr error
// synchronises access to waitErr
waitMu sync.RWMutex
*InstantiatedDecoder
}
// Err returns the first error encountered by the [InstantiatedEvaluator].
// Err blocks until process exit and returns the first error encountered by the [InstantiatedEvaluator].
func (e *InstantiatedEvaluator) Err() error {
e.waitMu.RLock()
defer e.waitMu.RUnlock()
return errors.Join(e.waitErr, e.InstantiatedDecoder.Err())
}
@ -157,41 +158,26 @@ func NewInstantiatedEvaluator(ctx context.Context, installable string) (*Instant
// increase verbosity so nix outputs 'instantiated' messages
FlagPrintBuildLogs, FlagDebug,
),
cancel: cancel,
}
e.cmd.Stdout = Stdout
// verbose output ends up on stderr in the current nix implementation
if stderr, err := e.cmd.StderrPipe(); err != nil {
cancel()
return nil, err
} else {
e.InstantiatedDecoder = NewInstantiatedDecoder(stderr)
}
er, ew := io.Pipe()
e.cmd.Stderr = ew
e.InstantiatedDecoder = NewInstantiatedDecoder(er)
if err := e.cmd.Start(); err != nil {
cancel()
// have finalizer take care of the pipe
return nil, err
}
e.waitMu.Lock()
go func() { e.waitErr = e.cmd.Wait(); cancel(); _ = ew.Close(); e.waitMu.Unlock() }()
runtime.SetFinalizer(e, (*InstantiatedEvaluator).Close)
return e, nil
}
// Close releases system resources held by [InstantiatedEvaluator].
func (e *InstantiatedEvaluator) Close() error {
if e.cmd.ProcessState != nil {
return syscall.EBADE
}
e.cancel()
e.waitErr = e.cmd.Wait()
runtime.SetFinalizer(e, nil)
return e.Err()
}
// EvalInstantiated calls the underlying [InstantiatedDecoder.Decode] of a new [InstantiatedEvaluator].
func EvalInstantiated(ctx context.Context, installable string) ([]string, error) {
evaluator, err := NewInstantiatedEvaluator(ctx, installable)
@ -199,5 +185,5 @@ func EvalInstantiated(ctx context.Context, installable string) ([]string, error)
return nil, err
}
instantiated, _ := evaluator.Decode() // error joined by Close
return instantiated, evaluator.Close()
return instantiated, evaluator.Err()
}