using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.AspNetCore.Hosting; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.Hosting; using DependencyContextCompilationOptions = Microsoft.Extensions.DependencyModel.CompilationOptions; namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; /* * This is a partial Clone'n'Own of microsofts CSharpCompiler, this is just the parts relevant for getting the CompilationOptions * Essentially, what this does is that it looks at the compilation options of the Dotnet project and "copies" that * https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Razor.RuntimeCompilation/src/CSharpCompiler.cs */ internal class CompilationOptionsProvider { private readonly IWebHostEnvironment _hostingEnvironment; private CSharpParseOptions? _parseOptions; private CSharpCompilationOptions? _compilationOptions; private bool _emitPdb; private EmitOptions? _emitOptions; private bool _optionsInitialized; public CompilationOptionsProvider(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment; public virtual CSharpParseOptions ParseOptions { get { EnsureOptions(); return _parseOptions; } } public virtual CSharpCompilationOptions CSharpCompilationOptions { get { EnsureOptions(); return _compilationOptions; } } public virtual bool EmitPdb { get { EnsureOptions(); return _emitPdb; } } public virtual EmitOptions EmitOptions { get { EnsureOptions(); return _emitOptions; } } [MemberNotNull(nameof(_emitOptions), nameof(_parseOptions), nameof(_compilationOptions))] private void EnsureOptions() { if (!_optionsInitialized) { var dependencyContextOptions = GetDependencyContextCompilationOptions(); _parseOptions = GetParseOptions(_hostingEnvironment, dependencyContextOptions); _compilationOptions = GetCompilationOptions(_hostingEnvironment, dependencyContextOptions); _emitOptions = GetEmitOptions(dependencyContextOptions); _optionsInitialized = true; } Debug.Assert(_parseOptions is not null); Debug.Assert(_compilationOptions is not null); Debug.Assert(_emitOptions is not null); } private DependencyContextCompilationOptions GetDependencyContextCompilationOptions() { if (!string.IsNullOrEmpty(_hostingEnvironment.ApplicationName)) { var applicationAssembly = Assembly.Load(new AssemblyName(_hostingEnvironment.ApplicationName)); var dependencyContext = DependencyContext.Load(applicationAssembly); if (dependencyContext?.CompilationOptions != null) { return dependencyContext.CompilationOptions; } } return DependencyContextCompilationOptions.Default; } private EmitOptions GetEmitOptions(DependencyContextCompilationOptions dependencyContextOptions) { // Assume we're always producing pdbs unless DebugType = none _emitPdb = true; DebugInformationFormat debugInformationFormat; if (string.IsNullOrEmpty(dependencyContextOptions.DebugType)) { debugInformationFormat = DebugInformationFormat.PortablePdb; } else { // Based on https://github.com/dotnet/roslyn/blob/1d28ff9ba248b332de3c84d23194a1d7bde07e4d/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs#L624-L640 switch (dependencyContextOptions.DebugType.ToLowerInvariant()) { case "none": // There isn't a way to represent none in DebugInformationFormat. // We'll set EmitPdb to false and let callers handle it by setting a null pdb-stream. _emitPdb = false; return new EmitOptions(); case "portable": debugInformationFormat = DebugInformationFormat.PortablePdb; break; case "embedded": // Roslyn does not expose enough public APIs to produce a binary with embedded pdbs. // We'll produce PortablePdb instead to continue providing a reasonable user experience. debugInformationFormat = DebugInformationFormat.PortablePdb; break; case "full": case "pdbonly": debugInformationFormat = DebugInformationFormat.PortablePdb; break; default: throw new InvalidOperationException(); } } var emitOptions = new EmitOptions(debugInformationFormat: debugInformationFormat); return emitOptions; } private static CSharpCompilationOptions GetCompilationOptions( IWebHostEnvironment hostingEnvironment, DependencyContextCompilationOptions dependencyContextOptions) { var csharpCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); // Disable 1702 until roslyn turns this off by default csharpCompilationOptions = csharpCompilationOptions.WithSpecificDiagnosticOptions( new Dictionary { {"CS1701", ReportDiagnostic.Suppress}, // Binding redirects {"CS1702", ReportDiagnostic.Suppress}, {"CS1705", ReportDiagnostic.Suppress} }); if (dependencyContextOptions.AllowUnsafe.HasValue) { csharpCompilationOptions = csharpCompilationOptions.WithAllowUnsafe( dependencyContextOptions.AllowUnsafe.Value); } OptimizationLevel optimizationLevel; if (dependencyContextOptions.Optimize.HasValue) { optimizationLevel = dependencyContextOptions.Optimize.Value ? OptimizationLevel.Release : OptimizationLevel.Debug; } else { optimizationLevel = hostingEnvironment.IsDevelopment() ? OptimizationLevel.Debug : OptimizationLevel.Release; } csharpCompilationOptions = csharpCompilationOptions.WithOptimizationLevel(optimizationLevel); if (dependencyContextOptions.WarningsAsErrors.HasValue) { var reportDiagnostic = dependencyContextOptions.WarningsAsErrors.Value ? ReportDiagnostic.Error : ReportDiagnostic.Default; csharpCompilationOptions = csharpCompilationOptions.WithGeneralDiagnosticOption(reportDiagnostic); } return csharpCompilationOptions; } private static CSharpParseOptions GetParseOptions( IWebHostEnvironment hostingEnvironment, DependencyContextCompilationOptions dependencyContextOptions) { var configurationSymbol = hostingEnvironment.IsDevelopment() ? "DEBUG" : "RELEASE"; var defines = dependencyContextOptions.Defines.Concat(new[] { configurationSymbol }).Where(define => define != null); var parseOptions = new CSharpParseOptions(preprocessorSymbols: (IEnumerable)defines); if (string.IsNullOrEmpty(dependencyContextOptions.LanguageVersion)) { parseOptions = parseOptions.WithLanguageVersion(LanguageVersion.Latest); } else if (LanguageVersionFacts.TryParse(dependencyContextOptions.LanguageVersion, out var languageVersion)) { parseOptions = parseOptions.WithLanguageVersion(languageVersion); } else { Debug.Fail($"LanguageVersion {languageVersion} specified in the deps file could not be parsed."); } return parseOptions; } }