From 4e2df6aa795de897347a2919ea6551387174aa9c Mon Sep 17 00:00:00 2001 From: jpetersen Date: Wed, 9 Oct 2024 11:43:12 -0700 Subject: [PATCH] In c# use the environment variable DOTNET_ROOT support to first check before using path which parallels modules/mono/editor/hostfxr_resolver.cpp's get_dotnet_root_from_env which does this check for c++ side. Additionally supports editor settings for environments where environment variables dont work: Fixes Issue: https://github.com/godotengine/godot/issues/97501 Implements Proposal: https://github.com/godotengine/godot-proposals/issues/1941 removed unused variable --- .../GodotTools/Build/BuildSystem.cs | 12 ++-- .../GodotTools/Build/DotNetFinder.cs | 52 +++++++++++++++- .../GodotTools/GodotTools/GodotSharpEditor.cs | 14 +++-- modules/mono/editor/hostfxr_resolver.cpp | 59 ++++++++++++++++++- 4 files changed, 123 insertions(+), 14 deletions(-) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index 5ae4dfa076d..25041743000 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -20,13 +20,13 @@ namespace GodotTools.Build private static Process LaunchBuild(BuildInfo buildInfo, Action? stdOutHandler, Action? stdErrHandler) { - string? dotnetPath = DotNetFinder.FindDotNetExe(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); + + string? dotnetPath = DotNetFinder.FindDotNetExe(editorSettings.GetSetting(GodotSharpEditor.Settings.OverrideDotnetExecutable).As()); if (dotnetPath == null) throw new FileNotFoundException("Cannot find the dotnet executable."); - var editorSettings = EditorInterface.Singleton.GetEditorSettings(); - var startInfo = new ProcessStartInfo(dotnetPath); BuildArguments(buildInfo, startInfo.ArgumentList, editorSettings); @@ -91,13 +91,13 @@ namespace GodotTools.Build private static Process LaunchPublish(BuildInfo buildInfo, Action? stdOutHandler, Action? stdErrHandler) { - string? dotnetPath = DotNetFinder.FindDotNetExe(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); + + string? dotnetPath = DotNetFinder.FindDotNetExe(editorSettings.GetSetting(GodotSharpEditor.Settings.OverrideDotnetExecutable).As()); if (dotnetPath == null) throw new FileNotFoundException("Cannot find the dotnet executable."); - var editorSettings = EditorInterface.Singleton.GetEditorSettings(); - var startInfo = new ProcessStartInfo(dotnetPath); BuildPublishArguments(buildInfo, startInfo.ArgumentList, editorSettings); diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs index 4d1456b70ca..e75ae9e95c0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs @@ -11,8 +11,55 @@ namespace GodotTools.Build { public static class DotNetFinder { - public static string? FindDotNetExe() + private static string DOTNET_ROOT_ENVIRONMENT_VARIABLE = "DOTNET_ROOT"; + + public static string? FindDotNetExe(string? overrideDotnetExecutable) { + if (string.IsNullOrEmpty(overrideDotnetExecutable) == false) + { + if (File.Exists(overrideDotnetExecutable)) + { + return overrideDotnetExecutable; + } + } + // Simplified parallel to modules/mono/editor/hostfxr_resolver.cpp's get_dotnet_root_from_env where + // DOTNET_ROOT is checked before path. + // https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables#net-sdk-and-cli-environment-variables + // Further support would be to handle DOTNET_ROOT, DOTNET_ROOT(x86), DOTNET_ROOT_X86, DOTNET_ROOT_X64 for specific architectures. + string envVariableToDotnetPath = Environment.GetEnvironmentVariable(DOTNET_ROOT_ENVIRONMENT_VARIABLE); + if (string.IsNullOrEmpty(envVariableToDotnetPath) == false) + { + if (Directory.Exists(envVariableToDotnetPath)) + { + string defaultDotnet = OS.PathWhich("dotnet"); + if (string.IsNullOrEmpty(defaultDotnet) == false) + { + string fileName = Path.GetFileName(defaultDotnet); + + string dotnetExecutable = Path.Join(envVariableToDotnetPath, fileName); + if (File.Exists(dotnetExecutable)) + { + return dotnetExecutable; + } + else if (Godot.OS.IsStdOutVerbose()) + { + Console.WriteLine( + $"Environment Variable \"{DOTNET_ROOT_ENVIRONMENT_VARIABLE}\"= \"{envVariableToDotnetPath}\" but does not contain a valid path to \"{dotnetExecutable}\"."); + } + } + else if (Godot.OS.IsStdOutVerbose()) + { + Console.WriteLine( + $"No default dotnet to use as method to connect \"{DOTNET_ROOT_ENVIRONMENT_VARIABLE}\" with correct executable name for platform."); + } + } + else if (Godot.OS.IsStdOutVerbose()) + { + Console.WriteLine( + $"Environment Variable \"{DOTNET_ROOT_ENVIRONMENT_VARIABLE}\" = \"{envVariableToDotnetPath}\" but is not a directory."); + } + } + // In the future, this method may do more than just search in PATH. We could look in // known locations or use Godot's linked nethost to search from the hostfxr location. @@ -38,6 +85,7 @@ namespace GodotTools.Build public static bool TryFindDotNetSdk( Version expectedVersion, + string? overrideDotnetExecutable, [NotNullWhen(true)] out Version? version, [NotNullWhen(true)] out string? path ) @@ -45,7 +93,7 @@ namespace GodotTools.Build version = null; path = null; - string? dotNetExe = FindDotNetExe(); + string? dotNetExe = FindDotNetExe(overrideDotnetExecutable); if (string.IsNullOrEmpty(dotNetExe)) return false; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 788b46ab9a1..d62405d002c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -34,6 +34,7 @@ namespace GodotTools public const string NoConsoleLogging = "dotnet/build/no_console_logging"; public const string CreateBinaryLog = "dotnet/build/create_binary_log"; public const string ProblemsLayout = "dotnet/build/problems_layout"; + public const string OverrideDotnetExecutable = "dotnet/build/override_dotnet_executable"; } #nullable disable @@ -264,8 +265,7 @@ namespace GodotTools GodotSharpDirs.ProjectSlnPath, line >= 0 ? $"{scriptPath};{line + 1};{col + 1}" : scriptPath }; - - string command = DotNetFinder.FindDotNetExe() ?? "dotnet"; + string command = DotNetFinder.FindDotNetExe(_editorSettings.GetSetting(GodotSharpEditor.Settings.OverrideDotnetExecutable).As()) ?? "dotnet"; try { @@ -456,9 +456,14 @@ namespace GodotTools var dotNetSdkSearchVersion = Environment.Version; + _editorSettings = EditorInterface.Singleton.GetEditorSettings(); + + string? dotnetExecutableEditorSettings = _editorSettings.GetSetting(GodotSharpEditor.Settings.OverrideDotnetExecutable) + .As(); + // First we try to find the .NET Sdk ourselves to make sure we get the // correct version first, otherwise pick the latest. - if (DotNetFinder.TryFindDotNetSdk(dotNetSdkSearchVersion, out var sdkVersion, out string? sdkPath)) + if (DotNetFinder.TryFindDotNetSdk(dotNetSdkSearchVersion, dotnetExecutableEditorSettings, out var sdkVersion, out string? sdkPath)) { if (Godot.OS.IsStdOutVerbose()) Console.WriteLine($"Found .NET Sdk version '{sdkVersion}': {sdkPath}"); @@ -483,8 +488,6 @@ namespace GodotTools var editorBaseControl = EditorInterface.Singleton.GetBaseControl(); - _editorSettings = EditorInterface.Singleton.GetEditorSettings(); - _errorDialog = new AcceptDialog(); _errorDialog.SetUnparentWhenInvisible(true); @@ -542,6 +545,7 @@ namespace GodotTools EditorDef(Settings.VerbosityLevel, Variant.From(VerbosityLevelId.Normal)); EditorDef(Settings.NoConsoleLogging, false); EditorDef(Settings.CreateBinaryLog, false); + EditorDef(Settings.OverrideDotnetExecutable, ""); EditorDef(Settings.ProblemsLayout, Variant.From(BuildProblemsView.ProblemsLayout.Tree)); string settingsHintStr = "Disabled"; diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp index 9c37ac810ac..1b5ce0c7d8f 100644 --- a/modules/mono/editor/hostfxr_resolver.cpp +++ b/modules/mono/editor/hostfxr_resolver.cpp @@ -67,6 +67,7 @@ SOFTWARE. #include "core/io/dir_access.h" #include "core/io/file_access.h" #include "core/os/os.h" +#include "editor/editor_settings.h" #ifdef WINDOWS_ENABLED #define WIN32_LEAN_AND_MEAN @@ -318,6 +319,61 @@ bool get_dotnet_root_from_env(String &r_dotnet_root) { return get_file_path_from_env(dotnet_root_env, r_dotnet_root); } +const StringName get_dotnet_editor_settings_name() { + return StringName("dotnet/build/override_dotnet_executable"); +} + +bool get_dotnet_exe_from_settings(String &r_dotnet_exe) { + const EditorSettings *editor_settings = EditorSettings::get_singleton(); + if (editor_settings == nullptr) { + // load editor settings. + EditorSettings::create(); + editor_settings = EditorSettings::get_singleton(); + if (editor_settings == nullptr) { + return false; + } + } + + const StringName editor_settings_name = get_dotnet_editor_settings_name(); + // Use EDITOR_DEF to default it to "", if not found. + const Variant property_val = EDITOR_DEF(editor_settings_name, ""); + if (property_val.is_string() == false) { + if (OS::get_singleton()->is_stdout_verbose()) { + ERR_PRINT("The editor setting (" + editor_settings_name + ") is not referencing a valid string path."); + } + return false; + } + + String dotnet_exe = property_val; + if (dotnet_exe.is_empty()) { + return false; + } + + r_dotnet_exe = path::abspath(path::realpath(dotnet_exe)); + + return true; +} + +bool get_dotnet_root_from_settings(String &r_dotnet_root) { + String dotnet_exe; + + if (!get_dotnet_exe_from_settings(dotnet_exe)) { + return false; + } + + r_dotnet_root = dotnet_exe.get_base_dir(); + + if (!DirAccess::exists(r_dotnet_root)) { + if (OS::get_singleton()->is_stdout_verbose()) { + ERR_PRINT("The editor setting (" + get_dotnet_editor_settings_name() + ") is not referencing a valid string path (" + r_dotnet_root + ")."); + } + + return false; + } + + return true; +} + } //namespace bool godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_fxr_path) { @@ -332,7 +388,8 @@ bool godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(const String &p } bool godotsharp::hostfxr_resolver::try_get_path(String &r_dotnet_root, String &r_fxr_path) { - if (!get_dotnet_root_from_env(r_dotnet_root) && + if (!get_dotnet_root_from_settings(r_dotnet_root) && + !get_dotnet_root_from_env(r_dotnet_root) && !get_dotnet_self_registered_dir(r_dotnet_root) && !get_default_installation_dir(r_dotnet_root)) { return false;