Skip to content
This repository has been archived by the owner on Nov 18, 2021. It is now read-only.

Commit

Permalink
cmd/cue/cmd: allow top-level tasks
Browse files Browse the repository at this point in the history
This will allow tasks referring to some shared top-level
task, like `env`. Tasks are only enabled if explicitly
referenced.

Change-Id: Ia05b2ecb1f9140a39ba70a8a41b94a4733d4eda8
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4908
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
  • Loading branch information
mpvl committed Feb 17, 2020
1 parent 429bd0f commit 511584e
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 33 deletions.
100 changes: 67 additions & 33 deletions cmd/cue/cmd/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package cmd
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -118,7 +117,10 @@ type customRunner struct {
name string
root *cue.Instance

tasks []*task
index map[taskKey]*task

allErrors errors.Error
}

type taskKey string
Expand All @@ -137,6 +139,18 @@ func doTasks(cmd *Command, typ, command string, root *cue.Instance) error {
return err
}

func (r *customRunner) insert(stack []string, v cue.Value) *task {
t, err := newTask(stack, v)
if err != nil {
r.allErrors = errors.Append(r.allErrors, err)
return nil
}
t.index = len(r.tasks)
r.tasks = append(r.tasks, t)
r.index[r.keyForTask(t)] = t
return t
}

func (r *customRunner) tagReference(t *task, ref cue.Value) error {
inst, path := ref.Reference()
if len(path) == 0 {
Expand Down Expand Up @@ -168,47 +182,66 @@ func (r *customRunner) tagDependencies(t *task, ref []string) bool {
t.dep[task] = true
}
}
return found
if found {
return true
}

v := r.root.Lookup(ref...)
if isTask(v) {
if task := r.insert(ref, v); task != nil {
t.dep[task] = true
return true
}
}

return false
}

func (r *customRunner) findTask(ref []string) *task {
for ; len(ref) > 0; ref = ref[:len(ref)-1] {
for ref := ref; len(ref) > 0; ref = ref[:len(ref)-1] {
if t := r.index[keyForReference(ref...)]; t != nil {
return t
}
}
for ref := ref; len(ref) > 0; ref = ref[:len(ref)-1] {
v := r.root.Lookup(ref...)
if isTask(v) {
return r.insert(ref, v)
}
}
return nil
}

func getTasks(q []*task, v cue.Value, stack []string) ([]*task, error) {
func isTask(v cue.Value) bool {
return v.Kind() == cue.StructKind &&
(v.Lookup("$id").Exists() || v.Lookup("kind").Exists())
}

func (r *customRunner) getTasks(v cue.Value, stack []string) {
// Allow non-task values, but do not allow errors.
if err := v.Err(); err != nil {
return nil, err
r.allErrors = errors.Append(r.allErrors, errors.Promote(err, "getTasks"))
return
}
if v.Kind()&cue.StructKind == 0 {
return q, nil
return
}

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
if isTask(v) {
_ = r.insert(stack, v)
return
}

for iter, _ := v.Fields(); iter.Next(); {
l := iter.Label()
if strings.HasPrefix(l, "$") || l == "command" || l == "commands" {
continue
}
var err error
q, err = getTasks(q, iter.Value(), append(stack, l))
if err != nil {
return nil, err
r.getTasks(iter.Value(), append(stack, l))
if r.allErrors != nil {
return
}
}
return q, nil
}

// executeTasks runs user-defined tasks as part of a user-defined command.
Expand All @@ -224,17 +257,16 @@ func executeTasks(cmd *Command, typ, command string, inst *cue.Instance) (err er

// Create task entries from spec.
base := []string{commandSection, cr.name}
queue, err := getTasks(nil, cr.root.Lookup(base...), base)
if err != nil {
return err
cr.getTasks(cr.root.Lookup(base...), base)
if cr.allErrors != nil {
return cr.allErrors
}

for _, t := range queue {
cr.index[cr.keyForTask(t)] = t
}
// Mark dependencies for unresolved nodes. Note that cr.tasks may grow
// during iteration, which is why we don't use range.
for i := 0; i < len(cr.tasks); i++ {
t := cr.tasks[i]

// Mark dependencies for unresolved nodes.
for _, t := range queue {
task := cr.root.Lookup(t.path...)

// Inject dependency in `$after` field
Expand Down Expand Up @@ -272,8 +304,11 @@ func executeTasks(cmd *Command, typ, command string, inst *cue.Instance) (err er
return true
}, nil)
}
if cr.allErrors != nil {
return cr.allErrors
}

if isCyclic(queue) {
if isCyclic(cr.tasks) {
return errors.New("cyclic dependency in tasks") // TODO: better message.
}

Expand All @@ -284,7 +319,7 @@ func executeTasks(cmd *Command, typ, command string, inst *cue.Instance) (err er
var m sync.Mutex

g, ctx := errgroup.WithContext(ctx)
for _, t := range queue {
for _, t := range cr.tasks {
t := t
g.Go(func() error {
for d := range t.dep {
Expand Down Expand Up @@ -394,38 +429,37 @@ var legacyKinds = map[string]string{
"testserver": "cmd/cue/cmd.Test",
}

func newTask(index int, path []string, v cue.Value) (*task, error) {
func newTask(path []string, v cue.Value) (*task, errors.Error) {
kind, err := v.Lookup("$id").String()
if err != nil {
// Lookup kind for backwards compatibility.
// TODO: consider at some point whether kind can be removed.
var err1 error
kind, err1 = v.Lookup("kind").String()
if err1 != nil {
return nil, err
return nil, errors.Promote(err1, "newTask")
}
}
if k, ok := legacyKinds[kind]; ok {
kind = k
}
rf := itask.Lookup(kind)
if rf == nil {
return nil, fmt.Errorf("runner of kind %q not found", kind)
return nil, errors.Newf(v.Pos(), "runner of kind %q not found", kind)
}

// Verify entry against template.
v = internal.UnifyBuiltin(v, kind).(cue.Value)
if err := v.Err(); err != nil {
return nil, err
return nil, errors.Promote(err, "newTask")
}

runner, err := rf(v)
if err != nil {
return nil, err
return nil, errors.Promote(err, "errors running task")
}
return &task{
Runner: runner,
index: index,
path: append([]string{}, path...), // make a copy.
done: make(chan error),
dep: make(map[*task]bool),
Expand Down
8 changes: 8 additions & 0 deletions cmd/cue/cmd/testdata/script/cmd_after.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ cue cmd after
cmp stdout expect-stdout

-- expect-stdout --
run also
run
true

SUCCESS
Expand All @@ -14,11 +16,17 @@ import (
"strings"
)

top0: cli.Print & { text: "run also" }
top1: cli.Print & { text: "run", $after: top0 }
top2: cli.Print & { text: "don't run also" }
top3: cli.Print & { text: "don't", $after: top2 }

command: after: {
group: {
t1: exec.Run & {
cmd: ["sh", "-c", "sleep 2; date +%s"]
stdout: string
$after: top1
}
t2: exec.Run & {
cmd: ["sh", "-c", "date +%s"]
Expand Down

0 comments on commit 511584e

Please sign in to comment.