cuelang.org/go@v0.10.1/tools/flow/tasks.go (about)

     1  // Copyright 2020 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package flow
    16  
    17  // This file contains functionality for identifying tasks in the configuration
    18  // and annotating the dependencies between them.
    19  
    20  import (
    21  	"cuelang.org/go/cue"
    22  	"cuelang.org/go/cue/errors"
    23  	"cuelang.org/go/internal/core/adt"
    24  	"cuelang.org/go/internal/core/dep"
    25  	"cuelang.org/go/internal/value"
    26  )
    27  
    28  // initTasks takes the current configuration and adds tasks to the list of
    29  // tasks. It can be run multiple times on increasingly more concrete
    30  // configurations to add more tasks, whereby the task pointers of previously
    31  // found tasks are preserved.
    32  func (c *Controller) initTasks(addStats bool) {
    33  	// Clear previous cache.
    34  	c.nodes = map[*adt.Vertex]*Task{}
    35  
    36  	v := c.inst.LookupPath(c.cfg.Root)
    37  	if err := v.Err(); err != nil {
    38  		c.addErr(err, "invalid root")
    39  		c.cancel()
    40  		return
    41  	}
    42  
    43  	// Mark any task that is located under the root.
    44  	c.findRootTasks(v)
    45  
    46  	// Mark any tasks that are implied by dependencies.
    47  	// Note that the list of tasks may grow as this loop progresses.
    48  	for i := 0; i < len(c.tasks); i++ {
    49  		t := c.tasks[i]
    50  		start := *c.opCtx.Stats()
    51  		c.markTaskDependencies(t, t.vertex())
    52  		if addStats {
    53  			t.stats.Add(c.opCtx.Stats().Since(start))
    54  		}
    55  	}
    56  
    57  	// Check if there are cycles in the task dependencies.
    58  	if err := checkCycle(c.tasks); err != nil {
    59  		c.addErr(err, "cyclic task")
    60  	}
    61  
    62  	if c.errs != nil {
    63  		c.cancel()
    64  	}
    65  }
    66  
    67  // findRootTasks finds tasks under the root.
    68  func (c *Controller) findRootTasks(v cue.Value) {
    69  	t := c.getTask(nil, v)
    70  
    71  	if t != nil {
    72  		return
    73  	}
    74  
    75  	opts := []cue.Option{}
    76  
    77  	if c.cfg.FindHiddenTasks {
    78  		opts = append(opts, cue.Hidden(true), cue.Definitions(false))
    79  	}
    80  
    81  	for iter, _ := v.Fields(opts...); iter.Next(); {
    82  		c.findRootTasks(iter.Value())
    83  	}
    84  	for iter, _ := v.List(); iter.Next(); {
    85  		c.findRootTasks(iter.Value())
    86  	}
    87  
    88  }
    89  
    90  // This file contains the functionality to locate and record the tasks of
    91  // a configuration. It:
    92  //   - create Task struct for each node that is a task
    93  //   - associate nodes in a configuration with a Task, if applicable.
    94  // The node-to-task map is used to determine task dependencies.
    95  
    96  // getTask finds and marks tasks that are descendents of v.
    97  func (c *Controller) getTask(scope *Task, v cue.Value) *Task {
    98  	// Look up cached node.
    99  	_, w := value.ToInternal(v)
   100  	if t, ok := c.nodes[w]; ok {
   101  		return t
   102  	}
   103  
   104  	if err := w.Err(c.opCtx); err != nil && err.Permanent {
   105  		c.addErr(err.Err, "invalid task")
   106  		return nil
   107  	}
   108  
   109  	// Look up cached task from previous evaluation.
   110  	p := v.Path()
   111  	key := p.String()
   112  
   113  	t := c.keys[key]
   114  
   115  	if t == nil {
   116  		r, err := c.isTask(v)
   117  
   118  		var errs errors.Error
   119  		if err != nil {
   120  			if !c.inRoot(w) {
   121  				// Must be in InferTask mode. In this case we ignore the error.
   122  				r = nil
   123  			} else {
   124  				c.addErr(err, "invalid task")
   125  				errs = errors.Promote(err, "create task")
   126  			}
   127  		}
   128  
   129  		if r != nil {
   130  			index := len(c.tasks)
   131  			t = &Task{
   132  				v:      v,
   133  				c:      c,
   134  				r:      r,
   135  				path:   p,
   136  				labels: w.Path(),
   137  				key:    key,
   138  				index:  index,
   139  				err:    errs,
   140  			}
   141  			c.tasks = append(c.tasks, t)
   142  			c.keys[key] = t
   143  		}
   144  	}
   145  
   146  	// Process nodes of task for this evaluation.
   147  	if t != nil {
   148  		scope = t
   149  		if t.state <= Ready {
   150  			// Don't set the value if the task is currently running as this may
   151  			// result in all kinds of inconsistency issues.
   152  			t.v = v
   153  		}
   154  
   155  		c.tagChildren(w, t)
   156  	}
   157  
   158  	c.nodes[w] = scope
   159  
   160  	return t
   161  }
   162  
   163  func (c *Controller) tagChildren(n *adt.Vertex, t *Task) {
   164  	for _, a := range n.Arcs {
   165  		c.nodes[a] = t
   166  		c.tagChildren(a, t)
   167  	}
   168  }
   169  
   170  // findImpliedTask determines the task of corresponding to node n, if any. If n
   171  // is not already associated with a task, it tries to determine whether n is
   172  // part of a task by checking if any of the parent nodes is a task.
   173  //
   174  // TODO: it is actually more accurate to check for tasks from top down. TODO:
   175  // What should be done if a subtasks is referenced that is embedded in another
   176  // task. Should the surrounding tasks be added as well?
   177  func (c *Controller) findImpliedTask(d dep.Dependency) *Task {
   178  	// Ignore references into packages. Fill will fundamentally not work for
   179  	// packages, and packages cannot point back to the main package as cycles
   180  	// are not allowed.
   181  	if d.Import() != nil {
   182  		return nil
   183  	}
   184  
   185  	n := d.Node
   186  
   187  	// This Finalize should not be necessary, as the input to dep is already
   188  	// finalized. However, cue cmd uses some legacy instance stitching code
   189  	// where some of the backlink Environments are not properly initialized.
   190  	// Finalizing should patch those up at the expense of doing some duplicate
   191  	// work. The plan is to replace `cue cmd` with a much more clean
   192  	// implementation (probably a separate tool called `cuerun`) where this
   193  	// issue is fixed. For now we leave this patch.
   194  	//
   195  	// Note that this issue predates package flow, but that it just surfaced in
   196  	// flow and having a different evaluation order.
   197  	//
   198  	// Note: this call is cheap if n is already Finalized.
   199  	n.Finalize(c.opCtx)
   200  
   201  	for ; n != nil; n = n.Parent {
   202  		if c.cfg.IgnoreConcrete && n.IsConcrete() {
   203  			if k := n.BaseValue.Kind(); k != adt.StructKind && k != adt.ListKind {
   204  				return nil
   205  			}
   206  		}
   207  
   208  		t, ok := c.nodes[n]
   209  		if ok || !c.cfg.InferTasks {
   210  			return t
   211  		}
   212  
   213  		if !d.IsRoot() {
   214  			v := value.Make(c.opCtx, n)
   215  
   216  			if t := c.getTask(nil, v); t != nil {
   217  				return t
   218  			}
   219  		}
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  // markTaskDependencies traces through all conjuncts of a Task and marks
   226  // any dependencies on other tasks.
   227  //
   228  // The dependencies for a node by traversing the nodes of a task and then
   229  // traversing the dependencies of the conjuncts.
   230  //
   231  // This terminates because:
   232  //
   233  //   - traversing all nodes of all tasks is guaranteed finite (CUE does not
   234  //     evaluate to infinite structure).
   235  //
   236  //   - traversing conjuncts of all nodes is finite, as the input syntax is
   237  //     inherently finite.
   238  //
   239  //   - as regular nodes are traversed recursively they are marked with a cycle
   240  //     marker to detect cycles, ensuring a finite traversal as well.
   241  func (c *Controller) markTaskDependencies(t *Task, n *adt.Vertex) {
   242  	cfg := &dep.Config{
   243  		Dynamic: true,
   244  	}
   245  	dep.Visit(cfg, c.opCtx, n, func(d dep.Dependency) error {
   246  		depTask := c.findImpliedTask(d)
   247  		if depTask != nil {
   248  			if depTask != cycleMarker {
   249  				v := value.Make(c.opCtx, d.Node)
   250  				t.addDep(v.Path().String(), depTask)
   251  			}
   252  			return nil
   253  		}
   254  
   255  		// If this points to a non-task node, it may itself point to a task.
   256  		// Handling this allows for dynamic references. For instance, such a
   257  		// value may reference the result value of a task, or even create
   258  		// new tasks based on the result of another task.
   259  		if d.Import() == nil {
   260  			if c.nodes[d.Node] == cycleMarker {
   261  				return nil
   262  			}
   263  			c.nodes[d.Node] = cycleMarker
   264  			d.Recurse()
   265  			c.nodes[d.Node] = nil
   266  		}
   267  		return nil
   268  	})
   269  }
   270  
   271  func (c *Controller) inRoot(n *adt.Vertex) bool {
   272  	path := value.Make(c.opCtx, n).Path().Selectors()
   273  	root := c.cfg.Root.Selectors()
   274  	if len(path) < len(root) {
   275  		return false
   276  	}
   277  	for i, sel := range root {
   278  		if path[i] != sel {
   279  			return false
   280  		}
   281  	}
   282  	return true
   283  }
   284  
   285  var cycleMarker = &Task{}