diff --git a/ICSharpCode.Decompiler.Tests/Helpers/RoslynToolset.cs b/ICSharpCode.Decompiler.Tests/Helpers/RoslynToolset.cs index 3a75f331a..ec251070f 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/RoslynToolset.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/RoslynToolset.cs @@ -149,14 +149,33 @@ namespace ICSharpCode.Decompiler.Tests.Helpers // In the .NET ("netcore") build of the compiler toolset the executables live in a // "bincore" subfolder of the tasks directory, as .dlls launched through the dotnet host. + // The old Microsoft.Net.Compilers packages (Roslyn 1.x/2.x) have no .NET build; they + // only ship .NET Framework executables, which non-Windows platforms host with Mono. public string GetCSharpCompiler(string version) { - return GetCompiler(OperatingSystem.IsWindows() ? "csc.exe" : "bincore/csc.dll", version); + return GetHostedCompiler(version, "csc"); } public string GetVBCompiler(string version) { - return GetCompiler(OperatingSystem.IsWindows() ? "vbc.exe" : "bincore/vbc.dll", version); + return GetHostedCompiler(version, "vbc"); + } + + string GetHostedCompiler(string version, string name) + { + if (!OperatingSystem.IsWindows()) + { + // The installed path may point directly at a dotnet-hosted compiler directory + // (e.g. Microsoft.NETCore.Compilers' tools/bincore) or at a tasks directory + // with a bincore subfolder (Microsoft.Net.Compilers.Toolset). + string dll = GetCompiler($"{name}.dll", version); + if (File.Exists(dll)) + return dll; + dll = GetCompiler($"bincore/{name}.dll", version); + if (File.Exists(dll)) + return dll; + } + return GetCompiler($"{name}.exe", version); } string GetCompiler(string compiler, string version) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 499e82c4c..c58ad2fa3 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -144,7 +144,17 @@ namespace ICSharpCode.Decompiler.Tests.Helpers internal static async Task Initialize() { await roslynToolset.Fetch("1.3.2", "Microsoft.Net.Compilers", "tools").ConfigureAwait(false); - await roslynToolset.Fetch("2.10.0", "Microsoft.Net.Compilers", "tools").ConfigureAwait(false); + if (OperatingSystem.IsWindows()) + { + await roslynToolset.Fetch("2.10.0", "Microsoft.Net.Compilers", "tools").ConfigureAwait(false); + } + else + { + // Microsoft.Net.Compilers only ships .NET Framework executables. The sibling + // Microsoft.NETCore.Compilers package contains the dotnet-hosted build of the + // same compiler version (tools/bincore/csc.dll), usable on any platform. + await roslynToolset.Fetch("2.10.0", "Microsoft.NETCore.Compilers", "tools/bincore").ConfigureAwait(false); + } // On non-Windows hosts the net472 compiler binaries cannot be executed; use the // .NET build of each toolset instead. Its tasks folder is named "netcoreapp3.1" // up to Roslyn 3.x and "netcore" from Roslyn 4.x on. @@ -175,34 +185,73 @@ namespace ICSharpCode.Decompiler.Tests.Helpers } } + /// + /// True when a Mono runtime is on the PATH. Mono hosts the .NET Framework builds of + /// compilers that have no .NET build (Roslyn 1.x csc.exe from Microsoft.Net.Compilers); + /// Roslyn 2.x+ configurations use dotnet-hosted builds and do not depend on Mono. + /// + public static readonly bool MonoRuntimeAvailable = + !OperatingSystem.IsWindows() + && (Environment.GetEnvironmentVariable("PATH") ?? "") + .Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries) + .Any(dir => File.Exists(Path.Combine(dir, "mono"))); + /// /// Reduces a compiler-configuration matrix to the entries usable on the current platform. /// On Windows every configuration is supported. On other platforms, configurations that - /// depend on Windows-only tools or runtimes are removed: the legacy (pre-Roslyn) csc/vbc, - /// Roslyn 1.x/2.x (their packages only ship .NET Framework binaries), mcs, and Force32Bit - /// (requires a 32-bit runtime). When is set - /// (correctness-style fixtures), configurations targeting .NET Framework 4.0 are removed - /// as well, because their output executables only run on Windows. + /// depend on Windows-only tools or runtimes are removed: the legacy (pre-Roslyn) csc/vbc + /// and Force32Bit (requires a 32-bit runtime) always; Roslyn 1.x/2.x (their packages only + /// ship .NET Framework binaries) and mcs unless a Mono runtime is available to host them. + /// When is set (correctness-style fixtures), + /// configurations targeting .NET Framework 4.0 are removed as well, because their output + /// executables only run on Windows, and the Mono-hosted compilers are not included either + /// (their output would likewise need a Mono host to execute). /// public static CompilerOptions[] SupportedOnCurrentPlatform(CompilerOptions[] configurations, bool executesCompiledOutput = false) { if (OperatingSystem.IsWindows()) return configurations; const CompilerOptions dotnetHostedCompilers = CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.UseRoslynLatest; - return configurations.Where(c => (c & dotnetHostedCompilers) != 0 + CompilerOptions supportedCompilers = dotnetHostedCompilers; + if (!executesCompiledOutput) + { + // The dotnet-hosted Roslyn 2.x build (Microsoft.NETCore.Compilers) can compile + // on any platform, but its output targets .NET Core 2.2 or .NET Framework, + // which the correctness runners cannot execute here. + supportedCompilers |= CompilerOptions.UseRoslyn2_10_0; + // Roslyn 1.3.2 has only a .NET Framework build and needs Mono as its host. + // The configuration stays in the matrix even without Mono so that a missing + // runtime shows up as skipped tests (see WrapCompiler) instead of a silently + // smaller matrix. mcs 2.6.4 stays excluded: it needs the Reflection.Emit + // COMPILER_ACCESS mode that current Mono runtimes no longer implement. + supportedCompilers |= CompilerOptions.UseRoslyn1_3_2; + } + return configurations.Where(c => (c & supportedCompilers) != 0 && !c.HasFlag(CompilerOptions.Force32Bit) && !(executesCompiledOutput && c.HasFlag(CompilerOptions.TargetNet40))).ToArray(); } /// /// Wraps an external compiler invocation. The .NET Framework builds of the Roslyn - /// compilers (csc.exe/vbc.exe) are directly executable; the .NET builds ship as a - /// .dll that must be launched through the 'dotnet' host. + /// compilers (csc.exe/vbc.exe) are directly executable on Windows and are hosted by + /// Mono elsewhere; the .NET builds ship as a .dll that must be launched through the + /// 'dotnet' host. /// static Command WrapCompiler(string compilerPath, string arguments) { + // Old compiler builds pin an out-of-support runtime in their runtimeconfig + // (e.g. Roslyn 2.x pins Microsoft.NETCore.App 2.0), so allow the host to + // roll forward across major versions to whatever runtime is installed. if (compilerPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) - return Cli.Wrap("dotnet").WithArguments($"\"{compilerPath}\" {arguments}"); + return Cli.Wrap("dotnet").WithArguments($"--roll-forward LatestMajor \"{compilerPath}\" {arguments}"); + if (!OperatingSystem.IsWindows() && compilerPath.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + { + if (!MonoRuntimeAvailable) + { + Assert.Ignore($"{Path.GetFileName(compilerPath)} requires a Mono runtime on the PATH to run on this platform."); + } + return Cli.Wrap("mono").WithArguments($"\"{compilerPath}\" {arguments}"); + } return Cli.Wrap(compilerPath).WithArguments(arguments); } @@ -695,7 +744,17 @@ namespace System.Runtime.CompilerServices if (flags.HasFlag(CompilerOptions.GeneratePdb)) { - otherOptions += "-debug:full "; + // The Windows PDB writer needs the native DiaSymReader, so only portable + // PDBs work on other platforms. Roslyn 2.x+ falls back to portable on its + // own; Roslyn 1.x needs the explicit request. + if (!OperatingSystem.IsWindows() && (flags & CompilerOptions.UseRoslynMask) != 0) + { + otherOptions += "-debug:portable "; + } + else + { + otherOptions += "-debug:full "; + } } else { @@ -764,9 +823,14 @@ namespace System.Runtime.CompilerServices Assert.Ignore($"Compilation with mcs ignored: test directory '{testBasePath}' needs to be checked out separately." + Environment.NewLine + $"git clone https://github.com/icsharpcode/ILSpy-tests \"{testBasePath}\""); } - string mcsPath = (flags & CompilerOptions.UseMcsMask) switch { - CompilerOptions.UseMcs5_23 => Path.Combine(testBasePath, @"mcs\5.23\bin\mcs.bat"), - _ => Path.Combine(testBasePath, @"mcs\2.6.4\bin\gmcs.bat") + // On Windows the submodule's bat launchers run the bundled Windows-native Mono; + // elsewhere the bundled compilers (managed .NET Framework assemblies) are hosted + // by the system Mono runtime via WrapCompiler. + string mcsPath = (flags & CompilerOptions.UseMcsMask, OperatingSystem.IsWindows()) switch { + (CompilerOptions.UseMcs5_23, true) => Path.Combine(testBasePath, @"mcs\5.23\bin\mcs.bat"), + (CompilerOptions.UseMcs5_23, false) => Path.Combine(testBasePath, "mcs", "5.23", "lib", "mono", "4.5", "mcs.exe"), + (_, true) => Path.Combine(testBasePath, @"mcs\2.6.4\bin\gmcs.bat"), + (_, false) => Path.Combine(testBasePath, "mcs", "2.6.4", "lib", "mono", "2.0", "gmcs.exe") }; string otherOptions = " -unsafe -o" + (flags.HasFlag(CompilerOptions.Optimize) ? "+ " : "- "); @@ -797,8 +861,7 @@ namespace System.Runtime.CompilerServices otherOptions += " \"-d:" + string.Join(";", preprocessorSymbols) + "\" "; } - var command = Cli.Wrap(mcsPath) - .WithArguments($"{otherOptions}-out:\"{Path.GetFullPath(results.PathToAssembly)}\" {string.Join(" ", sourceFileNames.Select(fn => '"' + Path.GetFullPath(fn) + '"'))}") + var command = WrapCompiler(mcsPath, $"{otherOptions}-out:\"{Path.GetFullPath(results.PathToAssembly)}\" {string.Join(" ", sourceFileNames.Select(fn => '"' + Path.GetFullPath(fn) + '"'))}") .WithValidation(CommandResultValidation.None); //Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}");