go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/lucicfg/graph/node.go (about) 1 // Copyright 2018 The LUCI 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 graph 16 17 import ( 18 "fmt" 19 "strings" 20 21 "go.starlark.net/starlark" 22 "go.starlark.net/starlarkstruct" 23 24 "go.chromium.org/luci/starlark/builtins" 25 ) 26 27 // Node is an element of the graph. 28 type Node struct { 29 Key *Key // unique ID of the node 30 Index int // index of the node in the graph 31 Props *starlarkstruct.Struct // struct(...) with frozen properties 32 Trace *builtins.CapturedStacktrace // where the node was defined 33 Idempotent bool // true if it's fine to redeclare this node 34 35 children []*Edge // edges from us (the parent) to the children 36 parents []*Edge // edges from the parents to us (the child) 37 38 childrenList []*Node // memoized result of listChildren() 39 parentsList []*Node // memoised result of listParents() 40 } 41 42 // Edge is a single 'Parent -> Child' edge in the graph. 43 type Edge struct { 44 Parent *Node 45 Child *Node 46 Title string // arbitrary title for error messages 47 Trace *builtins.CapturedStacktrace // where it was defined 48 } 49 50 // declare marks the node as declared. 51 // 52 // Freezes properties as a side effect. 53 func (n *Node) declare(idx int, props *starlarkstruct.Struct, idempotent bool, trace *builtins.CapturedStacktrace) { 54 props.Freeze() 55 n.Index = idx 56 n.Props = props 57 n.Idempotent = idempotent 58 n.Trace = trace 59 } 60 61 // BelongsTo returns true if the node was declared in the given graph. 62 func (n *Node) BelongsTo(g *Graph) bool { 63 return n.Key.set == &g.KeySet 64 } 65 66 // Declared is true if the node was fully defined via AddNode and false if 67 // it was only "predeclared" by being referenced in some edge (via AddEdge). 68 // 69 // In a finalized graph there are no dangling edges: all nodes are guaranteed to 70 // be declared. 71 func (n *Node) Declared() bool { 72 return n.Props != nil 73 } 74 75 // visitDescendants calls cb(n, path) and then recursively visits all 76 // descendants of 'n' in depth first order until a first error. 77 // 78 // If a descendant is reachable through multiple paths, it is visited multiple 79 // times. 80 // 81 // 'path' contains a path being explored now, defined as a list of visited 82 // edges. The array backing this slice is mutated by 'visitDescendants', so if 83 // 'cb' wants to preserve it, it needs to make a copy. 84 // 85 // Assumes the graph has no cycles (as verified by AddEdge). 86 func (n *Node) visitDescendants(path []*Edge, cb func(*Node, []*Edge) error) error { 87 if err := cb(n, path); err != nil { 88 return err 89 } 90 for _, e := range n.children { 91 if err := e.Child.visitDescendants(append(path, e), cb); err != nil { 92 return err 93 } 94 } 95 return nil 96 } 97 98 // listChildren returns a slice with a set of direct children of the node, in 99 // order corresponding edges were declared. 100 // 101 // Must be used only with finalized graphs, since the function caches its result 102 // internally on a first call. Returns a copy of the cached result. 103 func (n *Node) listChildren() []*Node { 104 if n.childrenList == nil { 105 // Note: we want to allocate a new slice even if it is empty, so that 106 // n.childrenList is not nil anymore (to indicate we did the calculation 107 // already). 108 n.childrenList = make([]*Node, 0, len(n.children)) 109 seen := make(map[*Key]struct{}, len(n.children)) 110 for _, edge := range n.children { 111 if _, ok := seen[edge.Child.Key]; !ok { 112 seen[edge.Child.Key] = struct{}{} 113 n.childrenList = append(n.childrenList, edge.Child) 114 } 115 } 116 } 117 return append([]*Node(nil), n.childrenList...) 118 } 119 120 // listParents returns a slice with a set of direct parents of the node, in 121 // order corresponding edges were declared. 122 // 123 // Must be used only with finalized graphs, since the function caches its result 124 // internally on a first call. Returns a copy of the cached result. 125 func (n *Node) listParents() []*Node { 126 if n.parentsList == nil { 127 // Note: we want to allocate a new slice even if it is empty, so that 128 // n.parentsList is not nil anymore (to indicate we did the calculation 129 // already). 130 n.parentsList = make([]*Node, 0, len(n.parents)) 131 seen := make(map[*Key]struct{}, len(n.parents)) 132 for _, edge := range n.parents { 133 if _, ok := seen[edge.Parent.Key]; !ok { 134 seen[edge.Parent.Key] = struct{}{} 135 n.parentsList = append(n.parentsList, edge.Parent) 136 } 137 } 138 } 139 return append([]*Node(nil), n.parentsList...) 140 } 141 142 // String is a part of starlark.Value interface. 143 // 144 // Returns a node title as derived from the kind of last component of its key 145 // and IDs of all key components. It's not 1-to-1 mapping to the full info in 146 // the key, but usually good enough to identify the node in error messages. 147 // 148 // Key components with kinds that start with '_' are skipped. 149 // 150 // If the kind of the first key component starts with '@' and its ID ("<id>") is 151 // not empty, the final string will have a form 'kind("<id>:a/b/c")'. 152 func (n *Node) String() string { 153 ids := make([]string, 0, 5) // overestimate 154 155 // Traverse the (kind, id) list starting from tail. 156 p := n.Key 157 kind := "" 158 for p != nil { 159 if kind = p.Kind(); !strings.HasPrefix(kind, "_") { 160 ids = append(ids, p.ID()) 161 } 162 p = p.Container() 163 } 164 165 // Reverse the result to get head-to-tail order. 166 for l, r := 0, len(ids)-1; l < r; l, r = l+1, r-1 { 167 ids[l], ids[r] = ids[r], ids[l] 168 } 169 170 // Deal with namespaced keys. Their root container kind (last visited) has 171 // form "@...". 172 pfx := "" 173 if strings.HasPrefix(kind, "@") { 174 if ns := ids[0]; ns != "" { 175 pfx = ns + ":" 176 } 177 ids = ids[1:] 178 } 179 180 return fmt.Sprintf("%s(%q)", n.Key.Kind(), pfx+strings.Join(ids, "/")) 181 } 182 183 // Type is a part of starlark.Value interface. 184 func (n *Node) Type() string { return "graph.node" } 185 186 // Freeze is a part of starlark.Value interface. 187 func (n *Node) Freeze() {} 188 189 // Truth is a part of starlark.Value interface. 190 func (n *Node) Truth() starlark.Bool { return starlark.True } 191 192 // Hash is a part of starlark.Value interface. 193 func (n *Node) Hash() (uint32, error) { return n.Key.Hash() } 194 195 // AttrNames is a part of starlark.HasAttrs interface. 196 func (n *Node) AttrNames() []string { 197 return []string{"key", "props", "trace"} 198 } 199 200 // Attr is a part of starlark.HasAttrs interface. 201 func (n *Node) Attr(name string) (starlark.Value, error) { 202 switch name { 203 case "key": 204 return n.Key, nil 205 case "props": 206 return n.Props, nil 207 case "trace": 208 return n.Trace, nil 209 default: 210 return nil, nil 211 } 212 }