cmd/cue/cmd: control task dependency using before and after fields
Currently we resolve task dependency by checking reference to incomplete task variable. We cannot do things like `create a file first, then run a command with it`.
For example this command would not work cause `write` and `ansible` will be executed at the same time.
```
command: play: {
task: write: file.Create & {
filename: "playbook.yml"
contents: yaml.MarshalStream([playbook])
}
task: ansible: exec.Run & {
cmd: "ansible-playbook playbook.yml"
stdout: string
}
task: display: cli.Print & {
text: task.ansible.stdout
}
}
```
Using this PR, we could declare dependency and reverse dependency using `before` and `after` fields. The value could be either a task or a list of tasks. Now we can rewrite the command like this to make it work.
```
command: play: {
task: write: file.Create & {
filename: "playbook.yml"
contents: yaml.MarshalStream([playbook])
}
task: ansible: exec.Run & {
cmd: "ansible-playbook playbook.yml"
stdout: string
after: task.write
}
task: display: cli.Print & {
text: task.ansible.stdout
}
}
```
Closes #200
https://github.com/cuelang/cue/pull/200
GitOrigin-RevId: 362c703d71dd425654f52d3e7aecf811cffa4a19
Change-Id: I075b871745f2b5dc18788d2d0c02d0623fced6c4
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4340
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/custom.go b/cmd/cue/cmd/custom.go
index b8670a4..f508ae8 100644
--- a/cmd/cue/cmd/custom.go
+++ b/cmd/cue/cmd/custom.go
@@ -127,6 +127,16 @@
return err
}
+func isTask(index map[taskKey]*task, root *cue.Instance, value cue.Value) (*task, bool) {
+ inst, path := value.Reference()
+ if path != nil && inst == root {
+ if task, ok := index[keyForReference(path)]; ok {
+ return task, true
+ }
+ }
+ return nil, false
+}
+
// executeTasks runs user-defined tasks as part of a user-defined command.
//
// All tasks are started at once, but will block until tasks that they depend
@@ -155,9 +165,48 @@
// Mark dependencies for unresolved nodes.
for _, t := range queue {
task := tasks.Lookup(t.name)
+
+ // Inject dependency in `$after` field
+ after := task.Lookup("$after")
+ if after.Err() == nil {
+ if dep, ok := isTask(index, root, after); ok {
+ t.dep[dep] = true
+ }
+ iter, err := after.List()
+ if err == nil {
+ for iter.Next() {
+ if dep, ok := isTask(index, root, iter.Value()); ok {
+ t.dep[dep] = true
+ }
+ }
+ }
+ }
+
+ // Inject reverse dependency in `$before` field
+ before := task.Lookup("$before")
+ if before.Err() == nil {
+ if dep, ok := isTask(index, root, before); ok {
+ dep.dep[t] = true
+ }
+ iter, err := before.List()
+ if err == nil {
+ for iter.Next() {
+ if dep, ok := isTask(index, root, iter.Value()); ok {
+ dep.dep[t] = true
+ }
+ }
+ }
+ }
+
task.Walk(func(v cue.Value) bool {
+ if v == task {
+ return true
+ }
+ if (after.Err() == nil && v.Equals(after)) || (before.Err() == nil && v.Equals(before)) {
+ return false
+ }
for _, r := range appendReferences(nil, root, v) {
- if dep, ok := index[keyForReference(r)]; ok {
+ if dep, ok := index[keyForReference(r)]; ok && t != dep {
v := root.Lookup(r...)
if v.IsIncomplete() && v.Kind() != cue.StructKind {
t.dep[dep] = true
diff --git a/cmd/cue/cmd/testdata/script/cmd_after.txt b/cmd/cue/cmd/testdata/script/cmd_after.txt
new file mode 100644
index 0000000..6849774
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_after.txt
@@ -0,0 +1,40 @@
+cue cmd after
+cmp stdout expect-stdout
+
+-- expect-stdout --
+true
+
+-- after_tool.cue --
+package home
+
+import (
+ "tool/exec"
+ "tool/cli"
+ "strings"
+)
+
+command: after: {
+ task: {
+ t1: exec.Run & {
+ cmd: ["sh", "-c", "sleep 2; date +%s"]
+ stdout: string
+ }
+ t2: exec.Run & {
+ cmd: ["sh", "-c", "date +%s"]
+ stdout: string
+ $after: task.t1
+ }
+ t3: exec.Run & {
+ cmd: ["sh", "-c", "a=\(strings.TrimSpace(task.t1.stdout));b=\(strings.TrimSpace(task.t2.stdout));if [ $a -le $b ]; then echo 'true'; fi"]
+ stdout: string
+ }
+ t4: cli.Print & {
+ text: task.t3.stdout
+ }
+ }
+}
+
+-- task.cue --
+package home
+
+-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/cmd_before.txt b/cmd/cue/cmd/testdata/script/cmd_before.txt
new file mode 100644
index 0000000..762509c
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_before.txt
@@ -0,0 +1,45 @@
+cue cmd before
+cmp stdout expect-stdout
+
+-- expect-stdout --
+true
+
+-- before_tool.cue --
+package home
+
+import (
+ "tool/exec"
+ "tool/cli"
+ "strings"
+)
+
+command: before: {
+ task: {
+ t1: exec.Run & {
+ cmd: ["sh", "-c", "sleep 2; date +%s"]
+ stdout: string
+ $before: [task.t2, task.t3]
+ }
+ t2: exec.Run & {
+ cmd: ["sh", "-c", "date +%s"]
+ stdout: string
+ $before: task.t4
+ }
+ t3: exec.Run & {
+ cmd: ["sh", "-c", "date +%s"]
+ stdout: string
+ }
+ t4: exec.Run & {
+ cmd: ["sh", "-c", "a=\(strings.TrimSpace(task.t1.stdout));b=\(strings.TrimSpace(task.t2.stdout));if [ $a -le $b ]; then echo 'true'; fi"]
+ stdout: string
+ }
+ t5: cli.Print & {
+ text: task.t4.stdout
+ }
+ }
+}
+
+-- task.cue --
+package home
+
+-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/cmd_ref.txt b/cmd/cue/cmd/testdata/script/cmd_ref.txt
new file mode 100644
index 0000000..5141b3f
--- /dev/null
+++ b/cmd/cue/cmd/testdata/script/cmd_ref.txt
@@ -0,0 +1,29 @@
+cue cmd ref
+cmp stdout expect-stdout
+
+-- expect-stdout --
+hello
+
+-- task_tool.cue --
+package home
+
+import (
+ "tool/cli"
+)
+
+command: ref: {
+ task: {
+ t1: exec.Run & {
+ ref: task.t1.stdout
+ cmd: ["sh", "-c", "echo hello"]
+ stdout: string
+ }
+ t2: cli.Print & {
+ text: task.t1.stdout
+ }
+ }
+}
+
+-- task.cue --
+package home
+-- cue.mod --