From a3dd8bc9d7f2590247af275a9bb9d195a16dd98c Mon Sep 17 00:00:00 2001 From: Ringo Hoffmann Date: Wed, 28 Apr 2021 08:23:52 +0200 Subject: [PATCH 01/33] add prerestore Dockerfile --- build/prerestore.Dockerfile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 build/prerestore.Dockerfile diff --git a/build/prerestore.Dockerfile b/build/prerestore.Dockerfile new file mode 100644 index 00000000..5c6664f5 --- /dev/null +++ b/build/prerestore.Dockerfile @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/dotnet/core/sdk:3.1 + +# 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 echo "return;" > tmpscript.cs +RUN dotnet script tmpscript.cs +RUN rm tmpscript.cs + +ENTRYPOINT [ "dotnet", "script" ] \ No newline at end of file From bea081723753f8d127566b74c162872fce7fb93a Mon Sep 17 00:00:00 2001 From: Ringo Hoffmann Date: Wed, 28 Apr 2021 08:24:29 +0200 Subject: [PATCH 02/33] add ghcr docker CD workflow --- .github/workflows/docker-cd.yml | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/docker-cd.yml 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 From e26df46cd66d5b9a5f0f3a7832219f9b304b09b1 Mon Sep 17 00:00:00 2001 From: zekroTJA Date: Tue, 11 May 2021 14:52:11 +0200 Subject: [PATCH 03/33] bump base image to 5.0; use dotnet eval to run prerestore --- build/prerestore.Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/build/prerestore.Dockerfile b/build/prerestore.Dockerfile index 5c6664f5..4bca3c56 100644 --- a/build/prerestore.Dockerfile +++ b/build/prerestore.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 @@ -8,8 +8,6 @@ RUN dotnet tool install dotnet-script --tool-path /usr/bin # 'dotnet restore' to be run. # This is necessary if you want to run this in a networkless # docker container. -RUN echo "return;" > tmpscript.cs -RUN dotnet script tmpscript.cs -RUN rm tmpscript.cs +RUN dotnet script eval "Console.WriteLine(\"☑️ Prepared env for offline usage\")" ENTRYPOINT [ "dotnet", "script" ] \ No newline at end of file From d93facebacb9f1cbea52f2c88961c63666d60d4a Mon Sep 17 00:00:00 2001 From: zekroTJA Date: Tue, 11 May 2021 14:52:46 +0200 Subject: [PATCH 04/33] bump base image to 5.0 --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From f4726ae45bb02ad033952c0f767bf3baf03cfe60 Mon Sep 17 00:00:00 2001 From: Bernhard Richter Date: Wed, 4 Aug 2021 23:26:04 +0200 Subject: [PATCH 05/33] Don't stop at entry when debugging in VS Code --- src/Dotnet.Script.Core/Templates/launch.json.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From f7e8573daeba7532aa0bb134c3e1919bf3e0c3d6 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Tue, 17 Aug 2021 14:55:23 +0300 Subject: [PATCH 06/33] Fixed issue with tests when running from a folder containing space. --- src/Dotnet.Script.Tests/ScriptTestRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dotnet.Script.Tests/ScriptTestRunner.cs b/src/Dotnet.Script.Tests/ScriptTestRunner.cs index ebb1d0d7..b7702f2d 100644 --- a/src/Dotnet.Script.Tests/ScriptTestRunner.cs +++ b/src/Dotnet.Script.Tests/ScriptTestRunner.cs @@ -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; } From 1c715d4b8f5a1ce14c4b5b8041cf76531bcb1111 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Tue, 17 Aug 2021 15:54:48 +0300 Subject: [PATCH 07/33] Fixed test issues when running from a folder containing space. --- src/Dotnet.Script.Tests/PackageSourceTests.cs | 4 ++-- src/Dotnet.Script.Tests/ScriptTestRunner.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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/ScriptTestRunner.cs b/src/Dotnet.Script.Tests/ScriptTestRunner.cs index b7702f2d..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) From d48d429834100a389cac57df25e85a296ae2b953 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Tue, 17 Aug 2021 16:44:17 +0300 Subject: [PATCH 08/33] Fixed test issues when running from a folder containing space. --- src/Dotnet.Script.Tests/ScriptPackagesFixture.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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}\""); } } From eece2a4dbe9ac0d4d8764e9e94929fe0ce635c43 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Tue, 17 Aug 2021 17:04:58 +0300 Subject: [PATCH 09/33] Fixed issue in DotNetRestorer with NuGet sources containing space. --- src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs | 3 ++- .../Dotnet.Script.DependencyModel.csproj | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs b/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs index 8b8a9f4a..4b028e23 100644 --- a/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs +++ b/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs @@ -2,6 +2,7 @@ using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.Process; using Dotnet.Script.DependencyModel.ProjectSystem; +using Gapotchenko.FX.Diagnostics; using System; using System.IO; using System.Linq; @@ -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..ecb2a623 100644 --- a/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj +++ b/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj @@ -32,6 +32,7 @@ + From 818d4958020dcc98fd4405ba55e0f830f2a1ec16 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Tue, 17 Aug 2021 17:14:04 +0300 Subject: [PATCH 10/33] Fixed all remaining test issues when running from a folder containing a space character in its name. --- src/Dotnet.Script.Tests/ScriptExecutionTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs index 62c84cb8..230bcd7d 100644 --- a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs +++ b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs @@ -3,6 +3,7 @@ using System.Text; using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.Shared.Tests; +using Gapotchenko.FX.Diagnostics; using Xunit; using Xunit.Abstractions; @@ -139,7 +140,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(CommandLine.Build(Path.Combine(TestPathUtils.GetPathToTestFixtureFolder("Issue189"), "SomeFolder", "Script.csx"))); Assert.Contains("Newtonsoft.Json.JsonConvert", result.output); } From 467eafdc959e08748b370b3297f754a9101db921 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Tue, 17 Aug 2021 17:58:39 +0300 Subject: [PATCH 11/33] Standalone implementation of a command line escape logic. --- .../Context/DotnetRestorer.cs | 2 +- .../Dotnet.Script.DependencyModel.csproj | 1 - .../Internal/CommandLine.cs | 93 +++++++++++++++++++ .../ScriptExecutionTests.cs | 3 +- 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/Dotnet.Script.DependencyModel/Internal/CommandLine.cs diff --git a/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs b/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs index 4b028e23..b3bcf621 100644 --- a/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs +++ b/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs @@ -1,8 +1,8 @@ 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; -using Gapotchenko.FX.Diagnostics; using System; using System.IO; using System.Linq; diff --git a/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj b/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj index ecb2a623..c1141c7b 100644 --- a/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj +++ b/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj @@ -32,7 +32,6 @@ - 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.Tests/ScriptExecutionTests.cs b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs index 230bcd7d..f13fd397 100644 --- a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs +++ b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs @@ -3,7 +3,6 @@ using System.Text; using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.Shared.Tests; -using Gapotchenko.FX.Diagnostics; using Xunit; using Xunit.Abstractions; @@ -140,7 +139,7 @@ public void ShouldHandleIssue181() [Fact] public void ShouldHandleIssue189() { - var result = ScriptTestRunner.Default.Execute(CommandLine.Build(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); } From 3e76cbddf217e494410c176deee81be80f00c1e0 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Tue, 17 Aug 2021 19:57:41 +0300 Subject: [PATCH 12/33] Fixed memory leak in AppDomain.AssemblyResolve event subscription on subsequent script executions. --- src/Dotnet.Script.Core/ScriptRunner.cs | 44 +++++++++++++++----------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index c9e23be7..38940695 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -34,30 +34,38 @@ public async Task Execute(string dllPath, IEnumerable var runtimeDepsMap = ScriptCompiler.CreateScriptDependenciesMap(runtimeDeps); var assembly = Assembly.LoadFrom(dllPath); // this needs to be called prior to 'AppDomain.CurrentDomain.AssemblyResolve' event handler added - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => ResolveAssembly(args, runtimeDepsMap); + Assembly OnResolve(object sender, ResolveEventArgs args) => ResolveAssembly(args, runtimeDepsMap); - 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; - - var resultTask = method.Invoke(null, new[] { submissionStates }) as Task; + AppDomain.CurrentDomain.AssemblyResolve += OnResolve; try { - _ = await resultTask; + 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; + + var resultTask = method.Invoke(null, new[] { submissionStates }) as Task; + try + { + _ = await resultTask; + } + catch (System.Exception ex) + { + ScriptConsole.WriteError(ex.ToString()); + throw new ScriptRuntimeException("Script execution resulted in an exception.", ex); + } + + return await resultTask; } - catch (System.Exception ex) + finally { - ScriptConsole.WriteError(ex.ToString()); - throw new ScriptRuntimeException("Script execution resulted in an exception.", ex); + AppDomain.CurrentDomain.AssemblyResolve -= OnResolve; } - - return await resultTask; } public Task Execute(ScriptContext context) From f1907e1627be46c0210c47841a6f83ad8b4d0b0f Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Wed, 18 Aug 2021 21:21:09 +0300 Subject: [PATCH 13/33] AssemblyLoadPal is used instead of AppDomain in order to cover both AppDomain and AssemblyLoadContext scenarios. --- .../Dotnet.Script.Core.csproj | 1 + src/Dotnet.Script.Core/ScriptRunner.cs | 18 ++++++++++-------- src/Dotnet.Script.Tests/ScriptRunnerTests.cs | 3 ++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 24a97b2f..665368de 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -23,6 +23,7 @@ + diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index 38940695..d4efedfe 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -7,6 +7,7 @@ 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; @@ -30,13 +31,15 @@ public ScriptRunner(ScriptCompiler scriptCompiler, LogFactory logFactory, Script public async Task Execute(string dllPath, IEnumerable commandLineArgs) { + var assemblyLoaderPal = AssemblyLoadPal.ForCurrentAppDomain; + 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 = assemblyLoaderPal.LoadFrom(dllPath); // this needs to be called prior to 'AppDomain.CurrentDomain.AssemblyResolve' event handler added - Assembly OnResolve(object sender, ResolveEventArgs args) => ResolveAssembly(args, runtimeDepsMap); + Assembly OnResolve(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args, runtimeDepsMap); - AppDomain.CurrentDomain.AssemblyResolve += OnResolve; + assemblyLoaderPal.Resolving += OnResolve; try { var type = assembly.GetType("Submission#0"); @@ -64,7 +67,7 @@ public async Task Execute(string dllPath, IEnumerable } finally { - AppDomain.CurrentDomain.AssemblyResolve -= OnResolve; + assemblyLoaderPal.Resolving -= OnResolve; } } @@ -97,12 +100,11 @@ public virtual async Task Execute(ScriptCompilationCont return ProcessScriptState(scriptResult); } - internal Assembly ResolveAssembly(ResolveEventArgs args, Dictionary runtimeDepsMap) + internal Assembly ResolveAssembly(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args, Dictionary runtimeDepsMap) { - var assemblyName = new AssemblyName(args.Name); - var result = runtimeDepsMap.TryGetValue(assemblyName.Name, out RuntimeAssembly runtimeAssembly); + var result = runtimeDepsMap.TryGetValue(args.Name.Name, out RuntimeAssembly runtimeAssembly); if (!result) return null; - var loadedAssembly = Assembly.LoadFrom(runtimeAssembly.Path); + var loadedAssembly = sender.LoadFrom(runtimeAssembly.Path); return loadedAssembly; } diff --git a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs index 913ad33d..b97197f5 100644 --- a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs +++ b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs @@ -3,6 +3,7 @@ using Dotnet.Script.Core; using Dotnet.Script.DependencyModel.Runtime; using Dotnet.Script.Shared.Tests; +using Gapotchenko.FX.Reflection; using Moq; using Xunit; @@ -15,7 +16,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 AssemblyLoadPal.ResolvingEventArgs(null, "AnyAssemblyName"), new Dictionary()); Assert.Null(result); } From 79066f115fe0e89c9bb1ea480ef02c570ec0b37a Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 11:06:52 +0300 Subject: [PATCH 14/33] ScriptRunner can be configured with a custom AssemblyLoadContext. --- .../Dotnet.Script.Core.csproj | 4 +++- src/Dotnet.Script.Core/ScriptRunner.cs | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 665368de..4080e72c 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.1.0 filipw - netstandard2.0 + netstandard2.0;netcoreapp2.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,6 +24,7 @@ + diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index d4efedfe..260b8127 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -3,6 +3,9 @@ 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; @@ -29,9 +32,23 @@ 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) { - var assemblyLoaderPal = AssemblyLoadPal.ForCurrentAppDomain; +#if NETCOREAPP + var assemblyLoadContext = AssemblyLoadContext; + var assemblyLoaderPal = assemblyLoadContext != null ? new AssemblyLoadPal(assemblyLoadContext) : AssemblyLoadPal.ForCurrentAppDomain; +#else + var assemblyLoaderPal = AssemblyLoadPal.ForCurrentAppDomain; +#endif var runtimeDeps = ScriptCompiler.RuntimeDependencyResolver.GetDependenciesForLibrary(dllPath); var runtimeDepsMap = ScriptCompiler.CreateScriptDependenciesMap(runtimeDeps); From 11e800cb27455784dba53c45d90368ae7656ddf9 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 11:09:11 +0300 Subject: [PATCH 15/33] ScriptRunner refactoring related to AssemblyLoadContext support. --- src/Dotnet.Script.Core/ScriptRunner.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index 260b8127..31d09583 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -45,18 +45,18 @@ public async Task Execute(string dllPath, IEnumerable { #if NETCOREAPP var assemblyLoadContext = AssemblyLoadContext; - var assemblyLoaderPal = assemblyLoadContext != null ? new AssemblyLoadPal(assemblyLoadContext) : AssemblyLoadPal.ForCurrentAppDomain; + var assemblyLoadPal = assemblyLoadContext != null ? new AssemblyLoadPal(assemblyLoadContext) : AssemblyLoadPal.ForCurrentAppDomain; #else - var assemblyLoaderPal = AssemblyLoadPal.ForCurrentAppDomain; + var assemblyLoadPal = AssemblyLoadPal.ForCurrentAppDomain; #endif var runtimeDeps = ScriptCompiler.RuntimeDependencyResolver.GetDependenciesForLibrary(dllPath); var runtimeDepsMap = ScriptCompiler.CreateScriptDependenciesMap(runtimeDeps); - var assembly = assemblyLoaderPal.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 Assembly OnResolve(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args, runtimeDepsMap); - assemblyLoaderPal.Resolving += OnResolve; + assemblyLoadPal.Resolving += OnResolve; try { var type = assembly.GetType("Submission#0"); @@ -84,7 +84,7 @@ public async Task Execute(string dllPath, IEnumerable } finally { - assemblyLoaderPal.Resolving -= OnResolve; + assemblyLoadPal.Resolving -= OnResolve; } } @@ -117,11 +117,11 @@ public virtual async Task Execute(ScriptCompilationCont return ProcessScriptState(scriptResult); } - internal Assembly ResolveAssembly(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args, Dictionary runtimeDepsMap) + internal Assembly ResolveAssembly(AssemblyLoadPal pal, AssemblyLoadPal.ResolvingEventArgs args, Dictionary runtimeDepsMap) { var result = runtimeDepsMap.TryGetValue(args.Name.Name, out RuntimeAssembly runtimeAssembly); if (!result) return null; - var loadedAssembly = sender.LoadFrom(runtimeAssembly.Path); + var loadedAssembly = pal.LoadFrom(runtimeAssembly.Path); return loadedAssembly; } From 5b765606f0aacf4f77710847a1655540b5db9079 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 13:30:40 +0300 Subject: [PATCH 16/33] AssemblyLoadContext: assembly loader, downstream propagation via contextual reflection for .NET Core 3.0+ hosts. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 2 +- src/Dotnet.Script.Core/ScriptRunner.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 4080e72c..f2749093 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.1.0 filipw - netstandard2.0;netcoreapp2.1 + netstandard2.0;netcoreapp2.1;netcoreapp3.1 Dotnet.Script.Core Dotnet.Script.Core script;csx;csharp;roslyn diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index 31d09583..0667c5c0 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -54,6 +54,15 @@ public async Task Execute(string dllPath, IEnumerable var runtimeDepsMap = ScriptCompiler.CreateScriptDependenciesMap(runtimeDeps); var assembly = assemblyLoadPal.LoadFrom(dllPath); // this needs to be called prior to 'AssemblyLoadPal.Resolving' event handler added +#if NETCOREAPP + using var assemblyAutoLoader = new AssemblyAutoLoader(assemblyLoadContext); + assemblyAutoLoader.AddAssembly(assembly); +#endif + +#if NETCOREAPP3_0_OR_GREATER + using var contextualReflectionScope = assemblyLoadContext.EnterContextualReflection(); +#endif + Assembly OnResolve(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args, runtimeDepsMap); assemblyLoadPal.Resolving += OnResolve; From a81c8ef0c41bfdc2660432463998c2e03b9e93f2 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 13:37:11 +0300 Subject: [PATCH 17/33] Custom assembly load context can be null and should not be used in this case. --- src/Dotnet.Script.Core/ScriptRunner.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index 0667c5c0..23d6ba1a 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -55,12 +55,12 @@ public async Task Execute(string dllPath, IEnumerable var assembly = assemblyLoadPal.LoadFrom(dllPath); // this needs to be called prior to 'AssemblyLoadPal.Resolving' event handler added #if NETCOREAPP - using var assemblyAutoLoader = new AssemblyAutoLoader(assemblyLoadContext); - assemblyAutoLoader.AddAssembly(assembly); + using var assemblyAutoLoader = assemblyLoadContext != null ? new AssemblyAutoLoader(assemblyLoadContext) : null; + assemblyAutoLoader?.AddAssembly(assembly); #endif #if NETCOREAPP3_0_OR_GREATER - using var contextualReflectionScope = assemblyLoadContext.EnterContextualReflection(); + using var contextualReflectionScope = assemblyLoadContext != null ? assemblyLoadContext.EnterContextualReflection() : default; #endif Assembly OnResolve(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args, runtimeDepsMap); From e71073995eb8d2a7b99d8a216a999fc56104ea30 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 14:06:43 +0300 Subject: [PATCH 18/33] Gluing and propagating custom AssemblyLoadContext through the higher API levels. --- .../Commands/ExecuteCodeCommand.cs | 7 ++++++- .../Commands/ExecuteCodeCommandOptions.cs | 12 ++++++++++++ .../Commands/ExecuteLibraryCommand.cs | 7 ++++++- .../Commands/ExecuteLibraryCommandOptions.cs | 13 +++++++++++++ .../Commands/ExecuteScriptCommand.cs | 15 ++++++++------- .../Commands/ExecuteScriptCommandOptions.cs | 12 ++++++++++++ 6 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs index 688540b0..b8b1b754 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs @@ -23,7 +23,12 @@ 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 runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; return await runner.Execute(context); } 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/ExecuteLibraryCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs index d7350e82..c406287d 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs @@ -26,7 +26,12 @@ 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 runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var result = await runner.Execute(absoluteFilePath, options.Arguments); return result; } 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..278ff42c 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) @@ -124,11 +131,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 From d8f58a083dfa9af899a435d60f5ee83de16292a3 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Thu, 19 Aug 2021 18:01:38 +0300 Subject: [PATCH 19/33] Script files are executed in isolated assembly load context. --- .gitignore | 1 + src/Dotnet.Script/IsolatedAssemblyLoadContext.cs | 10 ++++++++++ src/Dotnet.Script/Program.cs | 5 ++++- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/Dotnet.Script/IsolatedAssemblyLoadContext.cs 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/IsolatedAssemblyLoadContext.cs b/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs new file mode 100644 index 00000000..67ebe677 --- /dev/null +++ b/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs @@ -0,0 +1,10 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace Dotnet.Script +{ + sealed class IsolatedAssemblyLoadContext : AssemblyLoadContext + { + protected override Assembly Load(AssemblyName assemblyName) => null; + } +} diff --git a/src/Dotnet.Script/Program.cs b/src/Dotnet.Script/Program.cs index 54b2c196..62f3e6a2 100644 --- a/src/Dotnet.Script/Program.cs +++ b/src/Dotnet.Script/Program.cs @@ -245,7 +245,10 @@ private static int Wain(string[] args) packageSources.Values?.ToArray(), interactive.HasValue(), nocache.HasValue() - ); + ) + { + AssemblyLoadContext = new IsolatedAssemblyLoadContext() + }; var fileCommand = new ExecuteScriptCommand(ScriptConsole.Default, logFactory); return await fileCommand.Run(fileCommandOptions); From aecc8d626a364d6064b12786acf8f2336de2941a Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 19:02:38 +0300 Subject: [PATCH 20/33] Implemented full assembly isolation support at API level. --- .../Commands/ExecuteCodeCommand.cs | 13 +- .../Commands/ExecuteInteractiveCommand.cs | 7 +- .../ExecuteInteractiveCommandOptions.cs | 15 +- .../Commands/ExecuteLibraryCommand.cs | 13 +- .../Commands/ExecuteScriptCommand.cs | 7 +- .../Commands/PublishCommand.cs | 14 +- .../Commands/PublishCommandOptions.cs | 12 ++ .../Dotnet.Script.Core.csproj | 2 +- .../ScriptAssemblyLoadContext.cs | 140 ++++++++++++++++++ src/Dotnet.Script.Core/ScriptCompiler.cs | 26 +++- src/Dotnet.Script.Core/ScriptRunner.cs | 51 +++++-- src/Dotnet.Script.Tests/ScriptRunnerTests.cs | 3 +- 12 files changed, 265 insertions(+), 38 deletions(-) create mode 100644 src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs diff --git a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs index b8b1b754..b9b6b039 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs @@ -22,7 +22,12 @@ 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 compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) { #if NETCOREAPP @@ -31,11 +36,5 @@ public async Task Execute(ExecuteCodeCommandOptions options) }; 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/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 c406287d..f2a9fd37 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs @@ -25,7 +25,12 @@ public async Task Execute(ExecuteLibraryCommandOptions options } var absoluteFilePath = options.LibraryPath.GetRootedPath(); - var compiler = GetScriptCompiler(!options.NoCache, _logFactory); + var compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) { #if NETCOREAPP @@ -35,11 +40,5 @@ public async Task Execute(ExecuteLibraryCommandOptions options 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/ExecuteScriptCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs index 278ff42c..4b3b176a 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs @@ -64,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) { 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 f2749093..fcb3b5a4 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs new file mode 100644 index 00000000..fcd83e31 --- /dev/null +++ b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs @@ -0,0 +1,140 @@ +#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 + { + /// + /// + /// 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) => + assemblyName.Name switch + { + "mscorlib" or + "Microsoft.CodeAnalysis.Scripting" => true, + _ => false + }; + + /// + protected override Assembly? Load(AssemblyName assemblyName) => InvokeLoading(assemblyName); + + /// + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) => InvokeLoadingUnmanagedDll(unmanagedDllName); + + /// + /// Provides data for the event. + /// + public 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 23d6ba1a..b154d054 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -57,15 +57,43 @@ public async Task Execute(string dllPath, IEnumerable #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); + } + + 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 #if NETCOREAPP3_0_OR_GREATER using var contextualReflectionScope = assemblyLoadContext != null ? assemblyLoadContext.EnterContextualReflection() : default; #endif - Assembly OnResolve(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args, runtimeDepsMap); + Assembly OnResolving(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args.Name, runtimeDepsMap); - assemblyLoadPal.Resolving += OnResolve; + assemblyLoadPal.Resolving += OnResolving; try { var type = assembly.GetType("Submission#0"); @@ -78,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 { - assemblyLoadPal.Resolving -= OnResolve; + assemblyLoadPal.Resolving -= OnResolving; +#if NETCOREAPP + if (scriptAssemblyLoadContext != null) + { + scriptAssemblyLoadContext.LoadingUnmanagedDll -= OnLoadingUnmanagedDll; + scriptAssemblyLoadContext.Loading -= OnLoading; + } +#endif } } @@ -126,9 +159,9 @@ public virtual async Task Execute(ScriptCompilationCont return ProcessScriptState(scriptResult); } - internal Assembly ResolveAssembly(AssemblyLoadPal pal, AssemblyLoadPal.ResolvingEventArgs args, Dictionary runtimeDepsMap) + internal Assembly ResolveAssembly(AssemblyLoadPal pal, AssemblyName assemblyName, Dictionary runtimeDepsMap) { - var result = runtimeDepsMap.TryGetValue(args.Name.Name, out RuntimeAssembly runtimeAssembly); + var result = runtimeDepsMap.TryGetValue(assemblyName.Name, out RuntimeAssembly runtimeAssembly); if (!result) return null; var loadedAssembly = pal.LoadFrom(runtimeAssembly.Path); return loadedAssembly; diff --git a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs index b97197f5..bebc1aea 100644 --- a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs +++ b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; using Dotnet.Script.Core; using Dotnet.Script.DependencyModel.Runtime; using Dotnet.Script.Shared.Tests; @@ -16,7 +17,7 @@ public void ResolveAssembly_ReturnsNull_WhenRuntimeDepsMapDoesNotContainAssembly { var scriptRunner = CreateScriptRunner(); - var result = scriptRunner.ResolveAssembly(AssemblyLoadPal.ForCurrentAppDomain, new AssemblyLoadPal.ResolvingEventArgs(null, "AnyAssemblyName"), new Dictionary()); + var result = scriptRunner.ResolveAssembly(AssemblyLoadPal.ForCurrentAppDomain, new AssemblyName("AnyAssemblyName"), new Dictionary()); Assert.Null(result); } From 056901d990c74a796b075608d787cfab6c2685e2 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 19:03:45 +0300 Subject: [PATCH 21/33] dotnet-script now uses full assembly isolation for script file execution. --- src/Dotnet.Script/IsolatedAssemblyLoadContext.cs | 10 ---------- src/Dotnet.Script/Program.cs | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 src/Dotnet.Script/IsolatedAssemblyLoadContext.cs diff --git a/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs b/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs deleted file mode 100644 index 67ebe677..00000000 --- a/src/Dotnet.Script/IsolatedAssemblyLoadContext.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Reflection; -using System.Runtime.Loader; - -namespace Dotnet.Script -{ - sealed class IsolatedAssemblyLoadContext : AssemblyLoadContext - { - protected override Assembly Load(AssemblyName assemblyName) => null; - } -} diff --git a/src/Dotnet.Script/Program.cs b/src/Dotnet.Script/Program.cs index 62f3e6a2..bd2fd1d3 100644 --- a/src/Dotnet.Script/Program.cs +++ b/src/Dotnet.Script/Program.cs @@ -247,7 +247,7 @@ private static int Wain(string[] args) nocache.HasValue() ) { - AssemblyLoadContext = new IsolatedAssemblyLoadContext() + AssemblyLoadContext = new ScriptAssemblyLoadContext() }; var fileCommand = new ExecuteScriptCommand(ScriptConsole.Default, logFactory); From 11a3aa3ca5ed2b589fa34d2afeae9ae05226f867 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 19:04:30 +0300 Subject: [PATCH 22/33] Implemented 'ShouldIsolateScriptAssemblies' test for full assembly isolation. --- src/Dotnet.Script.Tests/ScriptExecutionTests.cs | 6 ++++++ .../TestFixtures/Isolation/Isolation.csx | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx 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/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); From 68c5645ec3cbc69a879187226fdd6dff3d26c765 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 21:03:31 +0300 Subject: [PATCH 23/33] Fixed library lookup issues with loading of unmanaged DLLs. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index fcb3b5a4..c8b5af13 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -25,7 +25,7 @@ - + From fad86f6e624ca69740600151b12c1804407d5e73 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 22:26:54 +0300 Subject: [PATCH 24/33] Enabled assembly isolation for interactive script execution. --- src/Dotnet.Script/Program.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Dotnet.Script/Program.cs b/src/Dotnet.Script/Program.cs index bd2fd1d3..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 @@ -247,7 +248,7 @@ private static int Wain(string[] args) nocache.HasValue() ) { - AssemblyLoadContext = new ScriptAssemblyLoadContext() + AssemblyLoadContext = CreateAssemblyLoadContext() }; var fileCommand = new ExecuteScriptCommand(ScriptConsole.Default, logFactory); @@ -265,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(); } } From 436bbe96584178e489b7588b23885e33ed00f2d9 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Fri, 20 Aug 2021 22:32:27 +0300 Subject: [PATCH 25/33] Inheritance-friendly ScriptAssemblyLoadContext constructors. --- .../ScriptAssemblyLoadContext.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs index fcd83e31..a362e190 100644 --- a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs +++ b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs @@ -13,6 +13,36 @@ namespace Dotnet.Script.Core /// 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. From e6aef4713e3ab8910834705f6f348caae5481c0c Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Sun, 22 Aug 2021 13:18:53 +0300 Subject: [PATCH 26/33] Removed a no longer needed workaround for the issue #268. Reference to a more polished version of Gapotchenko.FX.Reflection.Loader. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index c8b5af13..b69bf38d 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -25,17 +25,16 @@ - + - - From dcc3a89d396b9929e831ea736f4c3248816b3f41 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Sun, 22 Aug 2021 13:26:36 +0300 Subject: [PATCH 27/33] Removed a no longer needed workaround for the issue #166. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index b69bf38d..871b5499 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -32,10 +32,6 @@ - - From 6a7c2b36d499d7b4c5d8c9d9978f2fe86743539f Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Sun, 22 Aug 2021 15:32:27 +0300 Subject: [PATCH 28/33] Use a release version of Gapotchenko.FX.Reflection.Loader package. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 871b5499..960a4db7 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -25,7 +25,7 @@ - + From 2610c6e93ec2328e4d10b4a2ba02696da2d1b185 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Sun, 22 Aug 2021 16:31:16 +0300 Subject: [PATCH 29/33] Assembly name comparison should be case-insensitive. Fixed internal class visibility. --- .../ScriptAssemblyLoadContext.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs index a362e190..f5691074 100644 --- a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs +++ b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs @@ -53,13 +53,13 @@ protected ScriptAssemblyLoadContext(bool isCollectible) : /// /// The assembly name. /// true if the specified assembly is homogeneous; otherwise, false. - protected internal virtual bool IsHomogeneousAssembly(AssemblyName assemblyName) => - assemblyName.Name switch - { - "mscorlib" or - "Microsoft.CodeAnalysis.Scripting" => true, - _ => 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); @@ -70,7 +70,7 @@ protected internal virtual bool IsHomogeneousAssembly(AssemblyName assemblyName) /// /// Provides data for the event. /// - public sealed class LoadingEventArgs : EventArgs + internal sealed class LoadingEventArgs : EventArgs { public LoadingEventArgs(AssemblyName assemblyName) { From b3b1e36e990000d2faa22623f1641b99d54f8e3c Mon Sep 17 00:00:00 2001 From: filipw Date: Mon, 23 Aug 2021 16:30:13 +0200 Subject: [PATCH 30/33] drop support for .NET Core 2.1 --- README.md | 8 ++++---- build/Build.csx | 2 +- build/omnisharp.json | 2 +- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 2 +- .../Dotnet.Script.DependencyModel.NuGet.csproj | 2 +- .../Dotnet.Script.DependencyModel.csproj | 2 +- .../ProjectSystem/csproj.template | 2 +- .../CompilationDepenencyTests.cs | 4 ++-- src/Dotnet.Script.Tests/Dotnet.Script.Tests.csproj | 2 +- src/Dotnet.Script/Dotnet.Script.csproj | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 5d621cd0..ae711e33 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` | @@ -316,7 +316,7 @@ 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" +#! "netcoreapp3.1" #load "nuget:simple-targets-csx, 6.0.0" using static SimpleTargets; @@ -466,7 +466,7 @@ 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" +#! "netcoreapp3.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/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/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 24a97b2f..3b7e406d 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -2,7 +2,7 @@ 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 Dotnet.Script.Core 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/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/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/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 From 4bbcc1aee5bfcce9e4566f3bcf54c56423f8ac47 Mon Sep 17 00:00:00 2001 From: filipw Date: Mon, 23 Aug 2021 16:33:45 +0200 Subject: [PATCH 31/33] clean up --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ae711e33..3586ff41 100644 --- a/README.md +++ b/README.md @@ -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 From 07e10a21b6a23572a614c3b490fa36d7e217c415 Mon Sep 17 00:00:00 2001 From: Filip W Date: Mon, 23 Aug 2021 17:16:49 +0200 Subject: [PATCH 32/33] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 3586ff41..05c5ae0c 100644 --- a/README.md +++ b/README.md @@ -313,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# -#! "netcoreapp3.1" #load "nuget:simple-targets-csx, 6.0.0" using static SimpleTargets; @@ -463,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 -#! "netcoreapp3.1" using (var streamReader = new StreamReader(Console.OpenStandardInput())) { Write(streamReader.ReadToEnd().ToUpper()); From 353609e2a116f1126f75c6da02373a051331de73 Mon Sep 17 00:00:00 2001 From: hrumhurum Date: Mon, 23 Aug 2021 20:52:08 +0300 Subject: [PATCH 33/33] Dropped support for .NET Core 2.1. --- src/Dotnet.Script.Core/Dotnet.Script.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 08426a75..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;netcoreapp2.1;netcoreapp3.1 + netstandard2.0;netcoreapp3.1 Dotnet.Script.Core Dotnet.Script.Core script;csx;csharp;roslyn