|
1 | 1 | using Microsoft.Extensions.Logging; |
2 | 2 | using Obsidian.API.Plugins; |
| 3 | +using Obsidian.Entities; |
3 | 4 | using Org.BouncyCastle.Crypto; |
4 | 5 | using System.Collections.Frozen; |
5 | 6 | using System.IO; |
6 | 7 | using System.Reflection; |
| 8 | +using System.Runtime.Loader; |
7 | 9 | using System.Security.Cryptography; |
| 10 | +using System.Text.Json; |
8 | 11 |
|
9 | 12 | namespace Obsidian.Plugins.PluginProviders; |
10 | 13 | public sealed class PackedPluginProvider(PluginManager pluginManager, ILogger logger) |
@@ -52,12 +55,20 @@ public sealed class PackedPluginProvider(PluginManager pluginManager, ILogger lo |
52 | 55 | //Can't load until those plugins are loaded |
53 | 56 | if (partialContainer.Info.Dependencies.Any(x => x.Required && !this.pluginManager.Plugins.Any(d => d.Info.Id == x.Id))) |
54 | 57 | { |
55 | | - var str = partialContainer.Info.Dependencies.Length > 1 ? "has multiple hard dependencies." : |
| 58 | + var str = partialContainer.Info.Dependencies.Length > 1 ? "has multiple hard dependencies." : |
56 | 59 | $"has a hard dependency on {partialContainer.Info.Dependencies.First().Id}."; |
57 | 60 | this.logger.LogWarning("{name} {message}. Will Attempt to load after.", partialContainer.Info.Name, str); |
58 | 61 | return partialContainer; |
59 | 62 | } |
60 | 63 |
|
| 64 | + foreach (var depends in partialContainer.Info.Dependencies) |
| 65 | + { |
| 66 | + var plugin = this.pluginManager.Plugins.FirstOrDefault(x => x.Info.Id == depends.Id); |
| 67 | + |
| 68 | + partialContainer.AddDependency(plugin.LoadContext); |
| 69 | + this.logger.LogInformation("Added {depends} as a dependency for {name}", plugin.Info.Name, partialContainer.Info.Name); |
| 70 | + } |
| 71 | + |
61 | 72 | var mainAssembly = this.InitializePlugin(partialContainer); |
62 | 73 |
|
63 | 74 | return HandlePlugin(partialContainer, mainAssembly); |
@@ -221,29 +232,98 @@ private List<string> ProcessEntries(PluginContainer pluginContainer) |
221 | 232 | var pluginAssembly = pluginContainer.LoadContext.Name; |
222 | 233 |
|
223 | 234 | var libsWithSymbols = new List<string>(); |
224 | | - foreach (var (_, entry) in pluginContainer.FileEntries) |
| 235 | + |
| 236 | + using var dependenciesData = new MemoryStream(pluginContainer.GetFileData($"{pluginAssembly}.deps.json")); |
| 237 | + var dependencies = JsonSerializer.Deserialize<DotNetDeps>(dependenciesData, JsonSerializerOptions.Web); |
| 238 | + var targets = dependencies.Targets[dependencies.RuntimeTarget.Name].Deserialize<Dictionary<string, DotNetTarget>>(JsonSerializerOptions.Web); |
| 239 | + |
| 240 | + foreach (var (key, target) in targets) |
225 | 241 | { |
226 | | - var actualBytes = entry.GetData(); |
| 242 | + var deps = target.Dependencies; |
| 243 | + var runtimes = target.Runtime; |
227 | 244 |
|
228 | | - var name = Path.GetFileNameWithoutExtension(entry.Name); |
229 | | - //Don't load this assembly wait |
230 | | - if (name == pluginAssembly) |
| 245 | + if (runtimes == null)//We don't care if its null |
231 | 246 | continue; |
232 | 247 |
|
233 | | - //TODO LOAD OTHER FILES SOMEWHERE |
234 | | - if (entry.Name.EndsWith(".dll")) |
| 248 | + foreach (var (dll, runtimeElement) in runtimes) |
235 | 249 | { |
236 | | - if (pluginContainer.FileEntries.ContainsKey(entry.Name.Replace(".dll", ".pdb"))) |
| 250 | + var sanitizedDll = dll; |
| 251 | + var split = dll.Split('/'); |
| 252 | + if (split.Length > 1) |
| 253 | + sanitizedDll = split.Last(); |
| 254 | + |
| 255 | + var name = sanitizedDll[..sanitizedDll.IndexOf(".dll")]; |
| 256 | + |
| 257 | + if (name == pluginAssembly || runtimeElement.ToString() == "{}") |
| 258 | + continue; |
| 259 | + |
| 260 | + var runtime = runtimeElement.Deserialize<DependencyRuntime>(JsonSerializerOptions.Web); |
| 261 | + var assemblyName = new AssemblyName |
| 262 | + { |
| 263 | + Name = name, |
| 264 | + Version = new(runtime.AssemblyVersion), |
| 265 | + }; |
| 266 | + |
| 267 | + var depends = pluginContainer.Info.Dependencies.Select(x => x.Id) |
| 268 | + .SelectMany(x => this.pluginManager.Plugins.Where(p => p.Info.Id == x)); |
| 269 | + |
| 270 | + //TODO allow users to define what version of a dependency is allowed/valid e.g version: >1.0.0 or >=1.0.0 or =1.0.0. |
| 271 | + var dependency = depends.FirstOrDefault(x => x.PluginAssembly.GetName().Name == name && x.Info.Version == assemblyName.Version); |
| 272 | + |
| 273 | + //we don't need to load this into the context. |
| 274 | + if (dependency != null) |
| 275 | + continue; |
| 276 | + |
| 277 | + if (pluginContainer.FileEntries.ContainsKey($"${name}.pdb")) |
237 | 278 | { |
238 | 279 | //Library has debug symbols load in last |
239 | | - libsWithSymbols.Add(entry.Name.Replace(".dll", ".pdb")); |
| 280 | + libsWithSymbols.Add(name); |
240 | 281 | continue; |
241 | 282 | } |
242 | 283 |
|
243 | | - pluginContainer.LoadContext.LoadAssembly(actualBytes); |
| 284 | + try |
| 285 | + { |
| 286 | + //Check to see if this assembly already exists in the shared context. |
| 287 | + var sharedAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName); |
| 288 | + if (sharedAssembly != null) |
| 289 | + continue; |
| 290 | + } |
| 291 | + catch { } |
| 292 | + |
| 293 | + var data = pluginContainer.GetFileData(sanitizedDll); |
| 294 | + pluginContainer.LoadContext.LoadAssembly(data); |
244 | 295 | } |
245 | 296 | } |
246 | 297 |
|
247 | 298 | return libsWithSymbols; |
248 | 299 | } |
249 | 300 | } |
| 301 | + |
| 302 | + |
| 303 | +public readonly struct DotNetDeps |
| 304 | +{ |
| 305 | + public required RuntimeTarget RuntimeTarget { get; init; } |
| 306 | + |
| 307 | + public required Dictionary<string, JsonElement> Targets { get; init; } |
| 308 | +} |
| 309 | + |
| 310 | +public readonly struct DotNetTarget |
| 311 | +{ |
| 312 | + public Dictionary<string, string>? Dependencies { get; init; } |
| 313 | + |
| 314 | + public Dictionary<string, JsonElement>? Runtime { get; init; } |
| 315 | +} |
| 316 | + |
| 317 | +public readonly struct DependencyRuntime |
| 318 | +{ |
| 319 | + public required string AssemblyVersion { get; init; } |
| 320 | + |
| 321 | + public required string FileVersion { get; init; } |
| 322 | +} |
| 323 | + |
| 324 | +public readonly struct RuntimeTarget |
| 325 | +{ |
| 326 | + public required string Name { get; init; } |
| 327 | + |
| 328 | + public required string Signature { get; init; } |
| 329 | +} |
0 commit comments