Skip to content

Commit

Permalink
Merge pull request #48 from Guila767/master
Browse files Browse the repository at this point in the history
Cache plugin builds
  • Loading branch information
ZehMatt authored Nov 25, 2021
2 parents 3ad45bc + 157f6c5 commit f3fdbe6
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 77 deletions.
57 changes: 55 additions & 2 deletions src/Dotx64Managed/AssemblyLoader.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
Expand All @@ -15,14 +15,47 @@ internal class AssemblyLoader : AssemblyLoadContext
public AssemblyLoader()
: base(true)
{
/*
* Some plugin dependencies don't trigger the AssemblyLoadContext.Load()
* So we need to load any missing dependency to the app domain 'manually'
*/
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}

private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var assemblyName = new System.Reflection.AssemblyName(args.Name);

var extenalDll = externalAssemblies.FirstOrDefault(file => System.IO.Path.GetFileNameWithoutExtension(file)
.Equals(assemblyName.Name, StringComparison.InvariantCultureIgnoreCase));

if (extenalDll is not null)
{
externalAssemblies.Remove(extenalDll);
return System.Reflection.Assembly.LoadFrom(extenalDll);
}

// If the required assembly is not in the list it's probably because it is already loaded in the current domain.
// So as the last resource we gonna try to find it in the current domain;
var requiredAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(assembly => assembly.FullName == assemblyName.FullName);
return requiredAssembly;
}

protected override Assembly Load(AssemblyName assemblyName)
{
#if DEBUG
Console.WriteLine("[DEBUG] LoaderContext.Load({0})", assemblyName.Name);
#endif
return Assembly.Load(assemblyName);
var extenalDll = externalAssemblies.FirstOrDefault(file => System.IO.Path.GetFileNameWithoutExtension(file)
.Equals(assemblyName.Name, StringComparison.InvariantCultureIgnoreCase));

if(extenalDll is not null)
{
externalAssemblies.Remove(extenalDll);
return Assembly.LoadFile(extenalDll);
}

return Assembly.Load(assemblyName); // The assembly is already loaded in the context (We hope so)
}

public Assembly LoadFromFile(string path)
Expand All @@ -42,9 +75,29 @@ public bool UnloadCurrent()
return true;
}

public void AddExternalRequiredAssemblies(ICollection<string> assembliesPath)
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach(string assemblyPath in assembliesPath)
{
// Filter assemblies that are already loaded
var assemblyName = System.Reflection.AssemblyName.GetAssemblyName(assemblyPath);
if (loadedAssemblies.Any(assembly => assembly.FullName == assemblyName.FullName))
continue;
externalAssemblies.Add(assemblyPath);
}
}

public bool IsLoaded()
{
return Current != null;
}

~AssemblyLoader()
{
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
}

private HashSet<string> externalAssemblies = new();
}
}
4 changes: 2 additions & 2 deletions src/Dotx64Managed/Manager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static void Init(int pluginHandle)
{
PluginHandle = pluginHandle;

AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_NugetDepsAssemblyResolve;

Logging.Initialize();
Commands.Initialize();
Expand All @@ -43,7 +43,7 @@ public static void Init(int pluginHandle)
PluginManager.Initialize();
}

private static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
private static System.Reflection.Assembly CurrentDomain_NugetDepsAssemblyResolve(object sender, ResolveEventArgs args)
{
var libs = new DirectoryInfo(Utils.DotX64DbgNugetDepsPath).GetFiles("*.dll");
var assemblyName = new System.Reflection.AssemblyName(args.Name);
Expand Down
106 changes: 106 additions & 0 deletions src/Dotx64Managed/Plugins.Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,113 @@ void RebuildPlugins(CancellationToken token)
continue;
}

// We need to check if a rebuild is indeed necessary
var cacheDirectory = new DirectoryInfo(Path.Combine(plugin.BuildOutputPath, ".cache"));
cacheDirectory.Create();
var cacheFile = Path.Combine(cacheDirectory.FullName, "last_build");
if (plugin.AssemblyPath is null && File.Exists(cacheFile))
{
if (InitializePluginFromCache(plugin, cacheFile))
{
ReloadPlugin(plugin, plugin.AssemblyPath, token);
Utils.DebugPrintLine($"Loaded plugin '{plugin.Info.Name}' from CACHE");
DeleteNotUsedPluginCache(plugin);
plugin.RequiresRebuild = false;
continue;
}
else
Utils.DebugPrintLine($"Skiping cache...");
}
RebuildPlugin(plugin, token);
CachePluginBuild(plugin, cacheFile);
DeleteNotUsedPluginCache(plugin);
}

void DeleteNotUsedPluginCache(Plugin plugin)
{
var oldFiles = Directory.GetFiles(plugin.BuildOutputPath, "*.*", SearchOption.AllDirectories);
string baseFileName = Path.GetFileNameWithoutExtension(plugin.AssemblyPath);

foreach (var oldFile in oldFiles)
{
if (Path.GetFileNameWithoutExtension(oldFile).Equals(baseFileName, StringComparison.OrdinalIgnoreCase))
continue;

try
{
if (oldFile.EndsWith(".dll") || oldFile.EndsWith(".pdb"))
{
File.Delete(oldFile);
}
}
catch (Exception)
{
}
}
}

uint ComputePluginSourcesHash(Plugin plugin)
{
uint hash = uint.MaxValue;
var md5 = System.Security.Cryptography.MD5.Create();
foreach (var source in plugin.SourceFiles)
{
var md5Hash = md5.ComputeHash(File.ReadAllBytes(source));
foreach (byte b in md5Hash)
hash = (hash >> 8) ^ b;
}
return hash;
}

void CachePluginBuild(Plugin plugin, string cacheFilePath)
{
using var fs = File.OpenWrite(cacheFilePath);
using System.IO.Compression.ZipArchive zipArchive = new(fs, System.IO.Compression.ZipArchiveMode.Create);
var entry = zipArchive.CreateEntry(nameof(Plugin));
using BinaryWriter bw = new(entry.Open());
bw.Write((uint)0x4D5A); // ZM magic number ;)
bw.Write(ComputePluginSourcesHash(plugin)); // uint32 hash
bw.Write(plugin.AssemblyPath);
bw.Write(plugin.BuildOutputPath);
bw.Write((uint)0x4D5A);
}

bool InitializePluginFromCache(Plugin plugin, string cacheFilePath)
{
try
{
using System.IO.Compression.ZipArchive zipArchive = System.IO.Compression.ZipFile.OpenRead(cacheFilePath);
var entry = zipArchive.GetEntry(nameof(Plugin));
if(entry is null)
return false;

using BinaryReader br = new(entry.Open());

if (br.ReadUInt32() != 0x4D5A) // Check magic
return false;

uint hash = br.ReadUInt32();
if (hash != ComputePluginSourcesHash(plugin)) // Modified source files
return false;

string assemblyPath = br.ReadString();
if (!File.Exists(assemblyPath)) // Invalid cache
return false;
string buildOutputPath = br.ReadString();

if (br.ReadUInt32() != 0x4D5A) // Check magic
return false;

plugin.AssemblyPath = assemblyPath;
plugin.BuildOutputPath = buildOutputPath;
return true;

} catch (Exception ex)
{
if (ex is FormatException || ex is EndOfStreamException || ex is InvalidDataException)
return false;
throw;
}
}
}
}
Expand Down
Loading

0 comments on commit f3fdbe6

Please sign in to comment.