From 61c6b5d78e9ac98748fddb4f6f483fb10802aaec Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 15 Jul 2025 01:14:27 +0900 Subject: [PATCH] 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. --- build.go | 13 ++++++------- instantiated.go | 38 ++++++++++++-------------------------- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/build.go b/build.go index bef9f14..6f3398c 100644 --- a/build.go +++ b/build.go @@ -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 } diff --git a/instantiated.go b/instantiated.go index 44e4696..6d3651f 100644 --- a/instantiated.go +++ b/instantiated.go @@ -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() }