diff --git a/.gitignore b/.gitignore index e9ec90a2..860e9507 100644 --- a/.gitignore +++ b/.gitignore @@ -265,3 +265,4 @@ project.json /build/dotnet-script /dotnet-script /.vscode +/src/Dotnet.Script/Properties/launchSettings.json diff --git a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs index 688540b0..b9b6b039 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs @@ -22,15 +22,19 @@ public async Task Execute(ExecuteCodeCommandOptions options) { var sourceText = SourceText.From(options.Code); var context = new ScriptContext(sourceText, options.WorkingDirectory ?? Directory.GetCurrentDirectory(), options.Arguments, null, options.OptimizationLevel, ScriptMode.Eval, options.PackageSources); - var compiler = GetScriptCompiler(!options.NoCache, _logFactory); - var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole); + var compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; + var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; return await runner.Execute(context); } - - private static ScriptCompiler GetScriptCompiler(bool useRestoreCache, LogFactory logFactory) - { - var compiler = new ScriptCompiler(logFactory, useRestoreCache); - return compiler; - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs index b1bdf40b..11ebf3a7 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs @@ -1,4 +1,7 @@ using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { @@ -20,5 +23,14 @@ public ExecuteCodeCommandOptions(string code, string workingDirectory, string[] public OptimizationLevel OptimizationLevel { get; } public bool NoCache { get; } public string[] PackageSources { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs index 17d4f3cf..a883fce2 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs @@ -18,7 +18,12 @@ public ExecuteInteractiveCommand(ScriptConsole scriptConsole, LogFactory logFact public async Task Execute(ExecuteInteractiveCommandOptions options) { - var compiler = new ScriptCompiler(_logFactory, useRestoreCache: false); + var compiler = new ScriptCompiler(_logFactory, useRestoreCache: false) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var runner = new InteractiveRunner(compiler, _logFactory, _scriptConsole, options.PackageSources); if (options.ScriptFile == null) diff --git a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs index 5b189d06..bb7658ca 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs @@ -1,10 +1,12 @@ -using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { public class ExecuteInteractiveCommandOptions { - public ExecuteInteractiveCommandOptions(ScriptFile scriptFile, string[] arguments ,string[] packageSources) + public ExecuteInteractiveCommandOptions(ScriptFile scriptFile, string[] arguments, string[] packageSources) { ScriptFile = scriptFile; Arguments = arguments; @@ -14,5 +16,14 @@ public ExecuteInteractiveCommandOptions(ScriptFile scriptFile, string[] argument public ScriptFile ScriptFile { get; } public string[] Arguments { get; } public string[] PackageSources { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs index d7350e82..f2a9fd37 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs @@ -25,16 +25,20 @@ public async Task Execute(ExecuteLibraryCommandOptions options } var absoluteFilePath = options.LibraryPath.GetRootedPath(); - var compiler = GetScriptCompiler(!options.NoCache, _logFactory); - var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole); + var compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; + var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var result = await runner.Execute(absoluteFilePath, options.Arguments); return result; } - - private static ScriptCompiler GetScriptCompiler(bool useRestoreCache, LogFactory logFactory) - { - var compiler = new ScriptCompiler(logFactory, useRestoreCache); - return compiler; - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs index 2c0f4796..a3c3cb7e 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs @@ -1,3 +1,7 @@ +#if NETCOREAPP +using System.Runtime.Loader; +#endif + namespace Dotnet.Script.Core.Commands { public class ExecuteLibraryCommandOptions @@ -12,5 +16,14 @@ public ExecuteLibraryCommandOptions(string libraryPath, string[] arguments, bool public string LibraryPath { get; } public string[] Arguments { get; } public bool NoCache { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs index 470a4e52..4b3b176a 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs @@ -32,7 +32,14 @@ public async Task Run(ExecuteScriptCommandOptions optio } var pathToLibrary = GetLibrary(options); - return await ExecuteLibrary(pathToLibrary, options.Arguments, options.NoCache); + + var libraryOptions = new ExecuteLibraryCommandOptions(pathToLibrary, options.Arguments, options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; + return await new ExecuteLibraryCommand(_scriptConsole, _logFactory).Execute(libraryOptions); } private async Task DownloadAndRunCode(ExecuteScriptCommandOptions executeOptions) @@ -57,7 +64,12 @@ private string GetLibrary(ExecuteScriptCommandOptions executeOptions) return pathToLibrary; } - var options = new PublishCommandOptions(executeOptions.File, executionCacheFolder, "script", PublishType.Library, executeOptions.OptimizationLevel, executeOptions.PackageSources, null, executeOptions.NoCache); + var options = new PublishCommandOptions(executeOptions.File, executionCacheFolder, "script", PublishType.Library, executeOptions.OptimizationLevel, executeOptions.PackageSources, null, executeOptions.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = executeOptions.AssemblyLoadContext +#endif + }; new PublishCommand(_scriptConsole, _logFactory).Execute(options); if (hash != null) { @@ -124,11 +136,5 @@ public bool TryGetHash(string cacheFolder, out string hash) hash = File.ReadAllText(pathToHashFile); return true; } - - private async Task ExecuteLibrary(string pathToLibrary, string[] arguments, bool noCache) - { - var options = new ExecuteLibraryCommandOptions(pathToLibrary, arguments, noCache); - return await new ExecuteLibraryCommand(_scriptConsole, _logFactory).Execute(options); - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs index 656cb74d..93577f01 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs @@ -1,4 +1,7 @@ using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { @@ -20,5 +23,14 @@ public ExecuteScriptCommandOptions(ScriptFile file, string[] arguments, Optimiza public string[] PackageSources { get; } public bool IsInteractive { get; } public bool NoCache { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/PublishCommand.cs b/src/Dotnet.Script.Core/Commands/PublishCommand.cs index 684911c0..7e5311eb 100644 --- a/src/Dotnet.Script.Core/Commands/PublishCommand.cs +++ b/src/Dotnet.Script.Core/Commands/PublishCommand.cs @@ -28,7 +28,12 @@ public void Execute(PublishCommandOptions options) (options.PublishType == PublishType.Library ? Path.Combine(Path.GetDirectoryName(absoluteFilePath), "publish") : Path.Combine(Path.GetDirectoryName(absoluteFilePath), "publish", options.RuntimeIdentifier)); var absolutePublishDirectory = publishDirectory.GetRootedPath(); - var compiler = GetScriptCompiler(!options.NoCache, _logFactory); + var compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var scriptEmitter = new ScriptEmitter(_scriptConsole, compiler); var publisher = new ScriptPublisher(_logFactory, scriptEmitter); var code = absoluteFilePath.ToSourceText(); @@ -43,12 +48,5 @@ public void Execute(PublishCommandOptions options) publisher.CreateExecutable(context, _logFactory, options.RuntimeIdentifier, options.LibraryName); } } - - private static ScriptCompiler GetScriptCompiler(bool useRestoreCache, LogFactory logFactory) - { - - var compiler = new ScriptCompiler(logFactory, useRestoreCache); - return compiler; - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs b/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs index a82fa295..7dd44e1c 100644 --- a/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs @@ -1,5 +1,8 @@ using Dotnet.Script.DependencyModel.Environment; using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { @@ -25,6 +28,15 @@ public PublishCommandOptions(ScriptFile file, string outputDirectory, string lib public string[] PackageSources { get; } public string RuntimeIdentifier { get; } public bool NoCache { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script isolation. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } public enum PublishType diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 3b7e406d..a58748f6 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -4,7 +4,7 @@ A cross platform library allowing you to run C# (CSX) scripts with support for debugging and inline NuGet packages. Based on Roslyn. 1.2.0 filipw - netstandard2.0 + netstandard2.0;netcoreapp3.1 Dotnet.Script.Core Dotnet.Script.Core script;csx;csharp;roslyn @@ -16,6 +16,7 @@ false false false + 9.0 true ../dotnet-script.snk @@ -23,17 +24,14 @@ + + - - - diff --git a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs new file mode 100644 index 00000000..f5691074 --- /dev/null +++ b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs @@ -0,0 +1,170 @@ +#if NETCOREAPP + +using System; +using System.Reflection; +using System.Runtime.Loader; + +#nullable enable + +namespace Dotnet.Script.Core +{ + /// + /// Represents assembly load context for a script with full and automatic assembly isolation. + /// + public class ScriptAssemblyLoadContext : AssemblyLoadContext + { + /// + /// Initializes a new instance of the class. + /// + public ScriptAssemblyLoadContext() + { + } + +#if NETCOREAPP3_0_OR_GREATER + /// + /// Initializes a new instance of the class + /// with a name and a value that indicates whether unloading is enabled. + /// + /// + /// + public ScriptAssemblyLoadContext(string? name, bool isCollectible = false) : + base(name, isCollectible) + { + } + + /// + /// Initializes a new instance of the class + /// with a value that indicates whether unloading is enabled. + /// + /// + protected ScriptAssemblyLoadContext(bool isCollectible) : + base(isCollectible) + { + } +#endif + + /// + /// + /// Gets the value indicating whether a specified assembly is homogeneous. + /// + /// + /// Homogeneous assemblies are those shared by both host and scripts. + /// + /// + /// The assembly name. + /// true if the specified assembly is homogeneous; otherwise, false. + protected internal virtual bool IsHomogeneousAssembly(AssemblyName assemblyName) + { + var name = assemblyName.Name; + return + string.Equals(name, "mscorlib", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Microsoft.CodeAnalysis.Scripting", StringComparison.OrdinalIgnoreCase); + } + + /// + protected override Assembly? Load(AssemblyName assemblyName) => InvokeLoading(assemblyName); + + /// + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) => InvokeLoadingUnmanagedDll(unmanagedDllName); + + /// + /// Provides data for the event. + /// + internal sealed class LoadingEventArgs : EventArgs + { + public LoadingEventArgs(AssemblyName assemblyName) + { + Name = assemblyName; + } + + public AssemblyName Name { get; } + } + + /// + /// Represents a method that handles the event. + /// + /// The sender. + /// The arguments. + /// The loaded assembly or null if the assembly cannot be resolved. + internal delegate Assembly? LoadingEventHandler(ScriptAssemblyLoadContext sender, LoadingEventArgs args); + + LoadingEventHandler? m_Loading; + + /// + /// Occurs when an assembly is being loaded. + /// + internal event LoadingEventHandler Loading + { + add => m_Loading += value; + remove => m_Loading -= value; + } + + Assembly? InvokeLoading(AssemblyName assemblyName) + { + var eh = m_Loading; + if (eh != null) + { + var args = new LoadingEventArgs(assemblyName); + foreach (LoadingEventHandler handler in eh.GetInvocationList()) + { + var assembly = handler(this, args); + if (assembly != null) + return assembly; + } + } + return null; + } + + /// + /// Provides data for the event. + /// + internal sealed class LoadingUnmanagedDllEventArgs : EventArgs + { + public LoadingUnmanagedDllEventArgs(string unmanagedDllName, Func loadUnmanagedDllFromPath) + { + UnmanagedDllName = unmanagedDllName; + LoadUnmanagedDllFromPath = loadUnmanagedDllFromPath; + } + + public string UnmanagedDllName { get; } + public Func LoadUnmanagedDllFromPath { get; } + } + + /// + /// Represents a method that handles the event. + /// + /// The sender. + /// The arguments. + /// The loaded DLL or if the DLL cannot be resolved. + internal delegate IntPtr LoadingUnmanagedDllEventHandler(ScriptAssemblyLoadContext sender, LoadingUnmanagedDllEventArgs args); + + LoadingUnmanagedDllEventHandler? m_LoadingUnmanagedDll; + + /// + /// Occurs when an unmanaged DLL is being loaded. + /// + internal event LoadingUnmanagedDllEventHandler LoadingUnmanagedDll + { + add => m_LoadingUnmanagedDll += value; + remove => m_LoadingUnmanagedDll -= value; + } + + IntPtr InvokeLoadingUnmanagedDll(string unmanagedDllName) + { + var eh = m_LoadingUnmanagedDll; + if (eh != null) + { + var args = new LoadingUnmanagedDllEventArgs(unmanagedDllName, LoadUnmanagedDllFromPath); + foreach (LoadingUnmanagedDllEventHandler handler in eh.GetInvocationList()) + { + var dll = handler(this, args); + if (dll != IntPtr.Zero) + return dll; + } + } + return IntPtr.Zero; + } + } +} + +#endif diff --git a/src/Dotnet.Script.Core/ScriptCompiler.cs b/src/Dotnet.Script.Core/ScriptCompiler.cs index 994cb39f..459f1603 100644 --- a/src/Dotnet.Script.Core/ScriptCompiler.cs +++ b/src/Dotnet.Script.Core/ScriptCompiler.cs @@ -3,6 +3,9 @@ using System.Collections.Immutable; using System.Linq; using System.Reflection; +#if NETCOREAPP +using System.Runtime.Loader; +#endif using System.Text; using System.Threading.Tasks; using Dotnet.Script.Core.Internal; @@ -78,6 +81,15 @@ private ScriptCompiler(LogFactory logFactory, RuntimeDependencyResolver runtimeD } } +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif + public virtual ScriptOptions CreateScriptOptions(ScriptContext context, IList runtimeDependencies) { var scriptMap = runtimeDependencies.ToDictionary(rdt => rdt.Name, rdt => rdt.Scripts); @@ -176,7 +188,19 @@ private ScriptOptions AddScriptReferences(ScriptOptions scriptOptions, Dictionar { foreach (var runtimeAssembly in scriptDependenciesMap.Values) { - loadedAssembliesMap.TryGetValue(runtimeAssembly.Name.Name, out var loadedAssembly); + bool homogenization; +#if NETCOREAPP + homogenization = + AssemblyLoadContext is not ScriptAssemblyLoadContext salc || + salc.IsHomogeneousAssembly(runtimeAssembly.Name); +#else + homogenization = true; +#endif + + Assembly loadedAssembly = null; + if (homogenization) + loadedAssembliesMap.TryGetValue(runtimeAssembly.Name.Name, out loadedAssembly); + if (loadedAssembly == null) { _logger.Trace("Adding reference to a runtime dependency => " + runtimeAssembly); diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index 38940695..b154d054 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -3,10 +3,14 @@ using System.Collections.Immutable; using System.Linq; using System.Reflection; +#if NETCOREAPP +using System.Runtime.Loader; +#endif using System.Threading.Tasks; using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.Runtime; +using Gapotchenko.FX.Reflection; using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting; using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting.Hosting; @@ -28,15 +32,68 @@ public ScriptRunner(ScriptCompiler scriptCompiler, LogFactory logFactory, Script _scriptEnvironment = ScriptEnvironment.Default; } +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif + public async Task Execute(string dllPath, IEnumerable commandLineArgs) { +#if NETCOREAPP + var assemblyLoadContext = AssemblyLoadContext; + var assemblyLoadPal = assemblyLoadContext != null ? new AssemblyLoadPal(assemblyLoadContext) : AssemblyLoadPal.ForCurrentAppDomain; +#else + var assemblyLoadPal = AssemblyLoadPal.ForCurrentAppDomain; +#endif + var runtimeDeps = ScriptCompiler.RuntimeDependencyResolver.GetDependenciesForLibrary(dllPath); var runtimeDepsMap = ScriptCompiler.CreateScriptDependenciesMap(runtimeDeps); - var assembly = Assembly.LoadFrom(dllPath); // this needs to be called prior to 'AppDomain.CurrentDomain.AssemblyResolve' event handler added + var assembly = assemblyLoadPal.LoadFrom(dllPath); // this needs to be called prior to 'AssemblyLoadPal.Resolving' event handler added + +#if NETCOREAPP + using var assemblyAutoLoader = assemblyLoadContext != null ? new AssemblyAutoLoader(assemblyLoadContext) : null; + assemblyAutoLoader?.AddAssembly(assembly); + + Assembly OnLoading(ScriptAssemblyLoadContext sender, ScriptAssemblyLoadContext.LoadingEventArgs args) + { + var assemblyName = args.Name; + + if (sender.IsHomogeneousAssembly(assemblyName)) + { + // The default assembly loader will take care of it. + return null; + } + + return ResolveAssembly(assemblyLoadPal, assemblyName, runtimeDepsMap); + } - Assembly OnResolve(object sender, ResolveEventArgs args) => ResolveAssembly(args, runtimeDepsMap); + IntPtr OnLoadingUnmanagedDll(ScriptAssemblyLoadContext sender, ScriptAssemblyLoadContext.LoadingUnmanagedDllEventArgs args) + { + string dllPath = assemblyAutoLoader.ResolveUnmanagedDllPath(args.UnmanagedDllName); + if (dllPath == null) + return IntPtr.Zero; + return args.LoadUnmanagedDllFromPath(dllPath); + } + + var scriptAssemblyLoadContext = assemblyLoadContext as ScriptAssemblyLoadContext; + if (scriptAssemblyLoadContext != null) + { + scriptAssemblyLoadContext.Loading += OnLoading; + scriptAssemblyLoadContext.LoadingUnmanagedDll += OnLoadingUnmanagedDll; + } +#endif - AppDomain.CurrentDomain.AssemblyResolve += OnResolve; +#if NETCOREAPP3_0_OR_GREATER + using var contextualReflectionScope = assemblyLoadContext != null ? assemblyLoadContext.EnterContextualReflection() : default; +#endif + + Assembly OnResolving(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args.Name, runtimeDepsMap); + + assemblyLoadPal.Resolving += OnResolving; try { var type = assembly.GetType("Submission#0"); @@ -49,22 +106,27 @@ public async Task Execute(string dllPath, IEnumerable var submissionStates = new object[2]; submissionStates[0] = globals; - var resultTask = method.Invoke(null, new[] { submissionStates }) as Task; try { - _ = await resultTask; + var resultTask = (Task)method.Invoke(null, new[] { submissionStates }); + return await resultTask; } catch (System.Exception ex) { ScriptConsole.WriteError(ex.ToString()); throw new ScriptRuntimeException("Script execution resulted in an exception.", ex); } - - return await resultTask; } finally { - AppDomain.CurrentDomain.AssemblyResolve -= OnResolve; + assemblyLoadPal.Resolving -= OnResolving; +#if NETCOREAPP + if (scriptAssemblyLoadContext != null) + { + scriptAssemblyLoadContext.LoadingUnmanagedDll -= OnLoadingUnmanagedDll; + scriptAssemblyLoadContext.Loading -= OnLoading; + } +#endif } } @@ -97,12 +159,11 @@ public virtual async Task Execute(ScriptCompilationCont return ProcessScriptState(scriptResult); } - internal Assembly ResolveAssembly(ResolveEventArgs args, Dictionary runtimeDepsMap) + internal Assembly ResolveAssembly(AssemblyLoadPal pal, AssemblyName assemblyName, Dictionary runtimeDepsMap) { - var assemblyName = new AssemblyName(args.Name); var result = runtimeDepsMap.TryGetValue(assemblyName.Name, out RuntimeAssembly runtimeAssembly); if (!result) return null; - var loadedAssembly = Assembly.LoadFrom(runtimeAssembly.Path); + var loadedAssembly = pal.LoadFrom(runtimeAssembly.Path); return loadedAssembly; } diff --git a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs index f13fd397..112403b4 100644 --- a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs +++ b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs @@ -472,6 +472,12 @@ public void ShouldIgnoreGlobalJsonInScriptFolder() Assert.Contains("Hello world!", result.output); } + [Fact] + public void ShouldIsolateScriptAssemblies() + { + var result = ScriptTestRunner.Default.ExecuteFixture("Isolation"); + Assert.Contains("10.0.0.0", result.output); + } private static string CreateTestScript(string scriptFolder) { diff --git a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs index 913ad33d..bebc1aea 100644 --- a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs +++ b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Reflection; using Dotnet.Script.Core; using Dotnet.Script.DependencyModel.Runtime; using Dotnet.Script.Shared.Tests; +using Gapotchenko.FX.Reflection; using Moq; using Xunit; @@ -15,7 +17,7 @@ public void ResolveAssembly_ReturnsNull_WhenRuntimeDepsMapDoesNotContainAssembly { var scriptRunner = CreateScriptRunner(); - var result = scriptRunner.ResolveAssembly(new ResolveEventArgs("AnyAssemblyName"), new Dictionary()); + var result = scriptRunner.ResolveAssembly(AssemblyLoadPal.ForCurrentAppDomain, new AssemblyName("AnyAssemblyName"), new Dictionary()); Assert.Null(result); } diff --git a/src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx b/src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx new file mode 100644 index 00000000..5acb385b --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx @@ -0,0 +1,6 @@ +#r "nuget:Newtonsoft.Json, 10.0.1" + +using Newtonsoft.Json; + +var version = typeof(JsonConvert).Assembly.GetName().Version; +Console.WriteLine(version); diff --git a/src/Dotnet.Script/Program.cs b/src/Dotnet.Script/Program.cs index 54b2c196..a12cb30b 100644 --- a/src/Dotnet.Script/Program.cs +++ b/src/Dotnet.Script/Program.cs @@ -11,6 +11,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Runtime.Loader; using System.Threading.Tasks; namespace Dotnet.Script @@ -245,7 +246,10 @@ private static int Wain(string[] args) packageSources.Values?.ToArray(), interactive.HasValue(), nocache.HasValue() - ); + ) + { + AssemblyLoadContext = CreateAssemblyLoadContext() + }; var fileCommand = new ExecuteScriptCommand(ScriptConsole.Default, logFactory); return await fileCommand.Run(fileCommandOptions); @@ -262,16 +266,24 @@ private static int Wain(string[] args) private static async Task RunInteractive(bool useRestoreCache, LogFactory logFactory, string[] packageSources) { - var options = new ExecuteInteractiveCommandOptions(null, Array.Empty(), packageSources); + var options = new ExecuteInteractiveCommandOptions(null, Array.Empty(), packageSources) + { + AssemblyLoadContext = CreateAssemblyLoadContext() + }; await new ExecuteInteractiveCommand(ScriptConsole.Default, logFactory).Execute(options); return 0; } private async static Task RunInteractiveWithSeed(string file, LogFactory logFactory, string[] arguments, string[] packageSources) { - var options = new ExecuteInteractiveCommandOptions(new ScriptFile(file), arguments, packageSources); + var options = new ExecuteInteractiveCommandOptions(new ScriptFile(file), arguments, packageSources) + { + AssemblyLoadContext = CreateAssemblyLoadContext() + }; await new ExecuteInteractiveCommand(ScriptConsole.Default, logFactory).Execute(options); return 0; } + + static AssemblyLoadContext CreateAssemblyLoadContext() => new ScriptAssemblyLoadContext(); } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy