Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect DOTNET_ROOT env var #179

Merged
merged 3 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Mono.TextTemplating.Roslyn/RoslynCodeCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SyntaxTree> ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
126 changes: 91 additions & 35 deletions Mono.TextTemplating/Mono.TextTemplating.CodeCompilation/RuntimeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@

using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace Mono.TextTemplating.CodeCompilation
{
Expand All @@ -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; }

/// <summary>
/// Maximum C# language version supported by C# compiler in <see cref="CscPath"/>.
/// </summary>
public CSharpLangVersion CscMaxLangVersion { get; }

/// <summary>
/// The C# version fully supported by the runtime, which is the default when targeting this runtime.
/// </summary>
/// <remarks>
/// Using newer C# language versions is possible but some features may not work if they depend on runtime changes.
/// </remarks>
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 ()
{
Expand All @@ -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 ()
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -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<string, bool> validate, out SemVersion bestVersion)
Expand Down
Loading