From a3427ce7dd73cd622102ac3811160bf08cb6a62d Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 13 Sep 2025 13:06:39 +0900 Subject: [PATCH] context: interface command type This should allow caller to override the method to run in a container. --- build.go | 10 +++++----- context.go | 29 +++++++++++++++++++++++++---- copy.go | 8 ++++---- derivation.go | 2 +- exec.go | 15 ++++++++++++--- exec_stub_test.go | 6 +++--- exec_test.go | 8 ++++---- instantiated.go | 5 ++--- package.nix | 22 +++++++++++++++------- sign.go | 2 +- 10 files changed, 72 insertions(+), 35 deletions(-) diff --git a/build.go b/build.go index a2a4916..88987a1 100644 --- a/build.go +++ b/build.go @@ -14,14 +14,14 @@ func Build(ctx Context, installables iter.Seq[string]) error { cmd := ctx.Nix(c, CommandBuild, FlagKeepGoing, FlagNoLink, FlagStdin) if stdout != nil { - cmd.Stdout = stdout - cmd.Args = append(cmd.Args, FlagPrintBuildLogs) + cmd.Stdout(stdout) + cmd.AppendArgs(FlagPrintBuildLogs) } else { - cmd.Args = append(cmd.Args, FlagQuiet) + cmd.AppendArgs(FlagQuiet) } if stderr != nil { - cmd.Stderr = stderr - cmd.Args = append(cmd.Args, FlagVerbose) + cmd.Stderr(stderr) + cmd.AppendArgs(FlagVerbose) } _, err := ctx.WriteStdin(cmd, installables, nil) diff --git a/context.go b/context.go index 9d2b561..7aaa2de 100644 --- a/context.go +++ b/context.go @@ -4,19 +4,40 @@ import ( "context" "io" "iter" - "os/exec" ) +// Cmd represents an external command or container being prepared to run. +type Cmd interface { + Start() error + Wait() error + StdinPipe() (io.WriteCloser, error) + StdoutPipe() (io.ReadCloser, error) + StderrPipe() (io.ReadCloser, error) + + Stdin(io.Reader) + Stdout(io.Writer) + Stderr(io.Writer) + AppendArgs(a ...string) + ReplaceEnv(env []string) +} + // Context holds configuration and environment information for interacting with nix. type Context interface { // Streams returns the stdout and stderr writers held by this [Context]. Streams() (stdout, stderr io.Writer) - // Nix returns the [exec.Cmd] struct to execute a nix command. - Nix(ctx context.Context, arg ...string) *exec.Cmd + // Nix returns an implementation of [Cmd] for running a nix command. + Nix(ctx context.Context, arg ...string) Cmd // WriteStdin calls [WriteStdin] for [exec.Cmd]. The function f points to is called if [WriteStdin] succeeds. - WriteStdin(cmd *exec.Cmd, installables iter.Seq[string], f func() error) (int, error) + WriteStdin(cmd Cmd, installables iter.Seq[string], f func() error) (int, error) // Unwrap returns the stored [context.Context] Unwrap() context.Context } + +// setStreams sets the stdout and stderr streams of [Cmd] according to [Context]. +func setStreams(ctx Context, cmd Cmd) { + stdout, stderr := ctx.Streams() + cmd.Stdout(stdout) + cmd.Stderr(stderr) +} diff --git a/copy.go b/copy.go index af4c49d..81d9d91 100644 --- a/copy.go +++ b/copy.go @@ -6,7 +6,7 @@ import ( "os" ) -// Copy copies installables to the binary cache store. +// Copy copies installables to [Store]. func Copy(ctx Context, store Store, installables iter.Seq[string]) error { if store == nil { return os.ErrInvalid @@ -18,13 +18,13 @@ func Copy(ctx Context, store Store, installables iter.Seq[string]) error { cmd := ctx.Nix(c, CommandCopy, FlagTo, store.String(), FlagStdin) - cmd.Env = append(os.Environ(), store.Environ()...) + cmd.ReplaceEnv(append(os.Environ(), store.Environ()...)) if _, ok := store.(Local); ok { // this is required for chroot stores, but does not seem to have any effect on binary cache - cmd.Args = append(cmd.Args, FlagNoCheckSigs) + cmd.AppendArgs(FlagNoCheckSigs) } - cmd.Stdout, cmd.Stderr = ctx.Streams() + setStreams(ctx, cmd) _, err := ctx.WriteStdin(cmd, installables, nil) return err } diff --git a/derivation.go b/derivation.go index 574395f..b0b4633 100644 --- a/derivation.go +++ b/derivation.go @@ -60,7 +60,7 @@ func DerivationShow(ctx Context, installables iter.Seq[string]) (DerivationMap, decoder = json.NewDecoder(r) } if stderr != nil { - cmd.Stderr = stderr + cmd.Stderr(stderr) } var v DerivationMap diff --git a/exec.go b/exec.go index b089737..9867c52 100644 --- a/exec.go +++ b/exec.go @@ -58,17 +58,26 @@ func New(ctx context.Context, store Store, extraArgs []string, stdout, stderr io } } -func (n *nix) Nix(ctx context.Context, arg ...string) *exec.Cmd { +// 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 cmd + return ExecCmd{cmd} } -func (n *nix) WriteStdin(cmd *exec.Cmd, installables iter.Seq[string], f func() error) (int, error) { +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 diff --git a/exec_stub_test.go b/exec_stub_test.go index bbf552b..2816903 100644 --- a/exec_stub_test.go +++ b/exec_stub_test.go @@ -63,10 +63,10 @@ type stubContextCommand struct { nix.Context } -func (s *stubContextCommand) Nix(ctx context.Context, arg ...string) *exec.Cmd { - cmd := s.Context.Nix(ctx, arg...) +func (s *stubContextCommand) Nix(ctx context.Context, arg ...string) nix.Cmd { + cmd := s.Context.Nix(ctx, arg...).(nix.ExecCmd) if s.f != nil { - s.f(cmd) + s.f(cmd.Cmd) } return cmd } diff --git a/exec_test.go b/exec_test.go index a4ead44..c600789 100644 --- a/exec_test.go +++ b/exec_test.go @@ -24,7 +24,7 @@ func TestNixWriteStdin(t *testing.T) { CredentialsPath: "/dev/null", KeyPath: nonexistent, }, nil, nil, nil) - cmd := ctx.Nix(t.Context(), nix.FlagVersion) + cmd := ctx.Nix(t.Context(), nix.FlagVersion).(nix.ExecCmd) wantArgs := []string{ nix.Nix, @@ -43,8 +43,8 @@ func TestNixWriteStdin(t *testing.T) { t.Run("already set", func(t *testing.T) { ctx := nix.New(t.Context(), nil, nil, os.Stdout, os.Stderr) - cmd := exec.CommandContext(t.Context(), nonexistent) - cmd.Stdin = os.Stdin + cmd := nix.ExecCmd{Cmd: exec.CommandContext(t.Context(), nonexistent)} + cmd.Stdin(os.Stdin) if _, err := ctx.WriteStdin(cmd, nil, nil); err == nil { t.Fatal("WriteStdinCommand unexpectedly succeeded") } @@ -68,7 +68,7 @@ func TestNixWriteStdin(t *testing.T) { ctx := newStubContext(t.Context(), nil, os.Stdout, os.Stderr) c, cancel := context.WithCancel(t.Context()) defer cancel() - cmd := ctx.Nix(c, "true") + cmd := ctx.Nix(c, "true").(nix.ExecCmd) if err := cmd.Start(); err != nil { t.Fatalf("Start: error = %v", err) } diff --git a/instantiated.go b/instantiated.go index cc0ebc5..564045e 100644 --- a/instantiated.go +++ b/instantiated.go @@ -6,7 +6,6 @@ import ( "errors" "io" "iter" - "os/exec" "path" "slices" "strings" @@ -153,7 +152,7 @@ func (d *InstantiatedDecoder) Decode() ([]string, error) { // are significantly more complete than `nix-store -qR` and there does not appear to be a better way. type InstantiatedEvaluator struct { // underlying nix program - cmd *exec.Cmd + cmd Cmd // populated by Close waitErr error // synchronises access to waitErr @@ -184,7 +183,7 @@ func NewInstantiatedEvaluator(ctx Context, installable string) (*InstantiatedEva } stdout, stderr := ctx.Streams() - e.cmd.Stdout = stdout + e.cmd.Stdout(stdout) // verbose output ends up on stderr in the current nix implementation if r, err := e.cmd.StderrPipe(); err != nil { diff --git a/package.nix b/package.nix index 526c74f..3d0414d 100644 --- a/package.nix +++ b/package.nix @@ -3,20 +3,28 @@ stdenv, buildGoModule, pkg-config, + libffi, + libseccomp, }: buildGoModule { pname = "nix-tool"; version = "0.1.4"; src = ./.; - vendorHash = "sha256-lK9+fI8/GR2GY6X899HnoP28FuQnklYbzaiGqIkus8c="; + vendorHash = "sha256-AUSqbsXvJvkOE0BjO6XnPTDD2NkOHY2XUbo7jZtYmd4="; - ldflags = - [ "-s -w" ] - ++ lib.optionals stdenv.hostPlatform.isStatic [ - "-linkmode external" - "-extldflags \"-static\"" - ]; + ldflags = [ + "-s -w" + ] + ++ lib.optionals stdenv.hostPlatform.isStatic [ + "-linkmode external" + "-extldflags \"-static\"" + ]; + + buildInputs = [ + libffi + libseccomp + ]; nativeBuildInputs = [ pkg-config diff --git a/sign.go b/sign.go index ec1adf5..cf58f1f 100644 --- a/sign.go +++ b/sign.go @@ -17,7 +17,7 @@ func Sign(ctx Context, keyPath string, installables iter.Seq[string]) error { FlagKeyFile, keyPath, FlagStdin) - cmd.Stdout, cmd.Stderr = ctx.Streams() + setStreams(ctx, cmd) _, err := ctx.WriteStdin(cmd, installables, nil) return err }