// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using Microsoft.AspNet.FileSystems; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; using Microsoft.Framework.Runtime; namespace Microsoft.AspNet.Mvc.Razor.Compilation { /// /// A type that uses Roslyn to compile C# content. /// public class RoslynCompilationService : ICompilationService { private readonly ConcurrentDictionary _metadataFileCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly ILibraryManager _libraryManager; private readonly IApplicationEnvironment _environment; private readonly IAssemblyLoaderEngine _loader; private readonly Lazy> _applicationReferences; /// /// Initalizes a new instance of the class. /// /// The environment for the executing application. /// The loader used to load compiled assemblies. /// The library manager that provides export and reference information. public RoslynCompilationService(IApplicationEnvironment environment, IAssemblyLoaderEngine loaderEngine, ILibraryManager libraryManager) { _environment = environment; _loader = loaderEngine; _libraryManager = libraryManager; _applicationReferences = new Lazy>(GetApplicationReferences); } /// public CompilationResult Compile(IFileInfo fileInfo, string compilationContent) { var sourceText = SourceText.From(compilationContent, Encoding.UTF8); var syntaxTrees = new[] { CSharpSyntaxTree.ParseText(sourceText, path: fileInfo.PhysicalPath) }; var targetFramework = _environment.TargetFramework; var references = _applicationReferences.Value; var assemblyName = Path.GetRandomFileName(); var compilation = CSharpCompilation.Create(assemblyName, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), syntaxTrees: syntaxTrees, references: references); using (var ms = new MemoryStream()) { using (var pdb = new MemoryStream()) { EmitResult result; if (PlatformHelper.IsMono) { result = compilation.Emit(ms, pdbStream: null); } else { result = compilation.Emit(ms, pdbStream: pdb); } if (!result.Success) { var formatter = new DiagnosticFormatter(); var messages = result.Diagnostics .Where(IsError) .Select(d => GetCompilationMessage(formatter, d)) .ToList(); return CompilationResult.Failed(fileInfo, compilationContent, messages); } Assembly assembly; ms.Seek(0, SeekOrigin.Begin); if (PlatformHelper.IsMono) { assembly = _loader.LoadStream(ms, pdbStream: null); } else { pdb.Seek(0, SeekOrigin.Begin); assembly = _loader.LoadStream(ms, pdb); } var type = assembly.GetExportedTypes() .First(); return UncachedCompilationResult.Successful(type, compilationContent); } } } private List GetApplicationReferences() { var references = new List(); var export = _libraryManager.GetAllExports(_environment.ApplicationName); foreach (var metadataReference in export.MetadataReferences) { // Taken from https://github.com/aspnet/KRuntime/blob/757ba9bfdf80bd6277e715d6375969a7f44370ee/src/... // Microsoft.Framework.Runtime.Roslyn/RoslynCompiler.cs#L164 // We don't want to take a dependency on the Roslyn bit directly since it pulls in more dependencies // than the view engine needs (Microsoft.Framework.Runtime) for example references.Add(ConvertMetadataReference(metadataReference)); } return references; } private MetadataReference ConvertMetadataReference(IMetadataReference metadataReference) { var roslynReference = metadataReference as IRoslynMetadataReference; if (roslynReference != null) { return roslynReference.MetadataReference; } var embeddedReference = metadataReference as IMetadataEmbeddedReference; if (embeddedReference != null) { return new MetadataImageReference(embeddedReference.Contents); } var fileMetadataReference = metadataReference as IMetadataFileReference; if (fileMetadataReference != null) { return CreateMetadataFileReference(fileMetadataReference.Path); } var projectReference = metadataReference as IMetadataProjectReference; if (projectReference != null) { using (var ms = new MemoryStream()) { projectReference.EmitReferenceAssembly(ms); ms.Seek(0, SeekOrigin.Begin); return new MetadataImageReference(ms); } } throw new NotSupportedException(); } private MetadataReference CreateMetadataFileReference(string path) { return _metadataFileCache.GetOrAdd(path, _ => { // TODO: What about access to the file system? We need to be able to // read files from anywhere on disk, not just under the web root using (var stream = File.OpenRead(path)) { return new MetadataImageReference(stream); } }); } private static CompilationMessage GetCompilationMessage(DiagnosticFormatter formatter, Diagnostic diagnostic) { return new CompilationMessage(formatter.Format(diagnostic)); } private static bool IsError(Diagnostic diagnostic) { return diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error; } } }