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}");