diff --git a/source/Calamari.Scripting/DotnetScript/dotnet-script.1.6.0.zip b/source/Calamari.Scripting/DotnetScript/dotnet-script.1.6.0.zip deleted file mode 100644 index 88cda61773..0000000000 Binary files a/source/Calamari.Scripting/DotnetScript/dotnet-script.1.6.0.zip and /dev/null differ diff --git a/source/Calamari.Scripting/DotnetScript/dotnet-script.2.0.1.zip b/source/Calamari.Scripting/DotnetScript/dotnet-script.2.0.1.zip new file mode 100644 index 0000000000..65024309a4 Binary files /dev/null and b/source/Calamari.Scripting/DotnetScript/dotnet-script.2.0.1.zip differ diff --git a/source/Calamari.Scripting/DotnetScript/dotnet-script.runtimeconfig.json b/source/Calamari.Scripting/DotnetScript/dotnet-script.runtimeconfig.json new file mode 100644 index 0000000000..5d68f1c6ee --- /dev/null +++ b/source/Calamari.Scripting/DotnetScript/dotnet-script.runtimeconfig.json @@ -0,0 +1,14 @@ +{ + "runtimeOptions": { + "tfm": "net8.0", + "rollForward": "Major", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "8.0.0" + }, + "configProperties": { + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false + } + } +} diff --git a/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptRuntimeConfigFixture.cs b/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptRuntimeConfigFixture.cs new file mode 100644 index 0000000000..1829e6e699 --- /dev/null +++ b/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptRuntimeConfigFixture.cs @@ -0,0 +1,102 @@ +#nullable enable +using System.IO; +using System.IO.Compression; +using System.Linq; +using Calamari.Testing.Helpers; +using FluentAssertions; +using Newtonsoft.Json.Linq; +using NUnit.Framework; + +namespace Calamari.Tests.Fixtures.DotnetScript +{ + /// + /// dotnet-script is framework-dependent and the upstream build requests + /// Microsoft.NETCore.App 8.0.0 with no rollForward, so by default it will not start on a target + /// that has no 8.x runtime installed. Calamari ships a roll-forward policy over the top. + /// + /// The policy lives in source control as dotnet-script.runtimeconfig.json and is copied over the + /// extracted file by IncludeDotNetScript.targets. These tests guard the two ways that can break: + /// the copy silently not happening, and a future re-vendor bringing new upstream settings that + /// the override then clobbers. + /// + [TestFixture] + [Category(TestCategory.PlatformAgnostic)] + public class DotnetScriptRuntimeConfigFixture + { + const string ExpectedRollForward = "Major"; + + [Test] + public void BundledDotnetScript_RollsForwardToTheNewestInstalledRuntime() + { + var runtimeConfig = JObject.Parse(File.ReadAllText(BundledRuntimeConfigPath())); + + runtimeConfig["runtimeOptions"]?["rollForward"]?.Value() + .Should() + .Be(ExpectedRollForward, + "without it, C# script steps fail to launch on a target that has no 8.x runtime"); + } + + /// + /// The override replaces the whole file, so anything upstream adds or changes would be + /// silently dropped. If this fails after re-vendoring the zip, reconcile the override with + /// the new upstream file rather than just updating the expectation. + /// + [Test] + public void BundledRuntimeConfig_DiffersFromUpstreamOnlyByRollForward() + { + var vendoredZip = FindVendoredZip(); + if (vendoredZip == null) + Assert.Inconclusive("Vendored dotnet-script zip not found - this test needs a source checkout."); + + using var archive = ZipFile.OpenRead(vendoredZip!); + var upstreamEntry = archive.Entries + .Single(e => e.FullName.EndsWith("dotnet-script.runtimeconfig.json")); + + using var reader = new StreamReader(upstreamEntry.Open()); + var upstream = JObject.Parse(reader.ReadToEnd()); + var shipped = JObject.Parse(File.ReadAllText(BundledRuntimeConfigPath())); + + upstream["runtimeOptions"]?["rollForward"] + .Should() + .BeNull("upstream is expected to set no policy - if it now does, the override may be redundant"); + + // Normalise away the one intended difference, then the two files must agree. + ((JObject)shipped["runtimeOptions"]!).Remove("rollForward"); + + JToken.DeepEquals(shipped, upstream) + .Should() + .BeTrue($"the shipped runtimeconfig should match upstream apart from rollForward.{System.Environment.NewLine}" + + $"upstream: {upstream.ToString(Newtonsoft.Json.Formatting.None)}{System.Environment.NewLine}" + + $"shipped: {shipped.ToString(Newtonsoft.Json.Formatting.None)}"); + } + + static string BundledRuntimeConfigPath() + { + var path = TestEnvironment.GetTestPath("dotnet-script", "dotnet-script.runtimeconfig.json"); + File.Exists(path) + .Should() + .BeTrue($"IncludeDotNetScript.targets should have extracted dotnet-script to {path}"); + return path; + } + + /// + /// Walks up from the test output to the checkout, since the zip is a source artefact and is + /// not copied to the build output. + /// + static string? FindVendoredZip() + { + var directory = new DirectoryInfo(TestEnvironment.CurrentWorkingDirectory); + + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, "source", "Calamari.Scripting", "DotnetScript"); + if (Directory.Exists(candidate)) + return Directory.GetFiles(candidate, "dotnet-script.*.zip").SingleOrDefault(); + + directory = directory.Parent; + } + + return null; + } + } +} diff --git a/source/IncludeDotNetScript.targets b/source/IncludeDotNetScript.targets index 1f6ed73182..81ba0e407d 100644 --- a/source/IncludeDotNetScript.targets +++ b/source/IncludeDotNetScript.targets @@ -3,6 +3,13 @@ + + @@ -10,6 +17,7 @@ + @@ -18,6 +26,7 @@ + - \ No newline at end of file +