diff --git a/Mono.TextTemplating.Roslyn/RoslynCodeCompiler.cs b/Mono.TextTemplating.Roslyn/RoslynCodeCompiler.cs index b3323d5..bc888a3 100644 --- a/Mono.TextTemplating.Roslyn/RoslynCodeCompiler.cs +++ b/Mono.TextTemplating.Roslyn/RoslynCodeCompiler.cs @@ -71,14 +71,13 @@ CodeCompilerResult CompileFileInternal ( // features depend on new APIs that aren't available on the current runtime. // If the runtime is an unknown version, its MaxSupportedLangVersion will default // to "latest" so new runtime versions will work before we explicitly add support for them. - if (LanguageVersionFacts.TryParse (CSharpLangVersionHelper.ToString (runtime.MaxSupportedLangVersion), out var runtimeSupportedLangVersion)) { + if (LanguageVersionFacts.TryParse (CSharpLangVersionHelper.ToString (runtime.RuntimeLangVersion), out var runtimeSupportedLangVersion)) { parseOptions = parseOptions.WithLanguageVersion (runtimeSupportedLangVersion); } else { // if Roslyn did not recognize the runtime's default lang version, it's newer than // this version of Roslyn supports, so default to the latest supported version parseOptions = parseOptions.WithLanguageVersion (LanguageVersion.Latest); } - } var syntaxTrees = new List (); diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs index c1f755b..347233c 100644 --- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs +++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs @@ -42,7 +42,7 @@ public static bool IsLangVersionArg (string arg) => // If we were unable to determine the supported language version for the runtime, // its MaxSupportedLangVersion will default to "Latest" so its language features // are available before we add a language version mapping for that runtime version. - return $"-langversion:{ToString (runtime.MaxSupportedLangVersion)}"; + return $"-langversion:{ToString (runtime.RuntimeLangVersion)}"; } //https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CscCodeCompiler.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CscCodeCompiler.cs index 00858b2..4e58b51 100644 --- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CscCodeCompiler.cs +++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CscCodeCompiler.cs @@ -46,6 +46,9 @@ class CscCodeCompiler : CodeCompiler public CscCodeCompiler (RuntimeInfo runtime) { this.runtime = runtime; + if (runtime.CscPath is null) { + throw new TemplatingEngineException ("Cannot find C# compiler"); + } } static StreamWriter CreateTempTextFile (string extension, out string path) diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs index 6449ad7..3a00fac 100644 --- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs +++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs @@ -26,9 +26,7 @@ using System; using System.IO; -using System.Linq; using System.Runtime.InteropServices; -using System.Threading; namespace Mono.TextTemplating.CodeCompilation { @@ -41,20 +39,51 @@ enum RuntimeKind class RuntimeInfo { - RuntimeInfo (RuntimeKind kind) => Kind = kind; + RuntimeInfo (RuntimeKind kind, string error) + { + Kind = kind; + Error = error; + } + + RuntimeInfo (RuntimeKind kind, string runtimeDir, Version runtimeVersion, string refAssembliesDir, string runtimeFacadesDir, string cscPath, CSharpLangVersion cscMaxLangVersion, CSharpLangVersion runtimeLangVersion) + { + Kind = kind; + RuntimeVersion = runtimeVersion; + RuntimeDir = runtimeDir; + RefAssembliesDir = refAssembliesDir; + RuntimeFacadesDir = runtimeFacadesDir; + CscPath = cscPath; + CscMaxLangVersion = cscMaxLangVersion; + RuntimeLangVersion = runtimeLangVersion; + } - static RuntimeInfo FromError (RuntimeKind kind, string error) => new (kind) { Error = error }; + static RuntimeInfo FromError (RuntimeKind kind, string error) => new (kind, error); + + public RuntimeKind Kind { get; } + public string Error { get; } + public string RuntimeDir { get; } + + // may be null, as this is not a problem when the optional in-process compiler is used + public string CscPath { get; } + + /// + /// Maximum C# language version supported by C# compiler in . + /// + public CSharpLangVersion CscMaxLangVersion { get; } + + /// + /// The C# version fully supported by the runtime, which is the default when targeting this runtime. + /// + /// + /// Using newer C# language versions is possible but some features may not work if they depend on runtime changes. + /// + public CSharpLangVersion RuntimeLangVersion { get; } - public RuntimeKind Kind { get; private set; } - public string Error { get; private set; } - public string RuntimeDir { get; private set; } - public string CscPath { get; private set; } public bool IsValid => Error == null; - public Version Version { get; private set; } - public CSharpLangVersion MaxSupportedLangVersion { get; private set; } + public Version RuntimeVersion { get; } - public string RefAssembliesDir { get; private set; } - public string RuntimeFacadesDir { get; internal set; } + public string RefAssembliesDir { get; } + public string RuntimeFacadesDir { get; } public static RuntimeInfo GetRuntime () { @@ -80,15 +109,18 @@ static RuntimeInfo GetMonoRuntime () return FromError (RuntimeKind.Mono, "Could not find csc in host Mono installation" ); } - return new RuntimeInfo (RuntimeKind.Mono) { - CscPath = csc, - RuntimeDir = runtimeDir, - RuntimeFacadesDir = Path.Combine (runtimeDir, "Facades"), + return new RuntimeInfo ( + RuntimeKind.Mono, + runtimeDir: runtimeDir, // we don't really care about the version if it's not .net core - Version = new Version ("4.7.2"), + runtimeVersion: new Version ("4.7.2"), + refAssembliesDir: null, + runtimeFacadesDir: Path.Combine (runtimeDir, "Facades"), + cscPath: csc, //if mono has csc at all, we know it at least supports 6.0 - MaxSupportedLangVersion = CSharpLangVersion.v6_0 - }; + cscMaxLangVersion: CSharpLangVersion.v6_0, + runtimeLangVersion: CSharpLangVersion.v5_0 + ); } static RuntimeInfo GetNetFrameworkRuntime () @@ -98,32 +130,44 @@ static RuntimeInfo GetNetFrameworkRuntime () if (!File.Exists (csc)) { return FromError (RuntimeKind.NetFramework, "Could not find csc in host .NET Framework installation"); } - return new RuntimeInfo (RuntimeKind.NetFramework) { - CscPath = csc, - RuntimeDir = runtimeDir, - RuntimeFacadesDir = runtimeDir, + return new RuntimeInfo ( + RuntimeKind.NetFramework, + runtimeDir: runtimeDir, // we don't really care about the version if it's not .net core - Version = new Version ("4.7.2"), - MaxSupportedLangVersion = CSharpLangVersion.v5_0 - }; + runtimeVersion: new Version ("4.7.2"), + refAssembliesDir: null, + runtimeFacadesDir: runtimeDir, + cscPath: csc, + cscMaxLangVersion: CSharpLangVersion.v5_0, + runtimeLangVersion: CSharpLangVersion.v5_0 + ); } static RuntimeInfo GetDotNetCoreSdk () { static bool DotnetRootIsValid (string root) => !string.IsNullOrEmpty (root) && (File.Exists (Path.Combine (root, "dotnet")) || File.Exists (Path.Combine (root, "dotnet.exe"))); - // this should get us something like /usr/local/share/dotnet/shared/Microsoft.NETCore.App/5.0.0 + // the runtime dir is used when probing for DOTNET_ROOT + // and as a fallback in case we cannot locate reference assemblies var runtimeDir = Path.GetDirectoryName (typeof (object).Assembly.Location); - var dotnetRoot = Path.GetDirectoryName (Path.GetDirectoryName (Path.GetDirectoryName (runtimeDir))); + + var dotnetRoot = Environment.GetEnvironmentVariable ("DOTNET_ROOT"); if (!DotnetRootIsValid (dotnetRoot)) { - // this may happen on single file deployments - return FromError (RuntimeKind.NetCore, "Not a valid .NET Core host"); + // this will work if runtimeDir is $DOTNET_ROOT/shared/Microsoft.NETCore.App/5.0.0 + dotnetRoot = Path.GetDirectoryName (Path.GetDirectoryName (Path.GetDirectoryName (runtimeDir))); + + if (!DotnetRootIsValid (dotnetRoot)) { + return FromError (RuntimeKind.NetCore, "Could not locate .NET root directory from running app. It can be set explicitly via the `DOTNET_ROOT` environment variable."); + } } var hostVersion = Environment.Version; - if (hostVersion.Major < 5) + + // fallback for .NET Core < 3.1, which always returned 4.0.x + if (hostVersion.Major == 4) { + // this will work if runtimeDir is $DOTNET_ROOT/shared/Microsoft.NETCore.App/5.0.0 var versionPathComponent = Path.GetFileName (runtimeDir); if (SemVersion.TryParse (versionPathComponent, out var hostSemVersion)) { hostVersion = new Version (hostSemVersion.Major, hostSemVersion.Minor, hostSemVersion.Patch); @@ -133,11 +177,12 @@ static RuntimeInfo GetDotNetCoreSdk () } } + // find the highest available C# compiler. we don't load it in process, so its runtime doesn't matter. static string MakeCscPath (string d) => Path.Combine (d, "Roslyn", "bincore", "csc.dll"); var sdkDir = FindHighestVersionedDirectory (Path.Combine (dotnetRoot, "sdk"), d => File.Exists (MakeCscPath (d)), out var sdkVersion); - if (sdkDir == null) { - return FromError (RuntimeKind.NetCore, "Could not find csc.dll in any .NET Core SDK"); - } + + // it's okay if cscPath is null as we may be using the in-process compiler + string cscPath = sdkDir == null ? null : MakeCscPath (sdkDir); var maxCSharpVersion = CSharpLangVersionHelper.FromNetCoreSdkVersion (sdkVersion); // it's ok if this is null, we may be running on an older SDK that didn't support packs @@ -148,7 +193,18 @@ static RuntimeInfo GetDotNetCoreSdk () out _ ); - return new RuntimeInfo (RuntimeKind.NetCore) { RuntimeDir = runtimeDir, RefAssembliesDir = refAssembliesDir, CscPath = MakeCscPath (sdkDir), MaxSupportedLangVersion = maxCSharpVersion, Version = hostVersion }; + + + return new RuntimeInfo ( + RuntimeKind.NetCore, + runtimeDir: runtimeDir, + runtimeVersion: hostVersion, + refAssembliesDir: refAssembliesDir, + runtimeFacadesDir: null, + cscPath: MakeCscPath (sdkDir), + cscMaxLangVersion: maxCSharpVersion, + runtimeLangVersion: CSharpLangVersionHelper.FromNetCoreSdkVersion (sdkVersion) + ); } static string FindHighestVersionedDirectory (string parentFolder, Func validate, out SemVersion bestVersion)