diff --git a/.github/workflows/docker-cd.yml b/.github/workflows/docker-cd.yml new file mode 100644 index 00000000..e1ed4155 --- /dev/null +++ b/.github/workflows/docker-cd.yml @@ -0,0 +1,41 @@ +name: Docker CD + +on: + push: + branches: + - master + tags: + - '*' + paths-ignore: + - '**.md' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + + - name: checkout code + uses: actions/checkout@v2 + + - name: Log in to ghcr + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - if: startsWith(github.ref, 'refs/heads/master') + run: echo "TAG=latest" >> $GITHUB_ENV + + - if: startsWith(github.ref, 'refs/tags') + run: echo "TAG=$(git describe --tags)" >> $GITHUB_ENV + + - name: Build & Push + uses: docker/build-push-action@v2 + with: + context: ./build + file: ./build/Dockerfile + push: true + tags: ghcr.io/${{ github.repository }}:${{ env.TAG }} \ No newline at end of file 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/README.md b/README.md index 5d621cd0..05c5ae0c 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Run C# scripts from the .NET CLI, define NuGet packages inline and edit/debug th | Name | Version | Framework(s) | | ------------------------------------- | ------------------------------------------------------------ | -------------------------------- | -| `dotnet-script` | [![Nuget](http://img.shields.io/nuget/v/dotnet-script.svg?maxAge=10800)](https://www.nuget.org/packages/dotnet-script/) | `netcoreapp2.1`, `netcoreapp3.1` | -| `Dotnet.Script` | [![Nuget](http://img.shields.io/nuget/v/dotnet.script.svg?maxAge=10800)](https://www.nuget.org/packages/dotnet.script/) | `netcoreapp2.1`, `netcoreapp3.1` | +| `dotnet-script` (global tool) | [![Nuget](http://img.shields.io/nuget/v/dotnet-script.svg?maxAge=10800)](https://www.nuget.org/packages/dotnet-script/) | `net5.0`, `netcoreapp3.1` | +| `Dotnet.Script` (CLI as Nuget) | [![Nuget](http://img.shields.io/nuget/v/dotnet.script.svg?maxAge=10800)](https://www.nuget.org/packages/dotnet.script/) | `net5.0`, `netcoreapp3.1` | | `Dotnet.Script.Core` | [![Nuget](http://img.shields.io/nuget/v/Dotnet.Script.Core.svg?maxAge=10800)](https://www.nuget.org/packages/Dotnet.Script.Core/) | `netstandard2.0` | | `Dotnet.Script.DependencyModel` | [![Nuget](http://img.shields.io/nuget/v/Dotnet.Script.DependencyModel.svg?maxAge=10800)](https://www.nuget.org/packages/Dotnet.Script.DependencyModel/) | `netstandard2.0` | | `Dotnet.Script.DependencyModel.Nuget` | [![Nuget](http://img.shields.io/nuget/v/Dotnet.Script.DependencyModel.Nuget.svg?maxAge=10800)](https://www.nuget.org/packages/Dotnet.Script.DependencyModel.Nuget/) | `netstandard2.0` | @@ -20,11 +20,11 @@ Run C# scripts from the .NET CLI, define NuGet packages inline and edit/debug th ### Prerequisites -The only thing we need to install is [.NET Core 2.1+ SDK](https://www.microsoft.com/net/download/core). In order to use C# 8.0 features, [.NET Core 3.1+ SDK](https://www.microsoft.com/net/download/core) must be installed. +The only thing we need to install is [.NET Core 3.1 or .NET 5.0 SDK](https://www.microsoft.com/net/download/core). ### .NET Core Global Tool -.NET Core 2.1 introduces the concept of global tools meaning that you can install `dotnet-script` using nothing but the .NET CLI. +.NET Core 2.1 introduced the concept of global tools meaning that you can install `dotnet-script` using nothing but the .NET CLI. ```shell dotnet tool install -g dotnet-script @@ -34,9 +34,6 @@ Tool 'dotnet-script' (version '0.22.0') was successfully installed. ``` The advantage of this approach is that you can use the same command for installation across all platforms. - -> ⚠️ In order to use the global tool you need [.NET Core SDK 2.1.300](https://www.microsoft.com/net/download/dotnet-core/sdk-2.1.300) or higher. The earlier previews and release candidates of .NET Core 2.1 are not supported. - .NET Core SDK also supports viewing a list of installed tools and their uninstallation. ```shell @@ -316,7 +313,6 @@ To consume a script package all we need to do specify the NuGet package in the ` The following example loads the [simple-targets](https://www.nuget.org/packages/simple-targets-csx) package that contains script files to be included in our script. ```C# -#! "netcoreapp2.1" #load "nuget:simple-targets-csx, 6.0.0" using static SimpleTargets; @@ -466,7 +462,6 @@ The following example shows how we can pipe data in and out of a script. The `UpperCase.csx` script simply converts the standard input to upper case and writes it back out to standard output. ```csharp -#! "netcoreapp2.1" using (var streamReader = new StreamReader(Console.OpenStandardInput())) { Write(streamReader.ReadToEnd().ToUpper()); diff --git a/build/Build.csx b/build/Build.csx index 02ee632c..0869e914 100644 --- a/build/Build.csx +++ b/build/Build.csx @@ -35,7 +35,7 @@ await StepRunner.Execute(Args); private void CreateGitHubReleaseAsset() { - DotNet.Publish(dotnetScriptProjectFolder, publishArtifactsFolder, "netcoreapp2.1"); + DotNet.Publish(dotnetScriptProjectFolder, publishArtifactsFolder, "netcoreapp3.1"); Zip(publishArchiveFolder, pathToGitHubReleaseAsset); } diff --git a/build/Dockerfile b/build/Dockerfile index 44e5014e..e6d29bc7 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/sdk:3.1 +FROM mcr.microsoft.com/dotnet/sdk:5.0 # https://www.nuget.org/packages/dotnet-script/ RUN dotnet tool install dotnet-script --tool-path /usr/bin diff --git a/build/omnisharp.json b/build/omnisharp.json index 5c14541a..44278159 100644 --- a/build/omnisharp.json +++ b/build/omnisharp.json @@ -1,6 +1,6 @@ { "script": { "enableScriptNuGetReferences": true, - "defaultTargetFramework": "netcoreapp2.1" + "defaultTargetFramework": "netcoreapp3.1" } } \ No newline at end of file diff --git a/build/prerestore.Dockerfile b/build/prerestore.Dockerfile new file mode 100644 index 00000000..4bca3c56 --- /dev/null +++ b/build/prerestore.Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/dotnet/sdk:5.0 + +# https://www.nuget.org/packages/dotnet-script/ +RUN dotnet tool install dotnet-script --tool-path /usr/bin + +# Create a simple script, execute it and cleanup after +# to create the script project dir, which requires +# 'dotnet restore' to be run. +# This is necessary if you want to run this in a networkless +# docker container. +RUN dotnet script eval "Console.WriteLine(\"☑️ Prepared env for offline usage\")" + +ENTRYPOINT [ "dotnet", "script" ] \ No newline at end of file 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 24a97b2f..a58748f6 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -2,9 +2,9 @@ A cross platform library allowing you to run C# (CSX) scripts with support for debugging and inline NuGet packages. Based on Roslyn. - 1.1.0 + 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 c9e23be7..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,36 +32,102 @@ 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 - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => ResolveAssembly(args, runtimeDepsMap); +#if NETCOREAPP + using var assemblyAutoLoader = assemblyLoadContext != null ? new AssemblyAutoLoader(assemblyLoadContext) : null; + assemblyAutoLoader?.AddAssembly(assembly); - var type = assembly.GetType("Submission#0"); - var method = type.GetMethod("", BindingFlags.Static | BindingFlags.Public); + Assembly OnLoading(ScriptAssemblyLoadContext sender, ScriptAssemblyLoadContext.LoadingEventArgs args) + { + var assemblyName = args.Name; - var globals = new CommandLineScriptGlobals(ScriptConsole.Out, CSharpObjectFormatter.Instance); - foreach (var arg in commandLineArgs) - globals.Args.Add(arg); + if (sender.IsHomogeneousAssembly(assemblyName)) + { + // The default assembly loader will take care of it. + return null; + } - var submissionStates = new object[2]; - submissionStates[0] = globals; + return ResolveAssembly(assemblyLoadPal, assemblyName, runtimeDepsMap); + } - var resultTask = method.Invoke(null, new[] { submissionStates }) as Task; - try + IntPtr OnLoadingUnmanagedDll(ScriptAssemblyLoadContext sender, ScriptAssemblyLoadContext.LoadingUnmanagedDllEventArgs args) { - _ = await resultTask; + string dllPath = assemblyAutoLoader.ResolveUnmanagedDllPath(args.UnmanagedDllName); + if (dllPath == null) + return IntPtr.Zero; + return args.LoadUnmanagedDllFromPath(dllPath); } - catch (System.Exception ex) + + var scriptAssemblyLoadContext = assemblyLoadContext as ScriptAssemblyLoadContext; + if (scriptAssemblyLoadContext != null) { - ScriptConsole.WriteError(ex.ToString()); - throw new ScriptRuntimeException("Script execution resulted in an exception.", ex); + scriptAssemblyLoadContext.Loading += OnLoading; + scriptAssemblyLoadContext.LoadingUnmanagedDll += OnLoadingUnmanagedDll; } +#endif - return await resultTask; +#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"); + var method = type.GetMethod("", BindingFlags.Static | BindingFlags.Public); + + var globals = new CommandLineScriptGlobals(ScriptConsole.Out, CSharpObjectFormatter.Instance); + foreach (var arg in commandLineArgs) + globals.Args.Add(arg); + + var submissionStates = new object[2]; + submissionStates[0] = globals; + + try + { + 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); + } + } + finally + { + assemblyLoadPal.Resolving -= OnResolving; +#if NETCOREAPP + if (scriptAssemblyLoadContext != null) + { + scriptAssemblyLoadContext.LoadingUnmanagedDll -= OnLoadingUnmanagedDll; + scriptAssemblyLoadContext.Loading -= OnLoading; + } +#endif + } } public Task Execute(ScriptContext context) @@ -89,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.Core/Templates/launch.json.template b/src/Dotnet.Script.Core/Templates/launch.json.template index 24492703..9b4b1c3b 100644 --- a/src/Dotnet.Script.Core/Templates/launch.json.template +++ b/src/Dotnet.Script.Core/Templates/launch.json.template @@ -12,7 +12,7 @@ "${file}" ], "cwd": "${workspaceRoot}", - "stopAtEntry": true + "stopAtEntry": false } ] } \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel.Nuget/Dotnet.Script.DependencyModel.NuGet.csproj b/src/Dotnet.Script.DependencyModel.Nuget/Dotnet.Script.DependencyModel.NuGet.csproj index becaacfe..ef8f59f9 100644 --- a/src/Dotnet.Script.DependencyModel.Nuget/Dotnet.Script.DependencyModel.NuGet.csproj +++ b/src/Dotnet.Script.DependencyModel.Nuget/Dotnet.Script.DependencyModel.NuGet.csproj @@ -8,7 +8,7 @@ https://github.com/filipw/dotnet-script.git git script;csx;csharp;roslyn;nuget - 1.1.0 + 1.2.0 A MetadataReferenceResolver that allows inline nuget references to be specified in script(csx) files. dotnet-script dotnet-script diff --git a/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs b/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs index 8b8a9f4a..b3bcf621 100644 --- a/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs +++ b/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs @@ -1,4 +1,5 @@ using Dotnet.Script.DependencyModel.Environment; +using Dotnet.Script.DependencyModel.Internal; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.Process; using Dotnet.Script.DependencyModel.ProjectSystem; @@ -44,7 +45,7 @@ string CreatePackageSourcesArguments() { return packageSources.Length == 0 ? string.Empty - : packageSources.Select(s => $"-s {s}") + : packageSources.Select(s => $"-s {CommandLine.EscapeArgument(s)}") .Aggregate((current, next) => $"{current} {next}"); } diff --git a/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj b/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj index c1141c7b..5f4f75d5 100644 --- a/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj +++ b/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj @@ -11,7 +11,7 @@ https://github.com/filipw/dotnet-script.git git script;csx;csharp;roslyn;omnisharp - 1.1.0 + 1.2.0 latest true ../dotnet-script.snk diff --git a/src/Dotnet.Script.DependencyModel/Internal/CommandLine.cs b/src/Dotnet.Script.DependencyModel/Internal/CommandLine.cs new file mode 100644 index 00000000..d7a037ba --- /dev/null +++ b/src/Dotnet.Script.DependencyModel/Internal/CommandLine.cs @@ -0,0 +1,93 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; + +namespace Dotnet.Script.DependencyModel.Internal +{ + /// + /// + /// Performs operations on instances that contain command line information. + /// + /// + /// Tip: a ready-to-use package with this functionality is available at https://www.nuget.org/packages/Gapotchenko.FX.Diagnostics.CommandLine. + /// + /// + /// + /// Available + /// + static class CommandLine + { + /// + /// Escapes and optionally quotes a command line argument. + /// + /// The command line argument. + /// The escaped and optionally quoted command line argument. + public static string EscapeArgument(string value) + { + if (value == null) + return null; + + int length = value.Length; + if (length == 0) + return string.Empty; + + var sb = new StringBuilder(); + Escape.AppendQuotedText(sb, value); + + if (sb.Length == length) + return value; + + return sb.ToString(); + } + + static class Escape + { + public static void AppendQuotedText(StringBuilder sb, string text) + { + bool quotingRequired = IsQuotingRequired(text); + if (quotingRequired) + sb.Append('"'); + + int numberOfQuotes = 0; + for (int i = 0; i < text.Length; i++) + { + if (text[i] == '"') + numberOfQuotes++; + } + + if (numberOfQuotes > 0) + { + if ((numberOfQuotes % 2) != 0) + throw new Exception("Command line parameter cannot contain an odd number of double quotes."); + text = text.Replace("\\\"", "\\\\\"").Replace("\"", "\\\""); + } + + sb.Append(text); + + if (quotingRequired && text.EndsWith("\\")) + sb.Append('\\'); + + if (quotingRequired) + sb.Append('"'); + } + + static bool IsQuotingRequired(string parameter) => + !AllowedUnquotedRegex.IsMatch(parameter) || + DefinitelyNeedQuotesRegex.IsMatch(parameter); + + static Regex m_CachedAllowedUnquotedRegex; + + static Regex AllowedUnquotedRegex => + m_CachedAllowedUnquotedRegex ??= new Regex( + @"^[a-z\\/:0-9\._\-+=]*$", + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + static Regex m_CachedDefinitelyNeedQuotesRegex; + + static Regex DefinitelyNeedQuotesRegex => + m_CachedDefinitelyNeedQuotesRegex ??= new Regex( + "[|><\\s,;\"]+", + RegexOptions.CultureInvariant); + } + } +} diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/csproj.template b/src/Dotnet.Script.DependencyModel/ProjectSystem/csproj.template index 119a3645..43ef3971 100644 --- a/src/Dotnet.Script.DependencyModel/ProjectSystem/csproj.template +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/csproj.template @@ -1,7 +1,7 @@  Exe - netcoreapp2.1 + net5.0 latest diff --git a/src/Dotnet.Script.Desktop.Tests/CompilationDepenencyTests.cs b/src/Dotnet.Script.Desktop.Tests/CompilationDepenencyTests.cs index ee90241a..e3b01a0b 100644 --- a/src/Dotnet.Script.Desktop.Tests/CompilationDepenencyTests.cs +++ b/src/Dotnet.Script.Desktop.Tests/CompilationDepenencyTests.cs @@ -15,8 +15,8 @@ public CompilationDependencyTests(ITestOutputHelper testOutputHelper) } [Theory] - [InlineData("netcoreapp2.1")] - [InlineData("netcoreapp3.0")] + [InlineData("netcoreapp3.1")] + [InlineData("net5.0")] public void ShouldGetCompilationDependenciesForNetCoreApp(string targetFramework) { var resolver = CreateResolver(); diff --git a/src/Dotnet.Script.Tests/Dotnet.Script.Tests.csproj b/src/Dotnet.Script.Tests/Dotnet.Script.Tests.csproj index 504cb2c0..2278ada9 100644 --- a/src/Dotnet.Script.Tests/Dotnet.Script.Tests.csproj +++ b/src/Dotnet.Script.Tests/Dotnet.Script.Tests.csproj @@ -1,6 +1,6 @@ - net5.0;netcoreapp3.1;netcoreapp2.1 + net5.0;netcoreapp3.1 false true ../dotnet-script.snk diff --git a/src/Dotnet.Script.Tests/PackageSourceTests.cs b/src/Dotnet.Script.Tests/PackageSourceTests.cs index c4668213..a7c30736 100644 --- a/src/Dotnet.Script.Tests/PackageSourceTests.cs +++ b/src/Dotnet.Script.Tests/PackageSourceTests.cs @@ -17,7 +17,7 @@ public void ShouldHandleSpecifyingPackageSource() { var fixture = "ScriptPackage/WithNoNuGetConfig"; var pathToScriptPackages = ScriptPackagesFixture.GetPathToPackagesFolder(); - var result = ScriptTestRunner.Default.ExecuteFixture(fixture, $"--no-cache -s {pathToScriptPackages}"); + var result = ScriptTestRunner.Default.ExecuteFixture(fixture, $"--no-cache -s \"{pathToScriptPackages}\""); Assert.Contains("Hello", result.output); Assert.Equal(0, result.exitCode); } @@ -27,7 +27,7 @@ public void ShouldHandleSpecifyingPackageSourceWhenEvaluatingCode() { var pathToScriptPackages = ScriptPackagesFixture.GetPathToPackagesFolder(); var code = @"#load \""nuget:ScriptPackageWithMainCsx,1.0.0\"" SayHello();"; - var result = ScriptTestRunner.Default.Execute($"--no-cache -s {pathToScriptPackages} eval \"{code}\""); + var result = ScriptTestRunner.Default.Execute($"--no-cache -s \"{pathToScriptPackages}\" eval \"{code}\""); Assert.Contains("Hello", result.output); Assert.Equal(0, result.exitCode); } diff --git a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs index 62c84cb8..112403b4 100644 --- a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs +++ b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs @@ -139,7 +139,7 @@ public void ShouldHandleIssue181() [Fact] public void ShouldHandleIssue189() { - var result = ScriptTestRunner.Default.Execute(Path.Combine(TestPathUtils.GetPathToTestFixtureFolder("Issue189"), "SomeFolder", "Script.csx")); + var result = ScriptTestRunner.Default.Execute($"\"{Path.Combine(TestPathUtils.GetPathToTestFixtureFolder("Issue189"), "SomeFolder", "Script.csx")}\""); Assert.Contains("Newtonsoft.Json.JsonConvert", result.output); } @@ -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/ScriptPackagesFixture.cs b/src/Dotnet.Script.Tests/ScriptPackagesFixture.cs index 1dadbdc2..2e635fcb 100644 --- a/src/Dotnet.Script.Tests/ScriptPackagesFixture.cs +++ b/src/Dotnet.Script.Tests/ScriptPackagesFixture.cs @@ -43,12 +43,12 @@ private void BuildScriptPackages() if (_scriptEnvironment.IsWindows) { command = pathtoNuget430; - var result = ProcessHelper.RunAndCaptureOutput(command, $"pack {specFile} -OutputDirectory {pathToPackagesOutputFolder}"); + var result = ProcessHelper.RunAndCaptureOutput(command, $"pack \"{specFile}\" -OutputDirectory \"{pathToPackagesOutputFolder}\""); } else { command = "mono"; - var result = ProcessHelper.RunAndCaptureOutput(command, $"{pathtoNuget430} pack {specFile} -OutputDirectory {pathToPackagesOutputFolder}"); + var result = ProcessHelper.RunAndCaptureOutput(command, $"\"{pathtoNuget430}\" pack \"{specFile}\" -OutputDirectory \"{pathToPackagesOutputFolder}\""); } } 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/ScriptTestRunner.cs b/src/Dotnet.Script.Tests/ScriptTestRunner.cs index ebb1d0d7..bd6ece3f 100644 --- a/src/Dotnet.Script.Tests/ScriptTestRunner.cs +++ b/src/Dotnet.Script.Tests/ScriptTestRunner.cs @@ -38,7 +38,7 @@ public int ExecuteInProcess(string arguments = null) public (string output, int exitCode) ExecuteFixture(string fixture, string arguments = null, string workingDirectory = null) { var pathToFixture = TestPathUtils.GetPathToTestFixture(fixture); - var result = ProcessHelper.RunAndCaptureOutput("dotnet", GetDotnetScriptArguments($"{pathToFixture} {arguments}"), workingDirectory); + var result = ProcessHelper.RunAndCaptureOutput("dotnet", GetDotnetScriptArguments($"\"{pathToFixture}\" {arguments}"), workingDirectory); return result; } @@ -46,7 +46,7 @@ public int ExecuteInProcess(string arguments = null) { var pathToScriptPackageFixtures = TestPathUtils.GetPathToTestFixtureFolder("ScriptPackage"); var pathToFixture = Path.Combine(pathToScriptPackageFixtures, fixture, $"{fixture}.csx"); - return ProcessHelper.RunAndCaptureOutput("dotnet", GetDotnetScriptArguments($"{pathToFixture} {arguments}"), workingDirectory); + return ProcessHelper.RunAndCaptureOutput("dotnet", GetDotnetScriptArguments($"\"{pathToFixture}\" {arguments}"), workingDirectory); } public int ExecuteFixtureInProcess(string fixture, string arguments = null) @@ -88,7 +88,7 @@ private string GetDotnetScriptArguments(string arguments) configuration = "Release"; #endif - var allArgs = $"exec {Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "Dotnet.Script", "bin", configuration, _scriptEnvironment.TargetFramework, "dotnet-script.dll")} {arguments}"; + var allArgs = $"exec \"{Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "Dotnet.Script", "bin", configuration, _scriptEnvironment.TargetFramework, "dotnet-script.dll")}\" {arguments}"; return allArgs; } 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/Dotnet.Script.csproj b/src/Dotnet.Script/Dotnet.Script.csproj index 7cbd6249..ca233066 100644 --- a/src/Dotnet.Script/Dotnet.Script.csproj +++ b/src/Dotnet.Script/Dotnet.Script.csproj @@ -1,10 +1,10 @@  Dotnet CLI tool allowing you to run C# (CSX) scripts. - 1.1.0 + 1.2.0 filipw Dotnet.Script - net5.0;netcoreapp2.1;netcoreapp3.1 + net5.0;netcoreapp3.1 portable dotnet-script Exe 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(); } }