From 121ec3da6373134e7cfc200409b3dd8113bd89ba Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Tue, 23 Jan 2024 12:45:40 -0500 Subject: [PATCH 1/3] Respect DOTNET_ROOT env var when locating SDK Fixes #168 --- .../RuntimeInfo.cs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs index 6449ad7..f21dcf0 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 { @@ -112,18 +110,27 @@ 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); From 590808c401918b453b8d331a5460d1196a78b51f Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Thu, 25 Jan 2024 23:51:33 -0500 Subject: [PATCH 2/3] Don't require csc.dll when using in-process compiler --- .../CSharpLangVersionHelper.cs | 2 +- .../CscCodeCompiler.cs | 3 +++ .../RuntimeInfo.cs | 22 +++++++++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/CSharpLangVersionHelper.cs index c1f755b..bca005f 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.CscMaxLangVersion)}"; } //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 f21dcf0..07d7385 100644 --- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs +++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs @@ -46,10 +46,17 @@ class RuntimeInfo public RuntimeKind Kind { get; private set; } public string Error { get; private set; } public string RuntimeDir { get; private set; } + + // may be null, as this is not a problem when the optional in-process compiler is used public string CscPath { get; private set; } + + /// + /// Maximum C# language version supported by C# compiler in . + /// + public CSharpLangVersion CscMaxLangVersion { get; private set; } + public bool IsValid => Error == null; public Version Version { get; private set; } - public CSharpLangVersion MaxSupportedLangVersion { get; private set; } public string RefAssembliesDir { get; private set; } public string RuntimeFacadesDir { get; internal set; } @@ -85,7 +92,7 @@ static RuntimeInfo GetMonoRuntime () // we don't really care about the version if it's not .net core Version = new Version ("4.7.2"), //if mono has csc at all, we know it at least supports 6.0 - MaxSupportedLangVersion = CSharpLangVersion.v6_0 + CscMaxLangVersion = CSharpLangVersion.v6_0 }; } @@ -102,7 +109,7 @@ static RuntimeInfo GetNetFrameworkRuntime () RuntimeFacadesDir = 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 + CscMaxLangVersion = CSharpLangVersion.v5_0 }; } @@ -140,11 +147,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 @@ -155,7 +163,7 @@ 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, RefAssembliesDir = refAssembliesDir, CscPath = MakeCscPath (sdkDir), CscMaxLangVersion = maxCSharpVersion, Version = hostVersion }; } static string FindHighestVersionedDirectory (string parentFolder, Func validate, out SemVersion bestVersion) From 56f20c81e90f837a494d775388a48de55045a491 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Fri, 26 Jan 2024 00:20:42 -0500 Subject: [PATCH 3/3] Separate csc and runtime C# versions --- .../RoslynCodeCompiler.cs | 3 +- .../CSharpLangVersionHelper.cs | 2 +- .../RuntimeInfo.cs | 91 ++++++++++++++----- 3 files changed, 68 insertions(+), 28 deletions(-) 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 bca005f..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.CscMaxLangVersion)}"; + 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/RuntimeInfo.cs b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs index 07d7385..3a00fac 100644 --- a/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs +++ b/Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs @@ -39,27 +39,51 @@ enum RuntimeKind class RuntimeInfo { - RuntimeInfo (RuntimeKind kind) => Kind = kind; + RuntimeInfo (RuntimeKind kind, string error) + { + Kind = kind; + Error = error; + } - static RuntimeInfo FromError (RuntimeKind kind, string error) => new (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; + } - public RuntimeKind Kind { get; private set; } - public string Error { get; private set; } - public string RuntimeDir { get; private set; } + 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; private set; } + public string CscPath { get; } /// /// Maximum C# language version supported by C# compiler in . /// - public CSharpLangVersion CscMaxLangVersion { get; private set; } + 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 bool IsValid => Error == null; - public Version Version { 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 () { @@ -85,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 - CscMaxLangVersion = CSharpLangVersion.v6_0 - }; + cscMaxLangVersion: CSharpLangVersion.v6_0, + runtimeLangVersion: CSharpLangVersion.v5_0 + ); } static RuntimeInfo GetNetFrameworkRuntime () @@ -103,14 +130,17 @@ 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"), - CscMaxLangVersion = 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 () @@ -163,7 +193,18 @@ static RuntimeInfo GetDotNetCoreSdk () out _ ); - return new RuntimeInfo (RuntimeKind.NetCore) { RuntimeDir = runtimeDir, RefAssembliesDir = refAssembliesDir, CscPath = MakeCscPath (sdkDir), CscMaxLangVersion = 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)