Optional recursive dependency pulls and fallback dependency load (#1595)

* Optional recursive dependency pulls and fallback dependency load

* add api10 todo

---------

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
This commit is contained in:
srkizer 2024-01-04 02:28:41 +09:00 committed by GitHub
parent 01cde50a46
commit de53150bd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 38 additions and 14 deletions

View file

@ -131,9 +131,16 @@ internal class AssemblyLoadContextBuilder
/// or the default app context. /// or the default app context.
/// </summary> /// </summary>
/// <param name="assemblyName">The name of the assembly.</param> /// <param name="assemblyName">The name of the assembly.</param>
/// <param name="recursive">Pull assmeblies recursively.</param>
/// <returns>The builder.</returns> /// <returns>The builder.</returns>
public AssemblyLoadContextBuilder PreferDefaultLoadContextAssembly(AssemblyName assemblyName) public AssemblyLoadContextBuilder PreferDefaultLoadContextAssembly(AssemblyName assemblyName, bool recursive)
{ {
if (!recursive)
{
this.defaultAssemblies.Add(assemblyName.Name);
return this;
}
var names = new Queue<AssemblyName>(new[] { assemblyName }); var names = new Queue<AssemblyName>(new[] { assemblyName });
while (names.TryDequeue(out var name)) while (names.TryDequeue(out var name))

View file

@ -46,7 +46,7 @@ internal class LoaderConfig
/// Gets a list of assemblies which should be unified between the host and the plugin. /// Gets a list of assemblies which should be unified between the host and the plugin.
/// </summary> /// </summary>
/// <seealso href="https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md">what-are-shared-types</seealso> /// <seealso href="https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md">what-are-shared-types</seealso>
public ICollection<AssemblyName> SharedAssemblies { get; } = new List<AssemblyName>(); public ICollection<(AssemblyName Name, bool Recursive)> SharedAssemblies { get; } = new List<(AssemblyName Name, bool Recursive)>();
/// <summary> /// <summary>
/// Gets or sets a value indicating whether attempt to unify all types from a plugin with the host. /// Gets or sets a value indicating whether attempt to unify all types from a plugin with the host.

View file

@ -194,7 +194,18 @@ internal class ManagedLoadContext : AssemblyLoadContext
} }
} }
return null; // https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/loading-managed#algorithm
// > These assemblies are loaded (load-by-name) as needed by the runtime.
// For load-by-name assembiles, the following will happen in order:
// (1) this.Load will be called.
// (2) AssemblyLoadContext.Default's cache will be referred for lookup.
// (3) Default probing will be done from PLATFORM_RESOURCE_ROOTS and APP_PATHS.
// https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing#managed-assembly-default-probing
// > TRUSTED_PLATFORM_ASSEMBLIES: List of platform and application assembly file paths.
// > APP_PATHS: is not populated by default and is omitted for most applications.
// If we return null here, if the assembly has not been already loaded, the resolution will fail.
// Therefore as the final attempt, we try loading from the default load context.
return this.defaultLoadContext.LoadFromAssemblyName(assemblyName);
} }
/// <summary> /// <summary>

View file

@ -146,18 +146,14 @@ internal class PluginLoader : IDisposable
builder.ShadowCopyNativeLibraries(); builder.ShadowCopyNativeLibraries();
} }
foreach (var assemblyName in config.SharedAssemblies) foreach (var (assemblyName, recursive) in config.SharedAssemblies)
{ {
builder.PreferDefaultLoadContextAssembly(assemblyName); builder.PreferDefaultLoadContextAssembly(assemblyName, recursive);
} }
// This allows plugins to search for dependencies in the Dalamud directory when their assembly // Note: not adding Dalamud path here as a probing path.
// load would otherwise fail, allowing them to resolve assemblies not already loaded by Dalamud // It will be dealt as the last resort from ManagedLoadContext.Load.
// itself yet. // See there for more details.
builder.AddProbingPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
// Also make sure that plugins do not load their own Dalamud assembly.
builder.PreferDefaultLoadContextAssembly(Assembly.GetExecutingAssembly().GetName());
return builder; return builder;
} }

View file

@ -627,8 +627,18 @@ internal class LocalPlugin : IDisposable
config.IsUnloadable = true; config.IsUnloadable = true;
config.LoadInMemory = true; config.LoadInMemory = true;
config.PreferSharedTypes = false; config.PreferSharedTypes = false;
config.SharedAssemblies.Add(typeof(Lumina.GameData).Assembly.GetName());
config.SharedAssemblies.Add(typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName()); // Pin Lumina and its dependencies recursively (compatibility behavior).
// It currently only pulls in System.* anyway.
// TODO(api10): Remove this. We don't want to pin Lumina anymore, plugins should be able to provide their own.
config.SharedAssemblies.Add((typeof(Lumina.GameData).Assembly.GetName(), true));
config.SharedAssemblies.Add((typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName(), true));
// Make sure that plugins do not load their own Dalamud assembly.
// We do not pin this recursively; if a plugin loads its own assembly of Dalamud, it is always wrong,
// but plugins may load other versions of assemblies that Dalamud depends on.
config.SharedAssemblies.Add((typeof(EntryPoint).Assembly.GetName(), false));
config.SharedAssemblies.Add((typeof(Common.DalamudStartInfo).Assembly.GetName(), false));
} }
private void EnsureLoader() private void EnsureLoader()