Skip to content

Commit f551153

Browse files
authored
Implement ScriptRunner for ICorDebug debugging infrastructure (#79)
1 parent 2aeeae5 commit f551153

File tree

11 files changed

+752
-5
lines changed

11 files changed

+752
-5
lines changed

.github/agents/basicAgent.agent.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ description: Used for general purpose NodeDev development
1515
3) Disabling, removing, skipping, deleting, bypassing or converting to warnings ANY tests IS NOT ALLOWED and is not considered the right way of fixing a problematic test. The test must be functional and actually testing what it is intended to test.
1616
4) Document newly added content or concepts in this `.github/agents/basicAgent.agent.md` file or any related documentation file.
1717
5) When the user corrects major mistakes done during your development, document them in this file to ensure it is never done again.
18+
6) You must always install playwright BEFORE trying to run the tests. build the projects and install playwright. If you struggle (take multiple iterations to do it), document the steps you took in this file to make it easier next time.
1819

1920
## Programming style
2021

@@ -34,6 +35,7 @@ NodeDev is a visual programming environment built with Blazor and Blazor.Diagram
3435
- **NodeDev.Blazor.MAUI**: MAUI-based desktop application wrapper
3536
- **NodeDev.Tests**: Unit tests for core functionality
3637
- **NodeDev.EndToEndTests**: Playwright-based E2E tests with Reqnroll (SpecFlow successor)
38+
- **NodeDev.ScriptRunner**: Console application that executes compiled user code as a separate process, serving as the target for the ICorDebug debugging infrastructure
3739

3840
### UI Structure
3941
The main UI consists of:
@@ -88,3 +90,35 @@ Detailed topic-specific documentation is maintained in the `docs/` folder:
8890

8991
- `docs/e2e-testing.md` - End-to-end testing patterns, node interaction, connection testing, and screenshot validation
9092
- `docs/node-types-and-connections.md` - Comprehensive guide to node types, connection system, port identification, and testing strategies
93+
- `docs/script-runner.md` - ScriptRunner architecture, usage, and ICorDebug debugging infrastructure
94+
95+
## Debugging Infrastructure
96+
97+
### ScriptRunner
98+
NodeDev includes a separate console application called **ScriptRunner** that serves as the target process for debugging. This architecture is being developed to support "Hard Debugging" via the ICorDebug API (.NET's unmanaged debugging interface).
99+
100+
**Architecture:**
101+
- **Host Process**: The Visual IDE (NodeDev.Blazor.Server or NodeDev.Blazor.MAUI)
102+
- **Target Process**: ScriptRunner - a separate console application that executes the user's compiled code
103+
104+
**ScriptRunner Features:**
105+
- Accepts a DLL path as command-line argument
106+
- Loads assemblies using `Assembly.LoadFrom()`
107+
- Finds and invokes entry points:
108+
- Static `Program.Main` method (in any namespace)
109+
- Types implementing `IRunnable` interface (future extensibility)
110+
- Wraps execution in try/catch blocks to print exceptions to console
111+
- Proper exit code handling for CI/CD integration
112+
113+
**Build System:**
114+
- ScriptRunner is automatically built with NodeDev.Core
115+
- MSBuild targets copy ScriptRunner to the output directory of dependent projects
116+
- The `Project.Run()` method automatically locates and launches ScriptRunner
117+
118+
**Future: ICorDebug Integration**
119+
This infrastructure prepares NodeDev for implementing advanced debugging features:
120+
- Breakpoints in visual graphs
121+
- Step-through execution
122+
- Variable inspection at runtime
123+
- Exception handling and catching
124+
- Live debugging across process boundaries

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,7 @@
3232
/src/NodeDev.Blazor.MAUI/obj
3333
/src/NodeDev.Blazor.MAUI/bin/Release/net9.0-windows10.0.19041.0/win-x64
3434
/src/NodeDev.Blazor.Server/AppOptions.json
35+
/src/NodeDev.ScriptRunner/bin
36+
/src/NodeDev.ScriptRunner/obj
3537

3638
/src/NodeDev.EndToEndTests/Features/*.feature.cs

docs/script-runner.md

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
# ScriptRunner - Debugging Infrastructure
2+
3+
## Overview
4+
5+
ScriptRunner is a console application that serves as the target process for debugging in NodeDev. It executes user-compiled DLLs in a separate process, providing the foundation for advanced debugging features via the ICorDebug API.
6+
7+
## Architecture
8+
9+
### Process Separation
10+
11+
NodeDev uses a two-process architecture for code execution:
12+
13+
```
14+
┌─────────────────────────────┐
15+
│ Host Process (IDE) │
16+
│ - NodeDev.Blazor.Server │
17+
│ - NodeDev.Blazor.MAUI │
18+
│ - Compiles user code │
19+
│ - Manages UI │
20+
└──────────┬──────────────────┘
21+
│ Launches
22+
23+
┌─────────────────────────────┐
24+
│ Target Process │
25+
│ - NodeDev.ScriptRunner │
26+
│ - Loads compiled DLL │
27+
│ - Executes user code │
28+
│ - Reports output/errors │
29+
└─────────────────────────────┘
30+
```
31+
32+
### Benefits
33+
34+
1. **Process Isolation**: User code runs in a separate process, protecting the IDE from crashes
35+
2. **Debugging Support**: Enables ICorDebug attachment for advanced debugging features
36+
3. **Resource Management**: Easier to manage memory and resources of user code
37+
4. **Security**: Sandboxing opportunities for untrusted code execution
38+
39+
## How It Works
40+
41+
### 1. Project Compilation
42+
43+
When `Project.Run()` is called:
44+
45+
```csharp
46+
var assemblyPath = Build(options); // Compiles to bin/Debug/project.exe
47+
```
48+
49+
The build process creates:
50+
- `project.exe` (or `project.dll`) - The compiled user code
51+
- `project.pdb` - Debug symbols
52+
- `project.runtimeconfig.json` - Runtime configuration
53+
54+
### 2. ScriptRunner Launch
55+
56+
The IDE launches ScriptRunner with the compiled DLL:
57+
58+
```bash
59+
dotnet NodeDev.ScriptRunner.dll "/absolute/path/to/project.exe"
60+
```
61+
62+
### 3. Assembly Loading
63+
64+
ScriptRunner loads the assembly:
65+
66+
```csharp
67+
Assembly assembly = Assembly.LoadFrom(dllPath);
68+
```
69+
70+
### 4. Entry Point Discovery
71+
72+
ScriptRunner searches for entry points in this order:
73+
74+
1. **Static Program.Main method** (in any namespace)
75+
```csharp
76+
public static class Program
77+
{
78+
public static int Main() { ... }
79+
}
80+
```
81+
82+
2. **IRunnable implementation** (future extensibility)
83+
```csharp
84+
public class MyClass : IRunnable
85+
{
86+
public void Run() { ... }
87+
}
88+
```
89+
90+
### 5. Execution
91+
92+
ScriptRunner invokes the entry point with proper exception handling:
93+
94+
```csharp
95+
try
96+
{
97+
int exitCode = InvokeEntryPoint(assembly, userArgs);
98+
return exitCode;
99+
}
100+
catch (Exception ex)
101+
{
102+
Console.Error.WriteLine($"Fatal error: {ex.GetType().Name}: {ex.Message}");
103+
Console.Error.WriteLine($"Stack trace:\n{ex.StackTrace}");
104+
return 3;
105+
}
106+
```
107+
108+
## Command-Line Interface
109+
110+
### Usage
111+
112+
```bash
113+
NodeDev.ScriptRunner <path-to-dll> [args...]
114+
```
115+
116+
### Arguments
117+
118+
- `<path-to-dll>`: **Required**. Absolute or relative path to the compiled DLL to execute
119+
- `[args...]`: **Optional**. Arguments to pass to the entry point (if it accepts `string[] args`)
120+
121+
### Exit Codes
122+
123+
| Code | Meaning |
124+
|------|---------|
125+
| 0 | Success - Program executed successfully |
126+
| 1 | Invalid usage - Missing DLL path argument |
127+
| 2 | File not found - DLL doesn't exist at specified path |
128+
| 3 | Fatal error - Unhandled exception during execution |
129+
| 4 | No entry point - No valid entry point found in assembly |
130+
| N | User-defined - Return value from `Program.Main()` |
131+
132+
### Examples
133+
134+
```bash
135+
# Execute a simple program
136+
dotnet NodeDev.ScriptRunner.dll /path/to/myprogram.dll
137+
138+
# Execute with arguments
139+
dotnet NodeDev.ScriptRunner.dll /path/to/myprogram.dll arg1 arg2 "arg with spaces"
140+
```
141+
142+
## Build Integration
143+
144+
### MSBuild Targets
145+
146+
ScriptRunner is automatically copied to dependent projects using MSBuild targets:
147+
148+
**NodeDev.Core/NodeDev.Core.csproj:**
149+
```xml
150+
<ItemGroup>
151+
<ProjectReference Include="..\NodeDev.ScriptRunner\NodeDev.ScriptRunner.csproj">
152+
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
153+
</ProjectReference>
154+
</ItemGroup>
155+
156+
<Target Name="CopyScriptRunner" AfterTargets="Build">
157+
<ItemGroup>
158+
<ScriptRunnerFiles Include="..\NodeDev.ScriptRunner\bin\$(Configuration)\$(TargetFramework)\**\*.*" />
159+
</ItemGroup>
160+
<Copy SourceFiles="@(ScriptRunnerFiles)" DestinationFolder="$(OutputPath)%(RecursiveDir)" />
161+
</Target>
162+
```
163+
164+
This ensures ScriptRunner is available wherever NodeDev.Core is built.
165+
166+
### Location at Runtime
167+
168+
The `FindScriptRunnerExecutable()` method locates ScriptRunner:
169+
170+
1. Same directory as NodeDev.Core assembly (production)
171+
2. Sibling directory in build output (development)
172+
173+
```csharp
174+
private static string FindScriptRunnerExecutable()
175+
{
176+
string coreDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
177+
string scriptRunnerDll = Path.Combine(coreDirectory, "NodeDev.ScriptRunner.dll");
178+
179+
if (File.Exists(scriptRunnerDll))
180+
return scriptRunnerDll;
181+
182+
// Try sibling directories for development scenarios
183+
// ...
184+
}
185+
```
186+
187+
## Testing
188+
189+
### Unit Tests
190+
191+
ScriptRunner functionality is tested in `NodeDev.Tests/ScriptRunnerTests.cs`:
192+
193+
1. **ScriptRunner_ShouldExecuteSimpleProgram** - Verifies basic execution and console output
194+
2. **ScriptRunner_ShouldHandleExceptions** - Tests graceful error handling
195+
3. **ScriptRunner_ShouldReturnExitCode** - Validates exit code propagation
196+
197+
### Test Pattern
198+
199+
```csharp
200+
[Fact]
201+
public void ScriptRunner_ShouldExecuteSimpleProgram()
202+
{
203+
// Arrange - Create a project with a WriteLine node
204+
var project = Project.CreateNewDefaultProject(out var mainMethod);
205+
// ... add nodes, connect them ...
206+
207+
// Act - Run via ScriptRunner
208+
var result = project.Run(BuildOptions.Debug);
209+
210+
// Assert - Verify output and behavior
211+
Assert.NotEmpty(consoleOutput);
212+
Assert.Contains(consoleOutput, line => line.Contains("ScriptRunner Test Output"));
213+
}
214+
```
215+
216+
## Future: ICorDebug Integration
217+
218+
ScriptRunner lays the groundwork for advanced debugging features:
219+
220+
### Planned Features
221+
222+
1. **Breakpoints**: Set breakpoints on visual nodes
223+
2. **Step Execution**: Step through node execution one at a time
224+
3. **Variable Inspection**: Inspect connection values at runtime
225+
4. **Call Stack**: View the execution path through the graph
226+
5. **Exception Catching**: Catch and inspect exceptions in the debugger
227+
228+
### ICorDebug API
229+
230+
The ICorDebug API provides:
231+
- Process creation and attachment
232+
- Thread control (suspend, resume)
233+
- Breakpoint management
234+
- Stack walking
235+
- Variable inspection
236+
- Exception handling
237+
238+
### Implementation Approach
239+
240+
```csharp
241+
// Future debugging implementation (pseudocode)
242+
var debugger = new ICorDebug();
243+
var process = debugger.CreateProcess("dotnet", "NodeDev.ScriptRunner.dll project.exe");
244+
process.OnBreakpoint += (sender, e) => {
245+
// Highlight the corresponding node in the UI
246+
// Show connection values
247+
// Enable step controls
248+
};
249+
```
250+
251+
## Troubleshooting
252+
253+
### ScriptRunner Not Found
254+
255+
**Error:** `FileNotFoundException: ScriptRunner executable not found`
256+
257+
**Solution:** Rebuild the solution to trigger the MSBuild copy target:
258+
```bash
259+
dotnet build
260+
```
261+
262+
### Assembly Load Errors
263+
264+
**Error:** `Could not load file or assembly`
265+
266+
**Cause:** The compiled DLL might have missing dependencies
267+
268+
**Solution:** Ensure all dependencies are in the output directory with the DLL
269+
270+
### No Entry Point Found
271+
272+
**Error:** `No entry point found. Expected: - Static method Program.Main()`
273+
274+
**Cause:** The compiled assembly doesn't have a valid entry point
275+
276+
**Solution:** Verify the project has a class named "Program" with a static "Main" method
277+
278+
## Development Guidelines
279+
280+
### Adding New Entry Point Types
281+
282+
To add support for new entry point patterns:
283+
284+
1. Add detection logic to `InvokeEntryPoint()`:
285+
```csharp
286+
// Strategy 3: Look for custom entry point
287+
Type? customType = assembly.GetTypes()
288+
.FirstOrDefault(t => t.GetCustomAttribute<EntryPointAttribute>() != null);
289+
```
290+
291+
2. Add invocation logic
292+
3. Update documentation and tests
293+
294+
### Modifying Execution Behavior
295+
296+
When modifying ScriptRunner:
297+
- Keep it lightweight and fast
298+
- Preserve exit code semantics
299+
- Maintain backward compatibility
300+
- Add corresponding tests
301+
- Update this documentation
302+
303+
## See Also
304+
305+
- [Project.cs](../src/NodeDev.Core/Project.cs) - `Run()` and `FindScriptRunnerExecutable()` methods
306+
- [Program.cs](../src/NodeDev.ScriptRunner/Program.cs) - ScriptRunner implementation
307+
- [ScriptRunnerTests.cs](../src/NodeDev.Tests/ScriptRunnerTests.cs) - Test suite

src/NodeDev.Blazor.Server/NodeDev.Blazor.Server.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@
88

99
<ItemGroup>
1010
<ProjectReference Include="..\NodeDev.Blazor\NodeDev.Blazor.csproj" />
11+
<ProjectReference Include="..\NodeDev.ScriptRunner\NodeDev.ScriptRunner.csproj" />
1112
</ItemGroup>
1213

14+
<!-- Copy ScriptRunner to output directory after build -->
15+
<Target Name="CopyScriptRunner" AfterTargets="Build">
16+
<ItemGroup>
17+
<ScriptRunnerFiles Include="..\NodeDev.ScriptRunner\bin\$(Configuration)\$(TargetFramework)\**\*.*" />
18+
</ItemGroup>
19+
<Copy SourceFiles="@(ScriptRunnerFiles)" DestinationFolder="$(OutputPath)%(RecursiveDir)" />
20+
</Target>
21+
1322
</Project>

src/NodeDev.Core/NodeDev.Core.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
<PackageReference Include="snakex64.Dis2Msil" Version="1.0.0" />
1616
</ItemGroup>
1717

18+
<ItemGroup>
19+
<!-- Reference to ScriptRunner to ensure it's built and available -->
20+
<ProjectReference Include="..\NodeDev.ScriptRunner\NodeDev.ScriptRunner.csproj">
21+
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
22+
</ProjectReference>
23+
</ItemGroup>
24+
1825
<ItemGroup>
1926
<EmbeddedResource Include="Dependencies\System.Reflection.Emit.dll">
2027
</EmbeddedResource>

0 commit comments

Comments
 (0)