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() }