pkg/tool/exec: fix stdin processing

Change-Id: I192ac14ae1123fd115c048c44fec18cd37a626f7
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4600
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/custom.go b/cmd/cue/cmd/custom.go
index 7000921..4835153 100644
--- a/cmd/cue/cmd/custom.go
+++ b/cmd/cue/cmd/custom.go
@@ -298,7 +298,7 @@
 			// NOTE: ignore the linter warning for the following line:
 			// itask.Context is an internal type and we want to break if any
 			// fields are added.
-			c := &itask.Context{ctx, stdout, stderr, obj, nil}
+			c := &itask.Context{ctx, stdin, stdout, stderr, obj, nil}
 			update, err := t.Run(c)
 			if c.Err != nil {
 				err = c.Err
diff --git a/cmd/cue/cmd/testdata/script/cmd_stdin.txt b/cmd/cue/cmd/testdata/script/cmd_stdin.txt
new file mode 100644
index 0000000..a397a47
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_stdin.txt
@@ -0,0 +1,27 @@
+stdin stdin.txt
+cue cmd echo
+cmp stdout expect-stdout
+
+
+-- expect-stdout --
+Hello World!
+
+-- stdin.txt --
+Hello World!
+-- hello_tool.cue --
+package hello
+
+command: echo: {
+    echo: {
+        kind:   "exec"
+        cmd:    "cat"
+        stdin: string
+        stdout: string
+    }
+
+    task: display: {
+        kind: "print"
+        text: echo.stdout
+    }
+}
+-- cue.mod --
diff --git a/internal/task/task.go b/internal/task/task.go
index a6e563c..a6a54d2 100644
--- a/internal/task/task.go
+++ b/internal/task/task.go
@@ -27,6 +27,7 @@
 // A Context provides context for running a task.
 type Context struct {
 	Context context.Context
+	Stdin   io.Reader
 	Stdout  io.Writer
 	Stderr  io.Writer
 	Obj     cue.Value
diff --git a/pkg/tool/exec/doc.go b/pkg/tool/exec/doc.go
index 484c361..ff150d0 100644
--- a/pkg/tool/exec/doc.go
+++ b/pkg/tool/exec/doc.go
@@ -26,8 +26,9 @@
 //     	// stderr is like stdout, but for errors.
 //     	stderr: *null | string | bytes
 //
-//     	// stdin specifies the input for the process. If null, stdin of the current
-//     	// process is used.
+//     	// stdin specifies the input for the process. If stdin is not a concrete
+//     	// value, it will capture the input from stdin. Otherwise, if it is null,
+//     	// there is no input. Otherwise it will read from the given value.
 //     	stdin: *null | string | bytes
 //
 //     	// success is set to true when the process terminates with with a zero exit
diff --git a/pkg/tool/exec/exec.cue b/pkg/tool/exec/exec.cue
index 7ab429a..be7843e 100644
--- a/pkg/tool/exec/exec.cue
+++ b/pkg/tool/exec/exec.cue
@@ -36,8 +36,9 @@
 	// stderr is like stdout, but for errors.
 	stderr: *null | string | bytes
 
-	// stdin specifies the input for the process. If null, stdin of the current
-	// process is used.
+	// stdin specifies the input for the process. If stdin is not a concrete
+	// value, it will capture the input from stdin. Otherwise, if it is null,
+	// there is no input. Otherwise it will read from the given value.
 	stdin: *null | string | bytes
 
 	// success is set to true when the process terminates with with a zero exit
diff --git a/pkg/tool/exec/exec.go b/pkg/tool/exec/exec.go
index eee4e3a..32f8d01 100644
--- a/pkg/tool/exec/exec.go
+++ b/pkg/tool/exec/exec.go
@@ -98,7 +98,9 @@
 	}
 
 	if v, ok := stream("stdin"); ok {
-		if cmd.Stdin, err = v.Reader(); err != nil {
+		if !v.IsConcrete() {
+			cmd.Stdin = ctx.Stdin
+		} else if cmd.Stdin, err = v.Reader(); err != nil {
 			return nil, fmt.Errorf("cue: %v", err)
 		}
 	}