Compiling a Dll Programmatically using CSharpCodeProvider

For bdUnit, I needed to output the generated source code as a compiled dll. I was dreading this but, when I bit the bullet, the code turned out to be pretty clean and simple:


using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Text;
using bdUnit.Core;
using Core.Enum;
using Microsoft.CSharp;

namespace bdUnit.Core
{
public class DllBuilder
{
private readonly string[] References =
new[] {
"Rhino.Mocks", "nunit.core", "nunit.core.interfaces", "nunit.framework",
"xunit", "MbUnit.Framework", "StructureMap", "StructureMap.AutoMocking"
};

public string CompileDll(string folderPath, UnitTestFrameworkEnum currentFramework)
{
CodeDomProvider compiler =
new CSharpCodeProvider(new Dictionary<string, string>{{"CompilerVersion","v3.5"}});

var parameters = new CompilerParameters
{
GenerateInMemory = false,
GenerateExecutable = false,
IncludeDebugInformation = true,
OutputAssembly = string.Format("bdUnit_{0}.dll", currentFramework),
ReferencedAssemblies = {"System.dll"}
};

foreach (var reference in References)
{
parameters.ReferencedAssemblies.Add(AppDomain.CurrentDomain.BaseDirectory
+ string.Format("\\{0}.dll", reference));
}
var source = GetSource(folderPath, currentFramework);
var results = compiler.CompileAssemblyFromSource(parameters, source);

if (results.Errors.HasErrors)
{
var errorText = new StringBuilder();
var count = results.Errors.Count;
for (var i = 0; i < count; i++)
{
errorText.AppendLine("Compilation Error: " + results.Errors[i].ErrorText);
}
return errorText.ToString();
}
return "Succesfully Generated Dll";
}

private static string[] GetSource(string folderPath, UnitTestFrameworkEnum framework)
{
Directory.SetCurrentDirectory(folderPath);
var files = Directory.GetFiles(folderPath, "*.input");
var source = new string[files.Length];
for (var i = 0; i < files.Length; i++)
{
var paths = new Dictionary<string, string>
{
{"input", string.Format("{0}", files[i])},
{"grammar", Settings.GrammarPath}
};
var parser = new Parser(paths);
source[i] = parser.Parse(framework);
}
return source;
}
}
}

I setup my compile parameters with the framework and references that I need. Note that these references are only present as includes and the corresponding dlls need to be present in the bin folder where the dynamically compiled dll is used. There may also be issue at runtime if different versions of such dependancies are referenced in the dll and present in the bin folder.

I use the GetSource method to parse the inputs/user stories and construct an array of source code, each member representing a .cs file. I then pass this as an argument for CompileAssemblyFromSource and report any errors that prevent the compilation from completing. The errors are informative and cover missing/misnamed references as well as build errors in the source code itself.

In the next post I will run through further developments to the MGrammar used in bdUnit including support for new input statements and steps I have taken to refactor the grammar itself.


James Lynch

2 comments:

gdusing said...

I tried using the GetSource method to parse the inputs anduser stories also to construct an array of the source code and I got this message "#error 213". I'm not sure what it means.

James Lynch said...

There are dependancies on other files from bdUnit.Core which you will need to include. You can find them at:
http://github.com/lynchjames/bdUnit/tree/master/Core