dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.12k stars 4.04k forks source link

Emit DLL for a single file in a project with Roslyn #23048

Closed ranuka2 closed 6 years ago

ranuka2 commented 7 years ago

I need to emit .dll and .pdb for a single .cs/.vb file in a project. Is this possible with Roslyn?

jcouv commented 7 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

ranuka2 commented 7 years ago

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);
        }
    }
jcouv commented 6 years ago

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.

jcouv commented 6 years ago

@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

ranuka2 commented 6 years ago

Thank you @jcouv for the information.