2022-10-07 10:42:32 +02:00
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 < string , ReportDiagnostic >
{
{ "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 < string > ) defines ) ;
if ( string . IsNullOrEmpty ( dependencyContextOptions . LanguageVersion ) )
{
2023-02-28 09:57:17 +01:00
parseOptions = parseOptions . WithLanguageVersion ( LanguageVersion . Latest ) ;
2022-10-07 10:42:32 +02:00
}
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 ;
}
}