Skip to content

Commit

Permalink
Merge pull request #179 from mono/dotnet-env-var
Browse files Browse the repository at this point in the history
Respect DOTNET_ROOT env var
  • Loading branch information
mhutch authored Jan 27, 2024
2 parents 055fcdf + 56f20c8 commit 4f33c8d
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 38 deletions.
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

0 comments on commit 4f33c8d

Please sign in to comment.