cmd/cue/cmd: allow task hierarchy
Change-Id: I5a14d62126739e776061b36ad33aae5d54c64715
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4442
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/custom.go b/cmd/cue/cmd/custom.go
index 60a9576..39684db 100644
--- a/cmd/cue/cmd/custom.go
+++ b/cmd/cue/cmd/custom.go
@@ -43,7 +43,6 @@
const (
commandSection = "command"
- taskSection = "task"
)
func lookupString(obj cue.Value, key, def string) string {
@@ -113,91 +112,112 @@
return sub, nil
}
-type taskKey struct {
- typ string
+type customRunner struct {
name string
- task string
+ root *cue.Instance
+
+ index map[taskKey]*task
}
-func (k taskKey) keyForTask(taskName string) taskKey {
- k.task = taskName
- return k
+type taskKey string
+
+func (r *customRunner) keyForTask(t *task) taskKey {
+ a := []string{commandSection, r.name}
+ return keyForReference(append(a, t.path...)...)
}
-func keyForReference(ref []string) (k taskKey) {
- // command <command> task <task>
- if len(ref) >= 4 && ref[2] == taskSection {
- k.typ = ref[0]
- k.name = ref[1]
- k.task = ref[3]
- }
- return k
+func keyForReference(ref ...string) (k taskKey) {
+ return taskKey(strings.Join(ref, "\000"))
}
-func (k taskKey) taskPath(task string) []string {
- k.task = task
- return []string{k.typ, k.name, taskSection, task}
+func (r *customRunner) taskPath(t *task) []string {
+ return append([]string{commandSection, r.name}, t.path...)
}
-func (k *taskKey) lookupTasks(root *cue.Instance) cue.Value {
- return root.Lookup(k.typ, k.name, taskSection)
+func (r *customRunner) lookupTasks() cue.Value {
+ return r.root.Lookup(commandSection, r.name)
}
func doTasks(cmd *Command, typ, command string, root *cue.Instance) error {
- err := executeTasks(typ, command, root)
+ err := executeTasks(cmd, typ, command, root)
exitIfErr(cmd, root, err, true)
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 {
+func (r *customRunner) referredTask(ref cue.Value) (t *task, ok bool) {
+ inst, path := ref.Reference()
+ if path != nil && inst == r.root {
+ if task := r.findTask(path); task != nil {
return task, true
}
}
return nil, false
}
+func (r *customRunner) findTask(ref []string) *task {
+ for ; len(ref) > 2; ref = ref[:len(ref)-1] {
+ if t := r.index[keyForReference(ref...)]; t != nil {
+ return t
+ }
+ }
+ return nil
+}
+
+func getTasks(q []*task, v cue.Value, stack []string) ([]*task, error) {
+ if v.Lookup("$id").Exists() || v.Lookup("kind").Exists() {
+ t, err := newTask(len(q), stack, v)
+ if err != nil {
+ return nil, err
+ }
+ return append(q, t), nil
+ }
+
+ for iter, _ := v.Fields(); iter.Next(); {
+ var err error
+ q, err = getTasks(q, iter.Value(), append(stack, iter.Label()))
+ if err != nil {
+ return nil, err
+ }
+ }
+ return q, nil
+}
+
// 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
// on will continue.
-func executeTasks(typ, command string, root *cue.Instance) (err error) {
- spec := taskKey{typ, command, ""}
- tasks := spec.lookupTasks(root)
-
- index := map[taskKey]*task{}
+func executeTasks(cmd *Command, typ, command string, inst *cue.Instance) (err error) {
+ cr := &customRunner{
+ name: command,
+ root: inst,
+ index: map[taskKey]*task{},
+ }
+ tasks := cr.lookupTasks()
// Create task entries from spec.
- queue := []*task{}
- iter, err := tasks.Fields()
+ queue, err := getTasks(nil, tasks, nil)
if err != nil {
return err
}
- for i := 0; iter.Next(); i++ {
- t, err := newTask(i, iter.Label(), iter.Value())
- if err != nil {
- return err
- }
- queue = append(queue, t)
- index[spec.keyForTask(iter.Label())] = t
+
+ for _, t := range queue {
+ cr.index[cr.keyForTask(t)] = t
}
// Mark dependencies for unresolved nodes.
for _, t := range queue {
- task := tasks.Lookup(t.name)
+ task := tasks.Lookup(t.path...)
// Inject dependency in `$after` field
after := task.Lookup("$after")
if after.Err() == nil {
- if dep, ok := isTask(index, root, after); ok {
+ if dep, ok := cr.referredTask(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 {
+ if dep, ok := cr.referredTask(iter.Value()); ok {
t.dep[dep] = true
}
}
@@ -207,13 +227,13 @@
// Inject reverse dependency in `$before` field
before := task.Lookup("$before")
if before.Err() == nil {
- if dep, ok := isTask(index, root, before); ok {
+ if dep, ok := cr.referredTask(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 {
+ if dep, ok := cr.referredTask(iter.Value()); ok {
dep.dep[t] = true
}
}
@@ -227,9 +247,9 @@
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 && t != dep {
- v := root.Lookup(r...)
+ for _, r := range appendReferences(nil, cr.root, v) {
+ if dep := cr.findTask(r); dep != nil && t != dep {
+ v := cr.root.Lookup(r...)
if v.IsIncomplete() && v.Kind() != cue.StructKind {
t.dep[dep] = true
}
@@ -261,16 +281,16 @@
// code does not look up new strings in the index and that the
// full configuration, as used by the tasks, is pre-evaluated.
m.Lock()
- obj := tasks.Lookup(t.name)
+ obj := tasks.Lookup(t.path...)
// 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.
update, err := t.Run(&itask.Context{ctx, stdout, stderr}, obj)
if err == nil && update != nil {
- root, err = root.Fill(update, spec.taskPath(t.name)...)
+ cr.root, err = cr.root.Fill(update, cr.taskPath(t)...)
if err == nil {
- tasks = spec.lookupTasks(root)
+ tasks = cr.lookupTasks()
}
}
m.Unlock()
@@ -348,7 +368,7 @@
itask.Runner
index int
- name string
+ path []string
done chan error
dep map[*task]bool
}
@@ -360,7 +380,7 @@
"testserver": "cmd/cue/cmd.Test",
}
-func newTask(index int, name string, v cue.Value) (*task, error) {
+func newTask(index int, path []string, v cue.Value) (*task, error) {
kind, err := v.Lookup("$id").String()
if err != nil {
// Lookup kind for backwards compatibility.
@@ -391,7 +411,7 @@
return &task{
Runner: runner,
index: index,
- name: name,
+ path: path,
done: make(chan error),
dep: make(map[*task]bool),
}, nil
diff --git a/cmd/cue/cmd/testdata/script/cmd_after.txt b/cmd/cue/cmd/testdata/script/cmd_after.txt
index 6849774..a2704c3 100644
--- a/cmd/cue/cmd/testdata/script/cmd_after.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_after.txt
@@ -14,23 +14,21 @@
)
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
- }
+ t1: exec.Run & {
+ cmd: ["sh", "-c", "sleep 2; date +%s"]
+ stdout: string
+ }
+ t2: exec.Run & {
+ cmd: ["sh", "-c", "date +%s"]
+ stdout: string
+ $after: t1
+ }
+ t3: exec.Run & {
+ cmd: ["sh", "-c", "a=\(strings.TrimSpace(t1.stdout));b=\(strings.TrimSpace(t2.stdout));if [ $a -le $b ]; then echo 'true'; fi"]
+ stdout: string
+ }
+ t4: cli.Print & {
+ text: t3.stdout
}
}
diff --git a/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt b/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt
index a41547b..101c0d9 100644
--- a/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_baddisplay.txt
@@ -14,7 +14,7 @@
package home
command: baddisplay: {
- task: display: {
+ display: {
kind: "print"
text: 42
}
diff --git a/cmd/cue/cmd/testdata/script/cmd_before.txt b/cmd/cue/cmd/testdata/script/cmd_before.txt
index 762509c..813bb77 100644
--- a/cmd/cue/cmd/testdata/script/cmd_before.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_before.txt
@@ -14,28 +14,26 @@
)
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
- }
+ t1: exec.Run & {
+ cmd: ["sh", "-c", "sleep 2; date +%s"]
+ stdout: string
+ $before: [t2, t3]
+ }
+ t2: exec.Run & {
+ cmd: ["sh", "-c", "date +%s"]
+ stdout: string
+ $before: t4
+ }
+ t3: exec.Run & {
+ cmd: ["sh", "-c", "date +%s"]
+ stdout: string
+ }
+ t4: exec.Run & {
+ cmd: ["sh", "-c", "a=\(strings.TrimSpace(t1.stdout));b=\(strings.TrimSpace(t2.stdout));if [ $a -le $b ]; then echo 'true'; fi"]
+ stdout: string
+ }
+ t5: cli.Print & {
+ text: t4.stdout
}
}
diff --git a/cmd/cue/cmd/testdata/script/cmd_dep.txt b/cmd/cue/cmd/testdata/script/cmd_dep.txt
index 67d3fe8..25484d7 100644
--- a/cmd/cue/cmd/testdata/script/cmd_dep.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_dep.txt
@@ -19,17 +19,15 @@
}
command: do: {
- task: name: exec.Run & {
- cmd: "echo cue"
+ inputs: name: exec.Run & {
+ cmd: "echo cue"
stdout: string
}
- task: out: cli.Print & {
+ outputs: print: cli.Print & {
text: json.Marshal(foo & {
- a :: strings.TrimSpace(task.name.stdout)
+ a :: strings.TrimSpace(inputs.name.stdout)
})
}
}
--- task.cue --
-package home
-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/cmd_echo.txt b/cmd/cue/cmd/testdata/script/cmd_echo.txt
index a626358..4468d6c 100644
--- a/cmd/cue/cmd/testdata/script/cmd_echo.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_echo.txt
@@ -13,7 +13,7 @@
package hello
command: echo: {
- task: echo: {
+ echo: {
kind: "exec"
cmd: "echo \(message)"
stdout: string
@@ -21,7 +21,7 @@
task: display: {
kind: "print"
- text: task.echo.stdout
+ text: echo.stdout
}
}
-- cue.mod --
diff --git a/cmd/cue/cmd/testdata/script/cmd_import.txt b/cmd/cue/cmd/testdata/script/cmd_import.txt
index d401652..0db42f7 100644
--- a/cmd/cue/cmd/testdata/script/cmd_import.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_import.txt
@@ -9,10 +9,8 @@
// missing imports
command: pkg: {
- task: {
- t2: cli.Print & {
- text: "Hello world!"
- }
+ t: cli.Print & {
+ text: "Hello world!"
}
}
diff --git a/cmd/cue/cmd/testdata/script/cmd_print.txt b/cmd/cue/cmd/testdata/script/cmd_print.txt
index 1f032b3..4fb822a 100644
--- a/cmd/cue/cmd/testdata/script/cmd_print.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_print.txt
@@ -15,18 +15,16 @@
)
command: print: {
- task: {
- t1: exec.Run & {
- cmd: ["sh", "-c", "sleep 1; echo t1"]
- stdout: string
- }
- t2: exec.Run & {
- cmd: ["sh", "-c", "sleep 1; echo t2"]
- stdout: string
- }
- t3: cli.Print & {
- text: (f & {arg: t1.stdout + t2.stdout}).result
- }
+ runs: t1: exec.Run & {
+ cmd: ["sh", "-c", "sleep 1; echo t1"]
+ stdout: string
+ }
+ runs: t2: exec.Run & {
+ cmd: ["sh", "-c", "sleep 1; echo t2"]
+ stdout: string
+ }
+ print: cli.Print & {
+ text: (f & {arg: runs.t1.stdout + runs.t2.stdout}).result
}
}
diff --git a/cmd/cue/cmd/testdata/script/cmd_ref.txt b/cmd/cue/cmd/testdata/script/cmd_ref.txt
index 456f992..8aa8eb2 100644
--- a/cmd/cue/cmd/testdata/script/cmd_ref.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_ref.txt
@@ -13,15 +13,13 @@
)
command: ref: {
- task: {
- t1: exec.Run & {
- ref: task.t1.stdout
- cmd: ["sh", "-c", "echo hello"]
- stdout: string
- }
- t2: cli.Print & {
- text: task.t1.stdout
- }
+ t1: exec.Run & {
+ ref: t1.stdout
+ cmd: ["sh", "-c", "echo hello"]
+ stdout: string
+ }
+ t2: cli.Print & {
+ text: t1.stdout
}
}