From 315943614573f0e1973bafc761c27207b8ea2b45 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 5 Dec 2016 23:51:09 -0500 Subject: reimplement assembly caching (#187) This commit ensures DLLs are copied to the cache directory only if they changed, to avoid breaking debugging support unless necessary. To support this change, the assembly hash file has been replaced with a more detailed JSON structure, which is used to determine whether the cache is up-to-date and whether to use the cached or original assembly. Some mods contain multiple DLLs, which must be kept together to prevent assembly resolution issues; to simplify that (and avoid orphaned cache entries), each mod now has its own separate cache. --- .../AssemblyRewriting/AssemblyTypeRewriter.cs | 6 ++- .../Framework/AssemblyRewriting/CacheEntry.cs | 46 ++++++++++++++++++++ .../Framework/AssemblyRewriting/CachePaths.cs | 10 ++--- .../Framework/AssemblyRewriting/RewriteResult.cs | 49 ++++++++++++++++++++++ 4 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs create mode 100644 src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs (limited to 'src/StardewModdingAPI/Framework/AssemblyRewriting') diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs index 3459488e..9d4d6b11 100644 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/AssemblyTypeRewriter.cs @@ -54,7 +54,8 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /// Rewrite the types referenced by an assembly. /// The assembly to rewrite. - public void RewriteAssembly(AssemblyDefinition assembly) + /// Returns whether the assembly was modified. + public bool RewriteAssembly(AssemblyDefinition assembly) { ModuleDefinition module = assembly.Modules.Single(); // technically an assembly can have multiple modules, but none of the build tools (including MSBuild) support it; simplify by assuming one module @@ -71,7 +72,7 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting } } if (!shouldRewrite) - return; + return false; // add target assembly references foreach (AssemblyNameReference target in this.AssemblyMap.TargetReferences.Values) @@ -117,6 +118,7 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting } method.Body.OptimizeMacros(); } + return true; } diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs new file mode 100644 index 00000000..3dfbc78c --- /dev/null +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs @@ -0,0 +1,46 @@ +using System.IO; + +namespace StardewModdingAPI.Framework.AssemblyRewriting +{ + /// Represents cached metadata for a rewritten assembly. + internal class CacheEntry + { + /********* + ** Accessors + *********/ + /// The MD5 hash for the original assembly. + public readonly string Hash; + + /// The SMAPI version used to rewrite the assembly. + public readonly string ApiVersion; + + /// Whether to use the cached assembly instead of the original assembly. + public readonly bool UseCachedAssembly; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The MD5 hash for the original assembly. + /// The SMAPI version used to rewrite the assembly. + /// Whether to use the cached assembly instead of the original assembly. + public CacheEntry(string hash, string apiVersion, bool useCachedAssembly) + { + this.Hash = hash; + this.ApiVersion = apiVersion; + this.UseCachedAssembly = useCachedAssembly; + } + + /// Get whether the cache entry is up-to-date for the given assembly hash. + /// The paths for the cached assembly. + /// The MD5 hash of the original assembly. + /// The current SMAPI version. + public bool IsUpToDate(CachePaths paths, string hash, Version currentVersion) + { + return hash == this.Hash + && this.ApiVersion == currentVersion.ToString() + && (!this.UseCachedAssembly || File.Exists(paths.Assembly)); + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs index 17c4d188..18861873 100644 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/CachePaths.cs @@ -12,8 +12,8 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /// The file path of the assembly file. public string Assembly { get; } - /// The file path containing the MD5 hash for the assembly. - public string Hash { get; } + /// The file path containing the assembly metadata. + public string Metadata { get; } /********* @@ -22,12 +22,12 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /// Construct an instance. /// The directory path which contains the assembly. /// The file path of the assembly file. - /// The file path containing the MD5 hash for the assembly. - public CachePaths(string directory, string assembly, string hash) + /// The file path containing the assembly metadata. + public CachePaths(string directory, string assembly, string metadata) { this.Directory = directory; this.Assembly = assembly; - this.Hash = hash; + this.Metadata = metadata; } } } \ No newline at end of file diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs new file mode 100644 index 00000000..8f34bb20 --- /dev/null +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/RewriteResult.cs @@ -0,0 +1,49 @@ +namespace StardewModdingAPI.Framework.AssemblyRewriting +{ + /// Metadata about a preprocessed assembly. + internal class RewriteResult + { + /********* + ** Accessors + *********/ + /// The original assembly path. + public readonly string OriginalAssemblyPath; + + /// The cache paths. + public readonly CachePaths CachePaths; + + /// The rewritten assembly bytes. + public readonly byte[] AssemblyBytes; + + /// The MD5 hash for the original assembly. + public readonly string Hash; + + /// Whether to use the cached assembly instead of the original assembly. + public readonly bool UseCachedAssembly; + + /// Whether this data is newer than the cache. + public readonly bool IsNewerThanCache; + + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// + /// The cache paths. + /// The rewritten assembly bytes. + /// The MD5 hash for the original assembly. + /// Whether to use the cached assembly instead of the original assembly. + /// Whether this data is newer than the cache. + public RewriteResult(string originalAssemblyPath, CachePaths cachePaths, byte[] assemblyBytes, string hash, bool useCachedAssembly, bool isNewerThanCache) + { + this.OriginalAssemblyPath = originalAssemblyPath; + this.CachePaths = cachePaths; + this.Hash = hash; + this.AssemblyBytes = assemblyBytes; + this.UseCachedAssembly = useCachedAssembly; + this.IsNewerThanCache = isNewerThanCache; + } + } +} -- cgit