Add better support for zip extensions (#607)
- AssemblyLoadContext requires assemblies to be seekable, so we now will copy assemblies to byte arrays if they are not seekable - We check for pattern that occurs when a zip file is created from a folder where the top level directory is a simple folder This change also consolidates loading logic all into the assembly load context where it will attempt to load the assemblies from the manifest.
This commit is contained in:
Родитель
060d1d22f1
Коммит
29580acdc5
|
@ -2,9 +2,11 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
||||
{
|
||||
|
@ -14,10 +16,38 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
|
||||
private readonly ExtensionInstance _extension;
|
||||
|
||||
public ExtensionAssemblyLoadContext(ExtensionInstance extension)
|
||||
public ExtensionAssemblyLoadContext(ExtensionInstance extension, string[] assemblies)
|
||||
: base(ALC_Prefix + extension.Name)
|
||||
{
|
||||
_extension = extension;
|
||||
Load(extension, assemblies);
|
||||
}
|
||||
|
||||
private void Load(ExtensionInstance extension, string[] assemblies)
|
||||
{
|
||||
foreach (var path in assemblies)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileInfo = extension.FileProvider.GetFileInfo(path);
|
||||
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
Console.WriteLine($"ERROR: Could not find extension service provider assembly {path} in extension {extension.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
using var assemblyStream = GetSeekableStream(fileInfo);
|
||||
|
||||
LoadFromStream(assemblyStream);
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override Assembly? Load(AssemblyName assemblyName)
|
||||
|
@ -35,14 +65,14 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
|
||||
if (dllFile.Exists)
|
||||
{
|
||||
using var dllStream = dllFile.CreateReadStream();
|
||||
using var dllStream = GetSeekableStream(dllFile);
|
||||
|
||||
var pdb = $"{assemblyName.Name}.pdb";
|
||||
var pdbFile = _extension.FileProvider.GetFileInfo(pdb);
|
||||
|
||||
if (pdbFile.Exists)
|
||||
{
|
||||
using var pdbStream = pdbFile.CreateReadStream();
|
||||
using var pdbStream = GetSeekableStream(pdbFile);
|
||||
return LoadFromStream(dllStream, pdbStream);
|
||||
}
|
||||
else
|
||||
|
@ -53,5 +83,21 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Stream GetSeekableStream(IFileInfo file)
|
||||
{
|
||||
var assemblyStream = file.CreateReadStream();
|
||||
|
||||
if (assemblyStream.CanSeek)
|
||||
{
|
||||
return assemblyStream;
|
||||
}
|
||||
|
||||
var ms = new MemoryStream();
|
||||
assemblyStream.CopyTo(ms);
|
||||
ms.Position = 0;
|
||||
assemblyStream.Dispose();
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,21 +2,26 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Loader;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
||||
{
|
||||
[DebuggerDisplay("{Name}, {Location}")]
|
||||
public sealed class ExtensionInstance : IDisposable
|
||||
{
|
||||
private const string ExtensionServiceProvidersSectionName = "ExtensionServiceProviders";
|
||||
public const string ManifestFileName = "ExtensionManifest.json";
|
||||
|
||||
private const string ExtensionNamePropertyName = "ExtensionName";
|
||||
private const string DefaultExtensionName = "Unknown";
|
||||
|
||||
private readonly Lazy<AssemblyLoadContext> _alc;
|
||||
private readonly Lazy<AssemblyLoadContext>? _alc;
|
||||
|
||||
public ExtensionInstance(IFileProvider fileProvider, string location)
|
||||
{
|
||||
|
@ -24,18 +29,30 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
Location = location;
|
||||
Configuration = CreateConfiguration(fileProvider);
|
||||
Name = GetName(Configuration, location);
|
||||
_alc = new Lazy<AssemblyLoadContext>(() => new ExtensionAssemblyLoadContext(this));
|
||||
|
||||
var serviceProviders = GetOptions<string[]>(ExtensionServiceProvidersSectionName);
|
||||
|
||||
if (serviceProviders is not null)
|
||||
{
|
||||
_alc = new Lazy<AssemblyLoadContext>(() => new ExtensionAssemblyLoadContext(this, serviceProviders));
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public bool HasAssemblyLoadContext => _alc.IsValueCreated;
|
||||
public IEnumerable<IExtensionServiceProvider> GetServiceProviders()
|
||||
{
|
||||
if (_alc is null)
|
||||
{
|
||||
return Enumerable.Empty<IExtensionServiceProvider>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="AssemblyLoadContext"/> for the extension. Guard calls with <see cref="HasAssemblyLoadContext"/> first,
|
||||
/// otherwise it may trigger creation of the load context if it is not needed.
|
||||
/// </summary>
|
||||
public AssemblyLoadContext LoadContext => _alc.Value;
|
||||
return _alc.Value.Assemblies.SelectMany(assembly => assembly
|
||||
.GetTypes()
|
||||
.Where(t => t.IsPublic && !t.IsAbstract && typeof(IExtensionServiceProvider).IsAssignableFrom(t))
|
||||
.Select(t => Activator.CreateInstance(t))
|
||||
.Cast<IExtensionServiceProvider>());
|
||||
}
|
||||
|
||||
public string Location { get; }
|
||||
|
||||
|
@ -74,7 +91,20 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
{
|
||||
var provider = new ZipFileProvider(e);
|
||||
|
||||
return new ExtensionInstance(new ZipFileProvider(e), e);
|
||||
try
|
||||
{
|
||||
return new ExtensionInstance(provider, e);
|
||||
}
|
||||
|
||||
// If the manifest file couldn't be found, let's try looking at one layer deep with the name
|
||||
// of the file as the first folder. This is what happens when you create a zip file from a folder
|
||||
// with Windows or 7-zip
|
||||
catch (UpgradeException ex) when (ex.InnerException is FileNotFoundException)
|
||||
{
|
||||
var subpath = Path.GetFileNameWithoutExtension(e);
|
||||
var subprovider = new SubFileProvider(provider, subpath);
|
||||
return new ExtensionInstance(subprovider, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -106,8 +136,17 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
}
|
||||
|
||||
private static IConfiguration CreateConfiguration(IFileProvider fileProvider)
|
||||
=> new ConfigurationBuilder()
|
||||
.AddJsonFile(fileProvider, ManifestFileName, optional: false, reloadOnChange: false)
|
||||
.Build();
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ConfigurationBuilder()
|
||||
.AddJsonFile(fileProvider, ManifestFileName, optional: false, reloadOnChange: false)
|
||||
.Build();
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new UpgradeException("Could not find manifest file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,26 +82,5 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
return Array.Empty<TTo>();
|
||||
}
|
||||
}
|
||||
|
||||
private class SubFileProvider : IFileProvider
|
||||
{
|
||||
private readonly IFileProvider _other;
|
||||
private readonly string _path;
|
||||
|
||||
public SubFileProvider(IFileProvider other, string path)
|
||||
{
|
||||
_other = other;
|
||||
_path = path;
|
||||
}
|
||||
|
||||
public IDirectoryContents GetDirectoryContents(string subpath)
|
||||
=> _other.GetDirectoryContents(Path.Combine(_path, subpath));
|
||||
|
||||
public IFileInfo GetFileInfo(string subpath)
|
||||
=> _other.GetFileInfo(Path.Combine(_path, subpath));
|
||||
|
||||
public IChangeToken Watch(string filter)
|
||||
=> _other.Watch(Path.Combine(_path, filter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
{
|
||||
public static class ExtensionProviderExtensions
|
||||
{
|
||||
private const string ExtensionServiceProvidersSectionName = "ExtensionServiceProviders";
|
||||
private const string UpgradeAssistantExtensionPathsSettingName = "UpgradeAssistantExtensionPaths";
|
||||
|
||||
/// <summary>
|
||||
|
@ -41,7 +40,12 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
|
||||
foreach (var extension in GetExtensions(configuration, additionalExtensionPaths))
|
||||
{
|
||||
services.AddExtension(extension);
|
||||
services.AddSingleton(extension);
|
||||
|
||||
foreach (var sp in extension.GetServiceProviders())
|
||||
{
|
||||
sp.AddServices(new ExtensionServiceCollection(services, extension));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,63 +90,5 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
return fromConfig.Concat(pathsFromString).Concat(additionalExtensionPaths);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddExtension(this IServiceCollection services, ExtensionInstance extension)
|
||||
{
|
||||
services.AddSingleton(extension);
|
||||
|
||||
var extensionServiceProviderPaths = extension.GetOptions<string[]>(ExtensionServiceProvidersSectionName);
|
||||
|
||||
if (extensionServiceProviderPaths is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var path in extensionServiceProviderPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileInfo = extension.FileProvider.GetFileInfo(path);
|
||||
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
Console.WriteLine($"ERROR: Could not find extension service provider assembly {path} in extension {extension.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
using var assemblyStream = fileInfo.CreateReadStream();
|
||||
|
||||
if (assemblyStream is null)
|
||||
{
|
||||
Console.WriteLine($"ERROR: Could not find extension service provider assembly {path} in extension {extension.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
extension.LoadContext.LoadFromStream(assemblyStream);
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!extension.HasAssemblyLoadContext)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var serviceProviders = extension.LoadContext.Assemblies.SelectMany(assembly => assembly
|
||||
.GetTypes()
|
||||
.Where(t => t.IsPublic && !t.IsAbstract && typeof(IExtensionServiceProvider).IsAssignableFrom(t))
|
||||
.Select(t => Activator.CreateInstance(t))
|
||||
.Cast<IExtensionServiceProvider>());
|
||||
|
||||
foreach (var sp in serviceProviders)
|
||||
{
|
||||
sp.AddServices(new ExtensionServiceCollection(services, extension));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
||||
{
|
||||
internal sealed class SubFileProvider : IFileProvider
|
||||
{
|
||||
private readonly IFileProvider _other;
|
||||
private readonly string _path;
|
||||
|
||||
public SubFileProvider(IFileProvider other, string path)
|
||||
{
|
||||
_other = other;
|
||||
_path = path;
|
||||
}
|
||||
|
||||
public IDirectoryContents GetDirectoryContents(string subpath)
|
||||
=> _other.GetDirectoryContents(Path.Combine(_path, subpath));
|
||||
|
||||
public IFileInfo GetFileInfo(string subpath)
|
||||
=> _other.GetFileInfo(Path.Combine(_path, subpath));
|
||||
|
||||
public IChangeToken Watch(string filter)
|
||||
=> _other.Watch(Path.Combine(_path, filter));
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
|
||||
public IDirectoryContents GetDirectoryContents(string subpath)
|
||||
{
|
||||
subpath = NormalizeZipPath(subpath);
|
||||
var list = new List<IFileInfo>();
|
||||
|
||||
foreach (var entry in _archive.Entries)
|
||||
|
@ -56,9 +57,11 @@ namespace Microsoft.DotNet.UpgradeAssistant.Extensions
|
|||
return new ListDirectoryContents(list);
|
||||
}
|
||||
|
||||
private static string NormalizeZipPath(string path) => path.Replace('\\', '/');
|
||||
|
||||
public IFileInfo GetFileInfo(string subpath)
|
||||
{
|
||||
var entry = _archive.GetEntry(subpath);
|
||||
var entry = _archive.GetEntry(NormalizeZipPath(subpath));
|
||||
|
||||
if (entry is null)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче