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  }