Closed ranuka2 closed 6 years ago
Yes. Create a CSharpCompilation object (or it's VB counterpart), passing in the source as a string and adequate assembly references, then call Emit.
https://joshvarty.wordpress.com/2016/01/16/learn-roslyn-now-part-16-the-emit-api/ https://stackoverflow.com/questions/24658381/roslyn-csharpcompilation
I have creates the following sample application based on provided references. However it failed to compile classes with partial classes, also some files failed with inaccessible due to its protection level, so that following code doesn't guarantee successful build for complex projects. can you review this and give the feedback.
//Microsoft.CodeAnalysis 1.0 Required
class Program
{
static List<string> passedFiles = new List<string>();
static List<string> failedFiles = new List<string>();
static void Main(string[] args)
{
try
{
//I'm using DotNetNuke source for testing the comilation (https://github.com/dnnsoftware/Dnn.Platform)
string projPath = @"C:\Sln\Dnn.Platform-development\DNN Platform\Library\DotNetNuke.Library.csproj";
string outFolder = @"C:\tempout\1";
string fileName = @"C:\Sln\Dnn.Platform-development\DNN Platform\Library\Entities\Content\ContentType.cs";
CompileProject(projPath, outFolder, fileName).Wait();
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
async static Task<string> CompileProject(string projectFilePath, string outputFolderPath, string classFileName)
{
using (var workspace = MSBuildWorkspace.Create())
{
var project = workspace.OpenProjectAsync(projectFilePath).Result;
await Emit(project, outputFolderPath, classFileName);
return Path.GetFileName(project.OutputFilePath);
}
}
async static Task Emit(Project project, string outputFolderPath, string classFileName)
{
Directory.CreateDirectory(outputFolderPath);
var compilation = await project.GetCompilationAsync();
var outputFilePath = Path.Combine(outputFolderPath, Path.GetFileName(project.OutputFilePath));
var pdbFilePath = Path.ChangeExtension(outputFilePath, "pdb");
//1. Compile the project and and emit output as memory stream
using (var projDLLStream = new MemoryStream())
{
var compilationStatus = compilation.Emit(projDLLStream);
if (!compilationStatus.Success)
{
Console.WriteLine("Project compilation failed.");
}
else
{
Console.WriteLine("Project compilation successful.");
}
projDLLStream.Seek(0, SeekOrigin.Begin);
//2. Create a metadata refarance from memstream
PortableExecutableReference projectMetadata = MetadataReference.CreateFromStream(projDLLStream);
//Emit all files in the project to separate dll
foreach (SyntaxTree syntaxTree in compilation.SyntaxTrees)
{
var currentSyntaxTree = syntaxTree;
if (currentSyntaxTree == null)
{
Console.WriteLine("Syntax tree not found.");
return;
}
var root = (CompilationUnitSyntax)currentSyntaxTree.GetRoot();
currentSyntaxTree = CSharpSyntaxTree.Create(root);
//3. Create metadata refarance with project metadata
var metadataReferences = compilation.References.ToList();
metadataReferences.Add(projectMetadata);
CompileFile(currentSyntaxTree, metadataReferences, outputFilePath, pdbFilePath, syntaxTree.FilePath);
}
//Print compilation result
Console.WriteLine("Passed");
foreach (var item in passedFiles)
{
Console.WriteLine(item);
}
Console.WriteLine("Failed");
foreach (var item in failedFiles)
{
Console.WriteLine(item);
}
}
}
private static void CompileFile(SyntaxTree fileSyntaxTree, IEnumerable<MetadataReference> metadataReferences, string dllPath, string pdbPath, string fileName)
{
var syntaxTreeList = new List<SyntaxTree>();
syntaxTreeList.Add(fileSyntaxTree);
Compilation compilation = CreateLibraryCompilation("MyAssembly", metadataReferences, syntaxTreeList);
var outputFilePath = Path.Combine(@"c:\DllFolder", Path.GetFileNameWithoutExtension(fileName) + ".dll");
var pdbFilePath = Path.Combine(@"c:\DllFolder", Path.GetFileNameWithoutExtension(fileName) + ".pdb");
//4. Compile the syntax tree of the file with project metadata referances
var emitResult = compilation.Emit(outputFilePath, pdbPath: pdbFilePath);
if (!emitResult.Success)
{
var errors = emitResult.Diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error);
foreach (var error in errors)
{
failedFiles.Add("File :" + fileName + " Error:" + error.ToString());
}
}
else
{
passedFiles.Add("File : " + fileName + "compiled sucessfully.");
}
}
private static Compilation CreateLibraryCompilation(string assemblyName, IEnumerable<MetadataReference> metadataReferences, IEnumerable<SyntaxTree> syntaxTree)
{
var options = new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Debug
);
return CSharpCompilation.Create(assemblyName, options: options, references: metadataReferences, syntaxTrees: syntaxTree);
}
}
If the cs
file that you want to compile isn't a valid/complete program by itself (without other source files), there is nothing you can do to compile it by itself.
In other words, there is no way to do that for some random source file in a project. Sorry I misunderstood your original question.
@ranuka2 I didn't hear back from you so I'll close the issue. Feel free to re-open if this is still an issue or you think there is a compiler bug. Thanks
Thank you @jcouv for the information.
I need to emit .dll and .pdb for a single .cs/.vb file in a project. Is this possible with Roslyn?