static Tester() { TesterPath = Path.GetDirectoryName(typeof(Tester).Assembly.Location); TestCasePath = Path.Combine(TesterPath, "../../../../TestCases"); #if DEBUG testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Debug/net6.0-windows"); #else testRunnerBasePath = Path.Combine(TesterPath, "../../../../../ICSharpCode.Decompiler.TestRunner/bin/Release/net6.0-windows"); #endif packagesPropsFile = Path.Combine(TesterPath, "../../../../../packages.props"); roslynLatestVersion = XDocument.Load(packagesPropsFile).XPathSelectElement("//RoslynVersion").Value; roslynToolset = new RoslynToolset(); vswhereToolset = new VsWhereToolset(); }
public static async Task <CompilerResults> CompileCSharp(string sourceFileName, CompilerOptions flags = CompilerOptions.UseDebug, string outputFileName = null) { List <string> sourceFileNames = new List <string> { sourceFileName }; foreach (Match match in Regex.Matches(File.ReadAllText(sourceFileName), @"#include ""([\w\d./]+)""")) { sourceFileNames.Add(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(sourceFileName), match.Groups[1].Value))); } bool targetNet40 = (flags & CompilerOptions.TargetNet40) != 0; bool useRoslyn = (flags & CompilerOptions.UseRoslynMask) != 0; if (useRoslyn && !targetNet40) { sourceFileNames.Add(targetFrameworkAttributeSnippetFile.Value); } var preprocessorSymbols = GetPreprocessorSymbols(flags); if ((flags & CompilerOptions.UseMcsMask) == 0) { CompilerResults results = new CompilerResults(); results.PathToAssembly = outputFileName ?? Path.GetTempFileName(); var(roslynVersion, languageVersion) = (flags & CompilerOptions.UseRoslynMask) switch { 0 => ("legacy", "5"), CompilerOptions.UseRoslyn1_3_2 => ("1.3.2", "6"), CompilerOptions.UseRoslyn2_10_0 => ("2.10.0", "latest"), CompilerOptions.UseRoslyn3_11_0 => ("3.11.0", "latest"), _ => (roslynLatestVersion, flags.HasFlag(CompilerOptions.Preview) ? "preview" : "latest") }; var cscPath = roslynToolset.GetCSharpCompiler(roslynVersion); string libPath; IEnumerable <string> references; if (useRoslyn && !targetNet40) { libPath = "\"" + coreRefAsmPath + "\""; references = coreDefaultReferences.Select(r => "-r:\"" + Path.Combine(coreRefAsmPath, r) + "\""); } else { libPath = "\"" + RefAsmPath + "\",\"" + Path.Combine(RefAsmPath, "Facades") + "\""; references = defaultReferences.Select(r => "-r:\"" + Path.Combine(RefAsmPath, r) + "\""); } if (flags.HasFlag(CompilerOptions.ReferenceVisualBasic)) { references = references.Concat(new[] { "-r:\"Microsoft.VisualBasic.dll\"" }); } string otherOptions = $"-noconfig " + $"-langversion:{languageVersion} " + $"-unsafe -o{(flags.HasFlag(CompilerOptions.Optimize) ? "+ " : "- ")}"; // note: the /shared switch is undocumented. It allows us to use the VBCSCompiler.exe compiler // server to speed up testing if (roslynVersion != "legacy") { otherOptions += "/shared "; if (!targetNet40 && Version.Parse(RoslynToolset.SanitizeVersion(roslynVersion)).Major > 2) { if (flags.HasFlag(CompilerOptions.NullableEnable)) { otherOptions += "/nullable+ "; } else { otherOptions += "/nullable- "; } } } if (flags.HasFlag(CompilerOptions.Library)) { otherOptions += "-t:library "; } else { otherOptions += "-t:exe "; } if (flags.HasFlag(CompilerOptions.GeneratePdb)) { otherOptions += "-debug:full "; } else { otherOptions += "-debug- "; } if (flags.HasFlag(CompilerOptions.Force32Bit)) { otherOptions += "-platform:x86 "; } else { otherOptions += "-platform:anycpu "; } if (preprocessorSymbols.Count > 0) { otherOptions += " \"-d:" + string.Join(";", preprocessorSymbols) + "\" "; } var command = Cli.Wrap(cscPath) .WithArguments($"{otherOptions} -lib:{libPath} {string.Join(" ", references)} -out:\"{Path.GetFullPath(results.PathToAssembly)}\" {string.Join(" ", sourceFileNames.Select(fn => '"' + Path.GetFullPath(fn) + '"'))}") .WithValidation(CommandResultValidation.None); Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}"); var result = await command.ExecuteBufferedAsync().ConfigureAwait(false); Console.WriteLine("output: " + result.StandardOutput); Console.WriteLine("errors: " + result.StandardError); Assert.AreEqual(0, result.ExitCode, "csc failed"); return(results); } else { CompilerResults results = new CompilerResults(); results.PathToAssembly = outputFileName ?? Path.GetTempFileName(); string testBasePath = RoundtripAssembly.TestDir; if (!Directory.Exists(testBasePath)) { 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") }; string otherOptions = " -unsafe -o" + (flags.HasFlag(CompilerOptions.Optimize) ? "+ " : "- "); if (flags.HasFlag(CompilerOptions.Library)) { otherOptions += "-t:library "; } else { otherOptions += "-t:exe "; } if (flags.HasFlag(CompilerOptions.UseDebug)) { otherOptions += "-g "; } if (flags.HasFlag(CompilerOptions.Force32Bit)) { otherOptions += "-platform:x86 "; } else { otherOptions += "-platform:anycpu "; } if (preprocessorSymbols.Count > 0) { 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) + '"'))}") .WithValidation(CommandResultValidation.None); Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}"); var result = await command.ExecuteBufferedAsync().ConfigureAwait(false); Console.WriteLine("output: " + result.StandardOutput); Console.WriteLine("errors: " + result.StandardError); Assert.AreEqual(0, result.ExitCode, "mcs failed"); return(results); } }