using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; namespace PluginLoader.Compiler; public class RoslynCompiler { private readonly List source = new(); private readonly bool debugBuild; public RoslynCompiler(bool debugBuild = false) { this.debugBuild = debugBuild; } public void Load(Stream s, string name) { var mem = new MemoryStream(); using (mem) { s.CopyTo(mem); source.Add(new(mem, name, debugBuild)); } } public byte[] Compile(string assemblyName, out byte[] symbols) { symbols = null; var compilation = CSharpCompilation.Create( assemblyName, source.Select(x => x.Tree), RoslynReferences.EnumerateAllReferences(), new( OutputKind.DynamicallyLinkedLibrary, optimizationLevel: debugBuild ? OptimizationLevel.Debug : OptimizationLevel.Release)); using (var pdb = new MemoryStream()) using (var ms = new MemoryStream()) { // write IL code into memory EmitResult result; if (debugBuild) result = compilation.Emit(ms, pdb, embeddedTexts: source.Select(x => x.Text), options: new(debugInformationFormat: DebugInformationFormat.PortablePdb, pdbFilePath: Path.ChangeExtension(assemblyName, "pdb"))); else result = compilation.Emit(ms); if (!result.Success) { // handle exceptions var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); foreach (var diagnostic in failures) { var location = diagnostic.Location; var source = this.source.FirstOrDefault(x => x.Tree == location.SourceTree); LogFile.WriteLine( $"{diagnostic.Id}: {diagnostic.GetMessage()} in file:\n{source?.Name ?? "null"} ({location.GetLineSpan().StartLinePosition})"); } throw new("Compilation failed!"); } if (debugBuild) { pdb.Seek(0, SeekOrigin.Begin); symbols = pdb.ToArray(); } ms.Seek(0, SeekOrigin.Begin); return ms.ToArray(); } } private class Source { public Source(Stream s, string name, bool includeText) { Name = name; var source = SourceText.From(s, canBeEmbedded: includeText); if (includeText) { Text = EmbeddedText.FromSource(name, source); Tree = CSharpSyntaxTree.ParseText(source, new(LanguageVersion.Latest), name); } else { Tree = CSharpSyntaxTree.ParseText(source, new(LanguageVersion.Latest)); } } public string Name { get; } public SyntaxTree Tree { get; } public EmbeddedText Text { get; } } }