github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/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 "github.com/joomcode/cue/cue" 22 "github.com/joomcode/cue/cue/errors" 23 "github.com/joomcode/cue/internal/core/adt" 24 "github.com/joomcode/cue/internal/core/dep" 25 "github.com/joomcode/cue/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() { 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 c.markTaskDependencies(t, t.vertex()) 51 } 52 53 // Check if there are cycles in the task dependencies. 54 if err := checkCycle(c.tasks); err != nil { 55 c.addErr(err, "cyclic task") 56 } 57 58 if c.errs != nil { 59 c.cancel() 60 } 61 } 62 63 // findRootTasks finds tasks under the root. 64 func (c *Controller) findRootTasks(v cue.Value) { 65 t := c.getTask(nil, v) 66 67 if t != nil { 68 return 69 } 70 71 opts := []cue.Option{} 72 73 if c.cfg.FindHiddenTasks { 74 opts = append(opts, cue.Hidden(true), cue.Definitions(false)) 75 } 76 77 for iter, _ := v.Fields(opts...); iter.Next(); { 78 c.findRootTasks(iter.Value()) 79 } 80 } 81 82 // This file contains the functionality to locate and record the tasks of 83 // a configuration. It: 84 // - create Task struct for each node that is a task 85 // - associate nodes in a configuration with a Task, if applicable. 86 // The node-to-task map is used to determine task dependencies. 87 88 // getTask finds and marks tasks that are descendents of v. 89 func (c *Controller) getTask(scope *Task, v cue.Value) *Task { 90 // Look up cached node. 91 _, w := value.ToInternal(v) 92 if t, ok := c.nodes[w]; ok { 93 return t 94 } 95 96 // Look up cached task from previous evaluation. 97 p := v.Path() 98 key := p.String() 99 100 t := c.keys[key] 101 102 if t == nil { 103 r, err := c.isTask(v) 104 105 var errs errors.Error 106 if err != nil { 107 if !c.inRoot(w) { 108 // Must be in InferTask mode. In this case we ignore the error. 109 r = nil 110 } else { 111 c.addErr(err, "invalid task") 112 errs = errors.Promote(err, "create task") 113 } 114 } 115 116 if r != nil { 117 index := len(c.tasks) 118 t = &Task{ 119 v: v, 120 c: c, 121 r: r, 122 path: p, 123 labels: w.Path(), 124 key: key, 125 index: index, 126 err: errs, 127 } 128 c.tasks = append(c.tasks, t) 129 c.keys[key] = t 130 } 131 } 132 133 // Process nodes of task for this evaluation. 134 if t != nil { 135 scope = t 136 if t.state <= Ready { 137 // Don't set the value if the task is currently running as this may 138 // result in all kinds of inconsistency issues. 139 t.v = v 140 } 141 142 c.tagChildren(w, t) 143 } 144 145 c.nodes[w] = scope 146 147 return t 148 } 149 150 func (c *Controller) tagChildren(n *adt.Vertex, t *Task) { 151 for _, a := range n.Arcs { 152 c.nodes[a] = t 153 c.tagChildren(a, t) 154 } 155 } 156 157 // findImpliedTask determines the task of corresponding to node n, if any. If n 158 // is not already associated with a task, it tries to determine whether n is 159 // part of a task by checking if any of the parent nodes is a task. 160 // 161 // TODO: it is actually more accurate to check for tasks from top down. TODO: 162 // What should be done if a subtasks is referenced that is embedded in another 163 // task. Should the surrounding tasks be added as well? 164 func (c *Controller) findImpliedTask(d dep.Dependency) *Task { 165 // Ignore references into packages. Fill will fundamentally not work for 166 // packages, and packages cannot point back to the main package as cycles 167 // are not allowed. 168 if d.Import() != nil { 169 return nil 170 } 171 172 n := d.Node 173 174 // This Finalize should not be necessary, as the input to dep is already 175 // finalized. However, cue cmd uses some legacy instance stitching code 176 // where some of the backlink Environments are not properly initialized. 177 // Finalizing should patch those up at the expense of doing some duplicate 178 // work. The plan is to replace `cue cmd` with a much more clean 179 // implementation (probably a separate tool called `cuerun`) where this 180 // issue is fixed. For now we leave this patch. 181 // 182 // Note that this issue predates package flow, but that it just surfaced in 183 // flow and having a different evaluation order. 184 // 185 // Note: this call is cheap if n is already Finalized. 186 n.Finalize(c.opCtx) 187 188 for ; n != nil; n = n.Parent { 189 if c.cfg.IgnoreConcrete && n.IsConcrete() { 190 if k := n.BaseValue.Kind(); k != adt.StructKind && k != adt.ListKind { 191 return nil 192 } 193 } 194 195 t, ok := c.nodes[n] 196 if ok || !c.cfg.InferTasks { 197 return t 198 } 199 200 if !d.IsRoot() { 201 v := value.Make(c.opCtx, n) 202 203 if t := c.getTask(nil, v); t != nil { 204 return t 205 } 206 } 207 } 208 209 return nil 210 } 211 212 // markTaskDependencies traces through all conjuncts of a Task and marks 213 // any dependencies on other tasks. 214 // 215 // The dependencies for a node by traversing the nodes of a task and then 216 // traversing the dependencies of the conjuncts. 217 // 218 // This terminates because: 219 // 220 // - traversing all nodes of all tasks is guaranteed finite (CUE does not 221 // evaluate to infinite structure). 222 // 223 // - traversing conjuncts of all nodes is finite, as the input syntax is 224 // inherently finite. 225 // 226 // - as regular nodes are traversed recursively they are marked with a cycle 227 // marker to detect cycles, ensuring a finite traversal as well. 228 // 229 func (c *Controller) markTaskDependencies(t *Task, n *adt.Vertex) { 230 dep.VisitFields(c.opCtx, n, func(d dep.Dependency) error { 231 depTask := c.findImpliedTask(d) 232 if depTask != nil { 233 if depTask != cycleMarker { 234 v := value.Make(c.opCtx, d.Node) 235 t.addDep(v.Path().String(), depTask) 236 } 237 return nil 238 } 239 240 // If this points to a non-task node, it may itself point to a task. 241 // Handling this allows for dynamic references. For instance, such a 242 // value may reference the result value of a task, or even create 243 // new tasks based on the result of another task. 244 if d.Import() == nil { 245 c.nodes[d.Node] = cycleMarker 246 c.markTaskDependencies(t, d.Node) 247 c.nodes[d.Node] = nil 248 } 249 return nil 250 }) 251 } 252 253 func (c *Controller) inRoot(n *adt.Vertex) bool { 254 path := value.Make(c.opCtx, n).Path().Selectors() 255 root := c.cfg.Root.Selectors() 256 if len(path) < len(root) { 257 return false 258 } 259 for i, sel := range root { 260 if path[i] != sel { 261 return false 262 } 263 } 264 return true 265 } 266 267 var cycleMarker = &Task{}