From 2285c2aba41511b7b00e19f3c91a0bbcd88dd646 Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Mon, 6 May 2024 01:35:33 +0200 Subject: [PATCH] Load nuget packages explicitly --- .../LocalMicrofrontendPackage.cs | 150 +++++++++++++++++- 1 file changed, 144 insertions(+), 6 deletions(-) diff --git a/src/Piral.Blazor.Orchestrator/LocalMicrofrontendPackage.cs b/src/Piral.Blazor.Orchestrator/LocalMicrofrontendPackage.cs index a786660..dcf8186 100644 --- a/src/Piral.Blazor.Orchestrator/LocalMicrofrontendPackage.cs +++ b/src/Piral.Blazor.Orchestrator/LocalMicrofrontendPackage.cs @@ -1,28 +1,88 @@ -using System.Reflection; +using NuGet.Frameworks; +using NuGet.Packaging; +using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; +using System.Text.Json.Serialization; namespace Piral.Blazor.Orchestrator; internal class LocalMicrofrontendPackage(string path, JsonObject? config, IModuleContainerService container, IEvents events, IData data) : MicrofrontendPackage(Path.GetFileNameWithoutExtension(path), "0.0.0", config, container, events, data) { + private const string target = "net8.0"; private readonly string _path = path; private readonly List _contentRoots = []; + private readonly Dictionary _deps = []; + private readonly List _packages = []; + + private Assembly? LoadAssembly(PackageArchiveReader package, string path) + { + using var msStream = GetFile(package, path).Result; + + if (msStream is not null) + { + return Context.LoadFromStream(msStream); + } + + return null; + } protected override Assembly? ResolveAssembly(AssemblyName assemblyName) { - var dllName = assemblyName.Name; - var basePath = Path.GetDirectoryName(_path)!; - return Context.LoadFromAssemblyPath(Path.Combine(basePath, $"{dllName}.dll")); + var dllName = assemblyName.Name!; + var version = assemblyName.Version?.ToString()!; + return ResolveAssembly(dllName, version); + } + + private Assembly? ResolveAssembly(string name, string version) + { + var packageId = $"{name}/{version}"; + + if (_deps.TryGetValue(packageId, out var dep) && dep.Type == "package") + { + var packageName = name.ToLowerInvariant(); + var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var packagePath = Path.Combine(userProfile, ".nuget", "packages", packageName, version, $"{packageName}.{version}.nupkg"); + var stream = File.OpenRead(packagePath); + _packages.Add(new PackageArchiveReader(stream)); + return AddAssemblyToContext(name); + } + else + { + var basePath = Path.GetDirectoryName(_path)!; + return Context.LoadFromAssemblyPath(Path.Combine(basePath, $"{name}.dll")); + } + } + + protected override async Task OnInitializing() + { + await SetContentRoots(); + await SetDependencies(); } - protected override async Task OnInitialized() + private async Task SetContentRoots() { var infos = Path.ChangeExtension(_path, ".staticwebassets.runtime.json"); using var fs = File.OpenRead(infos); var assets = await JsonSerializer.DeserializeAsync(fs); - _contentRoots.AddRange(assets?.ContentRoots ?? Enumerable.Empty()); + + if (assets?.ContentRoots is not null) + { + _contentRoots.AddRange(assets.ContentRoots); + } + } + + private async Task SetDependencies() + { + var infos = Path.ChangeExtension(_path, ".deps.json"); + using var fs = File.OpenRead(infos); + var deps = await JsonSerializer.DeserializeAsync(fs); + + if (deps?.Libraries is not null) + { + _deps.AddRange(deps.Libraries); + } } protected override Assembly? GetAssembly() => Context.LoadFromAssemblyPath(_path); @@ -68,8 +128,86 @@ protected override async Task OnInitialized() protected override string GetCssName() => $"{Name}.styles.css"; + private Assembly? AddAssemblyToContext(string dll) + { + foreach (var package in _packages) + { + var libItems = package.GetLibItems().FirstOrDefault(m => IsCompatible(m.TargetFramework))?.Items; + + if (libItems is not null) + { + foreach (var lib in libItems) + { + if (lib.EndsWith(dll)) + { + return LoadAssembly(package, lib); + } + } + } + } + + return null; + } + + private static bool IsCompatible(NuGetFramework framework) + { + var current = NuGetFramework.Parse(target); + return DefaultCompatibilityProvider.Instance.IsCompatible(current, framework); + } + + private static async Task GetFile(PackageArchiveReader package, string path) + { + try + { + var zip = package.GetEntry(path); + + if (zip is not null) + { + using var zipStream = zip.Open(); + var msStream = new MemoryStream(); + await zipStream.CopyToAsync(msStream); + msStream.Position = 0; + return msStream; + } + } + catch (FileNotFoundException) + { + // This is expected - nothing wrong here + } + catch (InvalidDataException) + { + // This is not expected, but should be handled gracefully + } + + return null; + } + class StaticWebAssets { public List? ContentRoots { get; set; } } + + class DependenciesList + { + [JsonPropertyName("libraries")] + public Dictionary? Libraries { get; set; } + } + + class DependencyDescription + { + [JsonPropertyName("type")] + public string? Type { get; set; } + + [JsonPropertyName("serviceable")] + public bool? IsServiceable { get; set; } + + [JsonPropertyName("sha512")] + public string? SHA512 { get; set; } + + [JsonPropertyName("path")] + public string? Path { get; set; } + + [JsonPropertyName("hashPath")] + public string? HashPath { get; set; } + } }