microsoft/openvmm

Public

mirrored fromhttps://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
d72b222d661d464d3fc1707cfd7e7d0cfcdaef40

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

Guide/src/dev_guide/dev_tools/flowey/nodes.md

182lines · modecode

1# Nodes
2
3At a conceptual level, a Flowey node is analogous to a strongly typed function: you "invoke" it by submitting one or more Request values (its parameters), and it responds by emitting steps that perform work and produce outputs (values written to `WriteVar`s, published artifacts, or side-effect dependencies).
4
5## The Node/Request Pattern
6
7Every node has an associated **Request** type that defines what operations the node can perform. Requests are defined using the [`flowey_request!`](https://openvmm.dev/rustdoc/linux/flowey_core/macro.flowey_request.html) macro and registered with [`new_flow_node!`](https://openvmm.dev/rustdoc/linux/flowey_core/macro.new_flow_node.html) or [`new_simple_flow_node!`](https://openvmm.dev/rustdoc/linux/flowey_core/macro.new_simple_flow_node.html) macros.
8
9For complete examples, see the [`FlowNode` trait documentation](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.FlowNode.html).
10
11## FlowNode vs SimpleFlowNode
12
13Flowey provides two node implementation patterns with a fundamental difference in their Request structure and complexity:
14
15[**`SimpleFlowNode`**](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.SimpleFlowNode.html) - for straightforward, function-like operations:
16
17- Uses a **single struct Request** type
18- Processes one request at a time independently
19- Behaves like a "plain old function" that resolves its single request type
20- Each invocation is isolated - no shared state or coordination between requests
21- Simpler implementation with less boilerplate
22- Ideal for straightforward operations like running a command or transforming data
23
24**Example use case**: A node that runs `cargo build` - each request is independent and just needs to know what to build.
25
26[**`FlowNode`**](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.FlowNode.html) - for complex nodes requiring coordination and non-local configuration:
27
28- Often uses an **enum Request** with multiple variants
29- Receives all requests as a `Vec<Request>` and processes them together
30- Can aggregate, optimize, and consolidate multiple requests into fewer steps
31- Enables **non-local configuration** - critical for simplifying complex pipelines
32
33### The Non-Local Configuration Pattern
34
35The key advantage of FlowNode is its ability to accept configuration from different parts of the node graph without forcing intermediate nodes to be aware of that configuration. This is the "non-local" aspect:
36
37Consider an "install Rust toolchain" node with an enum Request:
38
39```rust,ignore
40enum Request {
41 SetVersion { version: String },
42 GetToolchain { toolchain_path: WriteVar<PathBuf> },
43}
44```
45
46**Without this pattern** (struct-only requests), you'd need to thread the Rust version through every intermediate node in the call graph:
47
48```txt
49Root Node (knows version: "1.75")
50 → Node A (must pass through version)
51 → Node B (must pass through version)
52 → Node C (must pass through version)
53 → Install Rust Node (finally uses version)
54```
55
56**With FlowNode's enum Request**, the root node can send `Request::SetVersion` once, while intermediate nodes that don't care about the version can simply send `Request::GetToolchain`:
57
58```txt
59Root Node → InstallRust::SetVersion("1.75")
60 → Node A
61 → Node B
62 → Node C → InstallRust::GetToolchain()
63```
64
65The Install Rust FlowNode receives both requests together, validates that exactly one `SetVersion` was provided, and fulfills all the `GetToolchain` requests with that configured version. The intermediate nodes (A, B, C) never needed to know about or pass through version information.
66
67This pattern:
68
69- **Eliminates plumbing complexity** in large pipelines
70- **Allows global configuration** to be set once at the top level
71- **Keeps unrelated nodes decoupled** from configuration they don't need
72- **Enables validation** that required configuration was provided (exactly one `SetVersion`)
73
74**Additional Benefits of FlowNode:**
75
76- Optimize and consolidate multiple similar requests into fewer steps (e.g., installing a tool once for many consumers)
77- Resolve conflicts or enforce consistency across requests
78
79For detailed comparisons and examples, see the [`FlowNode`](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.FlowNode.html) and [`SimpleFlowNode`](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.SimpleFlowNode.html) documentation.
80
81## Node Registration
82
83Nodes are automatically registered using macros that handle most of the boilerplate:
84
85- [`new_flow_node!(struct Node)`](https://openvmm.dev/rustdoc/linux/flowey_core/macro.new_flow_node.html) - registers a FlowNode
86- [`new_simple_flow_node!(struct Node)`](https://openvmm.dev/rustdoc/linux/flowey_core/macro.new_simple_flow_node.html) - registers a SimpleFlowNode
87- [`flowey_request!`](https://openvmm.dev/rustdoc/linux/flowey_core/macro.flowey_request.html) - defines the Request type and implements [`IntoRequest`](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.IntoRequest.html)
88
89## The imports() Method
90
91The `imports()` method declares which other nodes this node might depend on. This enables flowey to:
92
93- Validate that all dependencies are available
94- Build the complete dependency graph
95- Catch missing dependencies at build-time
96
97```admonish warning
98Flowey does not catch unused imports today as part of its build-time validation step.
99```
100
101**Why declare imports?** Flowey needs to know the full set of potentially-used nodes at compilation time to properly resolve the dependency graph.
102
103For more on node imports, see the [`FlowNode::imports` documentation](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.FlowNode.html#tymethod.imports).
104
105## The emit() Method
106
107The [`emit()`](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.FlowNode.html#tymethod.emit) method is where a node's actual logic lives. For [`FlowNode`](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.FlowNode.html), it receives all requests together and must:
108
1091. Aggregate and validate requests (ensuring consistency where needed)
1102. Emit steps to perform the work
1113. Wire up dependencies between steps via variables
112
113For [`SimpleFlowNode`](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.SimpleFlowNode.html), the equivalent [`process_request()`](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.SimpleFlowNode.html#tymethod.process_request) method processes one request at a time.
114
115For complete implementation examples, see the [`FlowNode::emit` documentation](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.FlowNode.html#tymethod.emit).
116
117## Node Design Philosophy
118
119Flowey nodes are designed around several key principles:
120
121### 1. Composability
122
123Nodes should be reusable building blocks that can be combined to build complex
124workflows. Each node should have a single, well-defined responsibility.
125
126❌ **Bad**: A node that "builds and tests the project"
127✅ **Good**: Separate nodes for "build project" and "run tests"
128
129### 2. Explicit Dependencies
130
131Dependencies between steps should be explicit through variables, not implicit
132through side effects.
133
134❌ **Bad**: Assuming a tool is already installed
135✅ **Good**: Taking a `ReadVar<SideEffect>` that proves installation happened
136
137### 3. Backend Abstraction
138
139Nodes should work across all backends when possible. Backend-specific behavior
140should be isolated and documented.
141
142### 4. Separation of Concerns
143
144Keep node definition (request types, dependencies) separate from step
145implementation (runtime logic):
146
147- **Node definition**: What the node does, what it depends on
148- **Step implementation**: How it does it
149
150## Common Patterns
151
152### Request Aggregation and Validation
153
154When a FlowNode receives multiple requests, it often needs to ensure certain values are consistent across all requests while collecting others. The `same_across_all_reqs` helper function simplifies this pattern by validating that a value is identical across all requests.
155
156**Key concepts:**
157
158- Iterate through all requests and separate them by type
159- Use `same_across_all_reqs` to validate values that must be consistent
160- Collect values that can have multiple instances (like output variables)
161- Validate that required values were provided
162
163For a complete example, see the [`same_across_all_reqs` documentation](https://openvmm.dev/rustdoc/linux/flowey_core/node/user_facing/fn.same_across_all_reqs.html).
164
165### Conditional Execution Based on Backend/Platform
166
167Nodes can query the current backend and platform to emit platform-specific or backend-specific steps. This allows nodes to adapt their behavior based on the execution environment.
168
169**Key concepts:**
170
171- Use `ctx.backend()` to check if running locally, on ADO, or on GitHub Actions
172- Use `ctx.platform()` to check the operating system (Windows, Linux, macOS)
173- Use `ctx.arch()` to check the architecture (x86_64, Aarch64)
174- Emit different steps or use different tool configurations based on these values
175
176**When to use:**
177
178- Installing platform-specific tools or dependencies
179- Using different commands on Windows vs Unix systems
180- Optimizing for local development vs CI environments
181
182For more on backend and platform APIs, see the [`NodeCtx` documentation](https://openvmm.dev/rustdoc/linux/flowey_core/node/struct.NodeCtx.html).
183