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{}