-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDotnetInteractive.cs
189 lines (160 loc) · 5.95 KB
/
DotnetInteractive.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// The code in this file was copied from
// https://www.nuget.org/packages/Microsoft.dotnet-interactive
//
// Ideally, this should not be necessary, but currently, adding "Microsoft.dotnet-interactive" fails with
// error: NU1202:
// Package Microsoft.dotnet-interactive 1.0.420501 is not compatible with net7.0 (.NETCoreApp,Version=v7.0).
// Package Microsoft.dotnet-interactive 1.0.420501 supports: net7.0 (.NETCoreApp,Version=v7.0) / any
//
// which is either a ridiculously broken error message or a nuget bug.
//
// TODO FIXME
using System.Collections.Concurrent;
using System.Reactive.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.DotNet.Interactive;
using Microsoft.DotNet.Interactive.Events;
#nullable disable
public static class KernelExtensionLoader
{
public static CompositeKernel UseNuGetExtensions(this CompositeKernel kernel)
{
var packagesToCheckForExtensions = new ConcurrentQueue<PackageAdded>();
kernel.AddMiddleware(async (command, context, next) =>
{
await next(command, context);
while (packagesToCheckForExtensions.TryDequeue(out var packageAdded))
{
var packageRootDir = packageAdded.PackageReference.PackageRoot;
var extensionDir =
new DirectoryInfo
(Path.Combine(
packageRootDir,
"interactive-extensions",
"dotnet"));
if (extensionDir.Exists)
{
await LoadExtensionsFromDirectoryAsync(kernel, extensionDir, context);
}
}
});
kernel.RegisterForDisposal(
kernel.KernelEvents
.OfType<PackageAdded>()
.Where(pa => pa?.PackageReference.PackageRoot is not null)
.Distinct(pa => pa.PackageReference.PackageRoot)
.Subscribe(added => packagesToCheckForExtensions.Enqueue(added)));
return kernel;
}
public static async Task LoadExtensionsFromDirectoryAsync(this CompositeKernel kernel, DirectoryInfo extensionDir, KernelInvocationContext context)
{
await new PackageDirectoryExtensionLoader().LoadFromDirectoryAsync(
extensionDir,
kernel,
context);
}
internal static bool CanBeInstantiated(this Type type)
{
return !type.IsAbstract
&& !type.IsGenericTypeDefinition
&& !type.IsInterface;
}
}
internal class PackageDirectoryExtensionLoader
{
private const string ExtensionScriptName = "extension.dib";
private readonly HashSet<AssemblyName> _loadedAssemblies = new();
private readonly object _lock = new();
public async Task LoadFromDirectoryAsync(
DirectoryInfo directory,
Kernel kernel,
KernelInvocationContext context)
{
if (directory is null)
{
throw new ArgumentNullException(nameof(directory));
}
if (kernel is null)
{
throw new ArgumentNullException(nameof(kernel));
}
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (!directory.Exists)
{
throw new ArgumentException($"Directory {directory.FullName} doesn't exist", nameof(directory));
}
await LoadFromDllsInDirectory(
directory,
kernel,
context);
await LoadFromExtensionDibScript(
directory,
kernel,
context);
}
public async Task LoadFromDllsInDirectory(
DirectoryInfo directory,
Kernel kernel,
KernelInvocationContext context)
{
var extensionDlls = directory.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
foreach (var extensionDll in extensionDlls)
{
await LoadFromAssemblyFile(
extensionDll,
kernel,
context);
}
}
private async Task LoadFromAssemblyFile(
FileInfo assemblyFile,
Kernel kernel,
KernelInvocationContext context)
{
bool loadExtensions;
lock (_lock)
{
loadExtensions = _loadedAssemblies.Add(AssemblyName.GetAssemblyName(assemblyFile.FullName));
}
if (loadExtensions)
{
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyFile.FullName);
var extensionTypes = assembly
.ExportedTypes
.Where(t => t.CanBeInstantiated() && typeof(IKernelExtension).IsAssignableFrom(t))
.ToArray();
foreach (var extensionType in extensionTypes)
{
var extension = (IKernelExtension)Activator.CreateInstance(extensionType);
try
{
await extension.OnLoadAsync(kernel);
context.Publish(new KernelExtensionLoaded(extension, context.Command));
}
catch (Exception e)
{
context.Publish(new ErrorProduced(
$"Failed to load kernel extension \"{extensionType.Name}\" from assembly {assembly.Location}",
context.Command));
context.Fail(context.Command, new KernelExtensionLoadException(e));
}
}
}
}
private async Task LoadFromExtensionDibScript(
DirectoryInfo directory,
Kernel kernel,
KernelInvocationContext context)
{
var extensionFile = new FileInfo(Path.Combine(directory.FullName, ExtensionScriptName));
if (extensionFile.Exists)
{
var logMessage = $"Loading extension script from `{extensionFile.FullName}`";
await kernel.LoadAndRunInteractiveDocument(extensionFile);
}
}
}