Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b1087822c9 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -159,10 +159,6 @@ publish/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
|
162
Torch/Packages/PackageManager.cs
Normal file
162
Torch/Packages/PackageManager.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Torch.API;
|
||||
using Torch.API.Managers;
|
||||
using Torch.API.WebAPI.Plugins;
|
||||
using Torch.Managers;
|
||||
using Torch.Utils;
|
||||
|
||||
namespace Torch.Packages;
|
||||
|
||||
public class PackageManager : Manager, IPackageManager
|
||||
{
|
||||
private readonly PluginManager _pluginManager;
|
||||
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private IPackageResolver? _packageResolver;
|
||||
|
||||
public PackageManager(ITorchBase torchInstance, PluginManager pluginManager) : base(torchInstance)
|
||||
{
|
||||
_pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
private AssemblyLoadContext? _loadContext;
|
||||
|
||||
private ImmutableHashSet<Package> _packages = ImmutableHashSet<Package>.Empty;
|
||||
private ImmutableDictionary<Package,Assembly[]> _packageAssemblies = ImmutableDictionary<Package, Assembly[]>.Empty;
|
||||
private DirectoryInfo? _packagesDirectory;
|
||||
public IReadOnlySet<Package> Packages => _packages;
|
||||
|
||||
public bool TryGetPackageAssemblies(Package package, out Assembly[]? assemblies)
|
||||
{
|
||||
if (!Packages.Contains(package))
|
||||
throw new InvalidOperationException($"Package {package.Name} does not exist");
|
||||
|
||||
return _packageAssemblies.TryGetValue(package, out assemblies);
|
||||
}
|
||||
|
||||
internal async void LoadAsync(SemaphoreSlim semaphore)
|
||||
{
|
||||
_packagesDirectory = Directory.CreateDirectory(Path.Combine(Torch.InstancePath, "Packages"));
|
||||
_packageResolver = new PackageResolver(new[]
|
||||
{ // TODO make this configurable
|
||||
new PackageSource("nuget", "https://api.nuget.org/v3/index.json", new[] { "*" }, PackageSourceType.NuGet)
|
||||
}, _packagesDirectory);
|
||||
|
||||
try
|
||||
{
|
||||
var packages =
|
||||
Torch.Config.Packages.ToImmutableDictionary(b => b[..b.IndexOf(':')], b => b[(b.IndexOf(':') + 1)..]);
|
||||
await ResolvePackagesAsync(packages);
|
||||
|
||||
var resolvedPackages = await _packages.ToAsyncEnumerable()
|
||||
.SelectAwait(async b =>
|
||||
{
|
||||
var reader = await _packageResolver!.GetPackageAsync(b);
|
||||
var items = await reader.GetItemsAsync();
|
||||
|
||||
return (b, items);
|
||||
}).ToDictionaryAsync(b => b.b.Name, b => b,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var dependencies = new Dictionary<PackageDependency, IEnumerable<IPackageItem>>(
|
||||
resolvedPackages.Values.SelectMany(b => b.items.Dependencies)
|
||||
.Where(b => !resolvedPackages.ContainsKey(b.Key.Name))
|
||||
.GroupBy(b => b.Key.Name)
|
||||
.Select(b => b.MaxBy(c => c.Key.Version)));
|
||||
|
||||
_loadContext = new("PackageManager Context");
|
||||
|
||||
var dependencyAssemblies = await dependencies.ToAsyncEnumerable().SelectAwait(async pair =>
|
||||
{
|
||||
var (dep, items) = pair;
|
||||
var assemblies = await LoadAssembliesFromItems(items);
|
||||
|
||||
return (dep, assemblies);
|
||||
}).ToDictionaryAsync(b => b.dep, b => b.assemblies);
|
||||
|
||||
var packageAssemblies = await resolvedPackages.Values.ToAsyncEnumerable().SelectAwait(async b =>
|
||||
{
|
||||
var assemblies = await LoadAssembliesFromItems(b.items.Root);
|
||||
|
||||
return (b.b, assemblies);
|
||||
}).ToDictionaryAsync(b => b.b, b => b.assemblies);
|
||||
|
||||
foreach (var assembly in dependencyAssemblies.Values.Concat(packageAssemblies.Values).SelectMany(b => b))
|
||||
{
|
||||
TorchLauncher.RegisterAssembly(assembly);
|
||||
}
|
||||
|
||||
foreach (var (package, assemblies) in packageAssemblies)
|
||||
{
|
||||
using var hash = MD5.Create();
|
||||
hash.Initialize();
|
||||
var guid = new Guid(hash.ComputeHash(Encoding.UTF8.GetBytes(package.Name)));
|
||||
|
||||
_pluginManager.InstantiatePlugin(new()
|
||||
{
|
||||
Name = package.Name,
|
||||
Version = package.Version.ToString(),
|
||||
Guid = guid
|
||||
}, assemblies);
|
||||
}
|
||||
|
||||
_packageAssemblies = packageAssemblies.ToImmutableDictionary();
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Assembly[]> LoadAssembliesFromItems(IEnumerable<IPackageItem> items)
|
||||
{
|
||||
var dictionary = items.ToImmutableDictionary(b => b.FileName, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var assemblies = await dictionary
|
||||
.Where(b => Path.GetExtension(b.Key)
|
||||
.Equals("dll", StringComparison.OrdinalIgnoreCase))
|
||||
.ToAsyncEnumerable()
|
||||
.SelectAwait(async b =>
|
||||
{
|
||||
dictionary.TryGetValue(
|
||||
Path.ChangeExtension(b.Key, "pdb"), out var pdbItem);
|
||||
return await LoadFromStream(b.Key, b.Value, pdbItem);
|
||||
}).ToArrayAsync();
|
||||
return assemblies;
|
||||
}
|
||||
|
||||
private async ValueTask<Assembly> LoadFromStream(string key, IPackageItem dllItem, IPackageItem? pdbItem)
|
||||
{
|
||||
if (_loadContext is null)
|
||||
throw new InvalidOperationException("Load Context should be initialized before calling the method");
|
||||
|
||||
Log.Trace("Loading {0}", key);
|
||||
await using var dll = await dllItem.OpenFileAsync();
|
||||
if (pdbItem is null)
|
||||
return _loadContext.LoadFromStream(dll);
|
||||
|
||||
await using var pdb = await pdbItem.OpenFileAsync();
|
||||
return _loadContext.LoadFromStream(dll, pdb);
|
||||
}
|
||||
|
||||
private async Task ResolvePackagesAsync(IReadOnlyDictionary<string, string> requestedPackages)
|
||||
{
|
||||
Log.Info("Resolving packages");
|
||||
var packages = await _packageResolver!.ResolvePackagesAsync(requestedPackages);
|
||||
|
||||
_packages = packages.ToImmutableHashSet();
|
||||
Log.Info("Got {0} packages", _packages.Count);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user