diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CompiledAssemblyData.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CompiledAssemblyData.cs index 98f2445..a18665a 100644 --- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CompiledAssemblyData.cs +++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CompiledAssemblyData.cs @@ -38,7 +38,7 @@ public Assembly LoadInAssemblyLoadContext (AssemblyLoadContext loadContext) return loadContext.LoadFromStream (new MemoryStream (Assembly)); } } -#else +#endif public Assembly LoadInCurrentAppDomain () { if (DebugSymbols != null) { @@ -47,6 +47,5 @@ public Assembly LoadInCurrentAppDomain () return System.Reflection.Assembly.Load (Assembly); } } -#endif } } diff --git a/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateAssemblyContext.cs b/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateAssemblyContext.cs index 0dba4fc..177d37f 100644 --- a/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateAssemblyContext.cs +++ b/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateAssemblyContext.cs @@ -11,21 +11,51 @@ namespace Mono.TextTemplating; partial class CompiledTemplate { - [SuppressMessage ("Performance", "CA1822:Mark members as static", Justification = "Same API for ALC and AppDomain build variants of the class")] - sealed class TemplateAssemblyContext : IDisposable + /// + /// Abstracts over loading assemblies into an AssemblyLoadContext or AppDomain + /// and resolving assemblies from the host. + /// + abstract class TemplateAssemblyContext : IDisposable { + public abstract Assembly LoadAssemblyFile (string assemblyPath); + public abstract Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData); + public virtual void Dispose () { } + + [SuppressMessage ("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "Conditionally compiled version of this returns multiple concrete types")] + + public static TemplateAssemblyContext Create (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles) + { +#if FEATURE_ASSEMBLY_LOAD_CONTEXT + if (!host.IsAssemblyLoadContextDisabled ()) { + return new AssemblyLoadContextTemplateAssemblyContext (host, referenceAssemblyFiles); + } +#endif + return new CurrentAppDomainTemplateAssemblyContext (host, referenceAssemblyFiles); + } + } + #if FEATURE_ASSEMBLY_LOAD_CONTEXT + sealed class AssemblyLoadContextTemplateAssemblyContext : TemplateAssemblyContext + { readonly TemplateAssemblyLoadContext templateContext; - public TemplateAssemblyContext (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles) => templateContext = new (referenceAssemblyFiles, host); - public Assembly LoadAssemblyFile (string assemblyPath) => templateContext.LoadFromAssemblyPath (assemblyPath); - public Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInAssemblyLoadContext (templateContext); - public void Dispose () { } -#else - readonly CurrentDomainAssemblyResolver assemblyResolver; - public TemplateAssemblyContext (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles) => assemblyResolver = new (referenceAssemblyFiles, host.ResolveAssemblyReference); - public Assembly LoadAssemblyFile (string assemblyPath) => Assembly.LoadFile (assemblyPath); - public Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInCurrentAppDomain (); - public void Dispose () => assemblyResolver.Dispose (); + public AssemblyLoadContextTemplateAssemblyContext (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles) + => templateContext = new (referenceAssemblyFiles, host); + public override Assembly LoadAssemblyFile (string assemblyPath) => templateContext.LoadFromAssemblyPath (assemblyPath); + public override Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInAssemblyLoadContext (templateContext); + } #endif + + sealed class CurrentAppDomainTemplateAssemblyContext : TemplateAssemblyContext + { + readonly CurrentDomainAssemblyResolver assemblyResolver; + public CurrentAppDomainTemplateAssemblyContext (ITextTemplatingEngineHost host, string[] referenceAssemblyFiles) + => assemblyResolver = new (referenceAssemblyFiles, host.ResolveAssemblyReference); + public override Assembly LoadAssemblyFile (string assemblyPath) => Assembly.LoadFile (assemblyPath); + public override Assembly LoadInMemoryAssembly (CompiledAssemblyData assemblyData) => assemblyData.LoadInCurrentAppDomain (); + public override void Dispose () + { + base.Dispose (); + assemblyResolver.Dispose (); + } } } diff --git a/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateExecutor.cs b/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateExecutor.cs index 8fc8a46..3a40b77 100644 --- a/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateExecutor.cs +++ b/Mono.TextTemplating/Mono.TextTemplating/CompiledTemplate.TemplateExecutor.cs @@ -19,7 +19,7 @@ class TemplateProcessor : MarshalByRefObject [SuppressMessage ("Performance", "CA1822:Mark members as static", Justification = "Needs to be an instance for MarshalByRefObject")] public string CreateAndProcess (ITextTemplatingEngineHost host, CompiledAssemblyData templateAssemblyData, string templateAssemblyFile, string fullName, CultureInfo culture, string[] referencedAssemblyFiles) { - using var context = new TemplateAssemblyContext (host, referencedAssemblyFiles); + using var context = TemplateAssemblyContext.Create (host, referencedAssemblyFiles); Assembly assembly = templateAssemblyData is not null ? context.LoadInMemoryAssembly (templateAssemblyData) diff --git a/Mono.TextTemplating/Mono.TextTemplating/CurrentDomainAssemblyResolver.cs b/Mono.TextTemplating/Mono.TextTemplating/CurrentDomainAssemblyResolver.cs index e990380..85c958d 100644 --- a/Mono.TextTemplating/Mono.TextTemplating/CurrentDomainAssemblyResolver.cs +++ b/Mono.TextTemplating/Mono.TextTemplating/CurrentDomainAssemblyResolver.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -#if !FEATURE_ASSEMBLY_LOAD_CONTEXT - using System; using System.IO; using System.Reflection; @@ -52,5 +50,3 @@ public void Dispose () } } } - -#endif \ No newline at end of file diff --git a/Mono.TextTemplating/Mono.TextTemplating/HostOptions.cs b/Mono.TextTemplating/Mono.TextTemplating/HostOptions.cs new file mode 100644 index 0000000..ee43096 --- /dev/null +++ b/Mono.TextTemplating/Mono.TextTemplating/HostOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.VisualStudio.TextTemplating; + +static class HostOptionExtensions +{ + const string DisableAlcOptionName = "DisableAssemblyLoadContext"; + + static bool IsOptionTrue (this ITextTemplatingEngineHost host, string optionName) => + host.GetHostOption(optionName) is string optionVal + && (optionVal == "1" || optionVal.Equals("true", StringComparison.OrdinalIgnoreCase)); + + public static bool IsAssemblyLoadContextDisabled (this ITextTemplatingEngineHost host) => host.IsOptionTrue (DisableAlcOptionName); +} \ No newline at end of file