Skip to content

add functionality for writing compilation output to a Stream #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/Testura.Code.Tests/Compilation/CompilerTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using NUnit.Framework;
Expand All @@ -21,16 +22,17 @@ public void SetUp()
[Test]
public async Task CompileSourceAsync_WhenCompilingSource_ShouldGetADll()
{
var result = await _compiler.CompileSourceAsync(Path.Combine(TestContext.CurrentContext.TestDirectory, "test.dll"), new ClassBuilder("TestClass", "Test").Build().NormalizeWhitespace().ToString());
Assert.IsNotNull(result.PathToDll);
var outputPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "test01.dll");
var result = await _compiler.CompileSourceAsync(outputPath, new ClassBuilder("TestClass", "Test").Build().NormalizeWhitespace().ToString());
Assert.AreEqual(0, result.OutputRows.Count);
Assert.IsTrue(result.Success);
Assembly.LoadFrom(outputPath);
}

[Test]
public async Task CompileSourceAsync_WhenCompilingSourceWithError_ShouldGetListContainingErrors()
{
var result = await _compiler.CompileSourceAsync(Path.Combine(TestContext.CurrentContext.TestDirectory, "test.dll"), "gfdgdfgfdg");
var result = await _compiler.CompileSourceAsync(Path.Combine(TestContext.CurrentContext.TestDirectory, "test02.dll"), "gfdgdfgfdg");
Assert.AreEqual(1, result.OutputRows.Count);
Assert.IsFalse(result.Success);
}
Expand All @@ -50,5 +52,18 @@ public async Task CompileSourceInMemoryAsync_WhenCompilingSourceWithError_Should
Assert.AreEqual(1, result.OutputRows.Count);
Assert.IsFalse(result.Success);
}

[Test]
public async Task CompileSourceToStreamAsync_WhenCompilingSource_ShouldHaveAProperlyLoadingAssembly()
{
using (var ms = new MemoryStream())
{
var result = await _compiler.CompileSourceToStreamAsync("test", ms, new ClassBuilder("TestClass", "Test").Build().NormalizeWhitespace().ToString());
var assemblyBytes = ms.ToArray();
Assert.IsTrue(result.Success);
Assert.NotZero(assemblyBytes.Length);
Assembly.Load(assemblyBytes);
}
}
}
}
73 changes: 73 additions & 0 deletions src/Testura.Code/Compilations/AdvancedCompiler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Testura.Code.Compilations
{
public static class AdvancedCompiler
{
public static CompileResult CompileSourceStrings(Stream outputStream, CompilerSettings settings = default, params string[] sources)
{
if (settings == null)
{
settings = new CompilerSettings();
}

// add references
var metaDataRef = new List<MetadataReference>();
foreach (var s in settings.ReferenceAssemblyStreams)
{
metaDataRef.Add(MetadataReference.CreateFromStream(s));
}

foreach (var s in settings.ReferenceAssemblyFilePaths)
{
metaDataRef.Add(MetadataReference.CreateFromFile(s));
}

var parseOptions = new CSharpParseOptions(settings.LanguageVersion);
var parsedSyntaxTrees = new SyntaxTree[sources.Length];
for (int i = 0; i < sources.Length; i++)
{
var stringText = SourceText.From(sources[i], Encoding.UTF8);
parsedSyntaxTrees[i] = SyntaxFactory.ParseSyntaxTree(stringText, parseOptions);
}

var defaultCompilationOptions = new CSharpCompilationOptions(settings.OutputKind)
.WithPlatform(settings.Platform)
.WithOverflowChecks(settings.EnableOverflowChecks)
.WithOptimizationLevel(settings.OptimizationLevel)
.WithUsings(settings.Usings);

var compilation = CSharpCompilation.Create(
settings.AssemblyName,
parsedSyntaxTrees,
metaDataRef,
defaultCompilationOptions);

var result = compilation.Emit(outputStream);
var outputRows = ConvertDiagnosticsToOutputRows(result.Diagnostics);
return new CompileResult(result.Success, outputRows);
}

private static IList<OutputRow> ConvertDiagnosticsToOutputRows(IEnumerable<Diagnostic> diagnostics)
{
var outputRows = new List<OutputRow>();
foreach (var diagnostic in diagnostics)
{
if (diagnostic.Severity < DiagnosticSeverity.Error)
{
continue;
}

outputRows.Add(new OutputRow { Description = diagnostic.GetMessage(), Severity = diagnostic.Severity.ToString(), ClassName = diagnostic.Id });
}

return outputRows;
}
}
}
8 changes: 1 addition & 7 deletions src/Testura.Code/Compilations/CompileResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,12 @@ public class CompileResult
/// <param name="pathToDll">Path to the dll.</param>
/// <param name="success">If the compilation was successful or not.</param>
/// <param name="outputRows">Output from the compilation.</param>
public CompileResult(string pathToDll, bool success, IList<OutputRow> outputRows)
public CompileResult(bool success, IList<OutputRow> outputRows)
{
PathToDll = pathToDll;
Success = success;
OutputRows = outputRows;
}

/// <summary>
/// Gets or sets path to the generated dlls.
/// </summary>
public string PathToDll { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the test are successful.
/// </summary>
Expand Down
37 changes: 23 additions & 14 deletions src/Testura.Code/Compilations/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ public async Task<CompileResult> CompileFilesAsync(string outputPath, params str
source[n] = File.ReadAllText(pathsToCsFiles[n]);
}

return await CompileSourceAsync(outputPath, source);
using (var fs = File.OpenWrite(outputPath))
{
return await CompileSourceToStreamAsync("compilationResult", fs, source);
}
}

/// <summary>
Expand All @@ -89,6 +92,22 @@ public async Task<CompileResult> CompileFilesAsync(string outputPath, params str
/// <param name="source">Source string to compile.</param>
/// <returns>The result of the compilation.</returns>
public async Task<CompileResult> CompileSourceAsync(string outputPath, params string[] source)
{
var stream = outputPath != null ? (Stream)File.OpenWrite(outputPath) : new MemoryStream();
using (stream)
{
return await CompileSourceToStreamAsync("temporary", stream, source);
}
}

/// <summary>
/// Compile code from a string source into a dll.
/// </summary>
/// <param name="assemblyName">Name for the assembly.</param>
/// <param name="stream">Stream to write the assembly to.</param>
/// <param name="source">Source string to compile.</param>
/// <returns>The result of the compilation.</returns>
public async Task<CompileResult> CompileSourceToStreamAsync(string assemblyName, Stream stream, params string[] source)
{
if (source.Length == 0)
{
Expand All @@ -111,26 +130,16 @@ public async Task<CompileResult> CompileSourceAsync(string outputPath, params st
.WithUsings(_defaultNamespaces);

var compilation = CSharpCompilation.Create(
string.IsNullOrEmpty(outputPath) ? "temporary" : Path.GetFileName(outputPath),
assemblyName,
parsedSyntaxTrees,
ConvertReferenceToMetaDataReferfence(),
defaultCompilationOptions);

EmitResult result;

if (string.IsNullOrEmpty(outputPath))
{
using (var memoryStream = new MemoryStream())
{
result = compilation.Emit(memoryStream);
}
}
else
{
result = compilation.Emit(outputPath);
}
result = compilation.Emit(stream);
var outputRows = ConvertDiagnosticsToOutputRows(result.Diagnostics);
return new CompileResult(outputPath, result.Success, outputRows);
return new CompileResult(result.Success, outputRows);
});
}

Expand Down
61 changes: 61 additions & 0 deletions src/Testura.Code/Compilations/CompilerSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Testura.Code.Compilations
{
public class CompilerSettings
{
public CompilerSettings()
{
Platform = Platform.AnyCpu;
OutputKind = OutputKind.DynamicallyLinkedLibrary;
EnableOverflowChecks = false;
OptimizationLevel = OptimizationLevel.Release;
AssemblyName = "Temporary";
LanguageVersion = LanguageVersion.Latest;
var loadadAssemblies = AppDomain.CurrentDomain.GetAssemblies();
var defaultAssemblyNames = new[] { "mscorlib", "System", "System.Core" };

// make sure above default assemblies are available to load from file, if they aren't (e.g. we run on WASM) don't load them
var defaultAssemblies = loadadAssemblies.Where(x =>
defaultAssemblyNames.Contains(x.GetName().Name) &&
!string.IsNullOrEmpty(x.Location) &&
File.Exists(x.Location)).Select(x => x.Location);
ReferenceAssemblyFilePaths = new List<string>(defaultAssemblies);

ReferenceAssemblyStreams = new List<Stream>();
Usings = new List<string>
{
"System",
"System.IO",
"System.Net",
"System.Linq",
"System.Text",
"System.Text.RegularExpressions",
"System.Collections.Generic",
};
}

public Platform Platform { get; set; }

public OutputKind OutputKind { get; set; }

public bool EnableOverflowChecks { get; set; }

public OptimizationLevel OptimizationLevel { get; set; }

public string AssemblyName { get; set; }

public LanguageVersion LanguageVersion { get; set; }

public List<string> ReferenceAssemblyFilePaths { get; set; }

public List<Stream> ReferenceAssemblyStreams { get; set; }

public List<string> Usings { get; set; }
}
}