From bf3af9fd48aceda699bb558558bd805a7e522760 Mon Sep 17 00:00:00 2001 From: Ivan Shakhov Date: Thu, 22 Jun 2023 17:42:21 +0200 Subject: [PATCH] Update the RiderPathLocator to support the JetBrains Toolbox 2.0 --- .../GodotTools/GodotTools/GodotTools.csproj | 3 +- .../Ides/Rider/RiderLocatorEnvironment.cs | 51 ++ .../GodotTools/Ides/Rider/RiderPathLocator.cs | 474 ------------------ .../GodotTools/Ides/Rider/RiderPathManager.cs | 8 + 4 files changed, 60 insertions(+), 476 deletions(-) create mode 100644 modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs delete mode 100644 modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index 30525ba04ac7..4a0b7f9bed6f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -28,10 +28,9 @@ + - - $(GodotApiAssembliesDir)/GodotSharp.dll False diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs new file mode 100644 index 000000000000..7e08d8c01dad --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs @@ -0,0 +1,51 @@ +using System; +using Godot; +using JetBrains.Rider.PathLocator; +using Newtonsoft.Json; +using OS = GodotTools.Utils.OS; + +namespace GodotTools.Ides.Rider; + +public class RiderLocatorEnvironment : IRiderLocatorEnvironment +{ + public JetBrains.Rider.PathLocator.OS CurrentOS + { + get + { + if (OS.IsWindows) + return JetBrains.Rider.PathLocator.OS.Windows; + if (OS.IsMacOS) return JetBrains.Rider.PathLocator.OS.MacOSX; + if (OS.IsUnixLike) return JetBrains.Rider.PathLocator.OS.Linux; + return JetBrains.Rider.PathLocator.OS.Other; + } + } + + public T FromJson(string json) + { + return JsonConvert.DeserializeObject(json); + } + + public void Info(string message, Exception e = null) + { + if (e == null) + GD.Print(message); + else + GD.Print(message, e); + } + + public void Warn(string message, Exception e = null) + { + if (e == null) + GD.PushWarning(message); + else + GD.PushWarning(message, e); + } + + public void Error(string message, Exception e = null) + { + if (e == null) + GD.PushError(message); + else + GD.PushError(message, e); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs deleted file mode 100644 index dad6e353448d..000000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs +++ /dev/null @@ -1,474 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Runtime.Versioning; -using Godot; -using Microsoft.Win32; -using Newtonsoft.Json; -using Directory = System.IO.Directory; -using Environment = System.Environment; -using File = System.IO.File; -using Path = System.IO.Path; -using OS = GodotTools.Utils.OS; - -// ReSharper disable UnassignedField.Local -// ReSharper disable InconsistentNaming -// ReSharper disable UnassignedField.Global -// ReSharper disable MemberHidesStaticFromOuterClass - -namespace GodotTools.Ides.Rider -{ - /// - /// This code is a modified version of the JetBrains resharper-unity plugin listed under Apache License 2.0 license: - /// https://github.com/JetBrains/resharper-unity/blob/master/unity/JetBrains.Rider.Unity.Editor/EditorPlugin/RiderPathLocator.cs - /// - public static class RiderPathLocator - { - public static RiderInfo[] GetAllRiderPaths() - { - try - { - if (OS.IsWindows) - { - return CollectRiderInfosWindows(); - } - if (OS.IsMacOS) - { - return CollectRiderInfosMac(); - } - if (OS.IsUnixLike) - { - return CollectAllRiderPathsLinux(); - } - throw new InvalidOperationException("Unexpected OS."); - } - catch (Exception e) - { - GD.PushWarning(e.Message); - } - - return Array.Empty(); - } - - private static RiderInfo[] CollectAllRiderPathsLinux() - { - var installInfos = new List(); - string home = Environment.GetEnvironmentVariable("HOME"); - if (!string.IsNullOrEmpty(home)) - { - string toolboxRiderRootPath = GetToolboxBaseDir(); - installInfos.AddRange(CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider.sh", false) - .Select(a => new RiderInfo(a, true)).ToList()); - - //$Home/.local/share/applications/jetbrains-rider.desktop - var shortcut = new FileInfo(Path.Combine(home, @".local/share/applications/jetbrains-rider.desktop")); - - if (shortcut.Exists) - { - string[] lines = File.ReadAllLines(shortcut.FullName); - foreach (string line in lines) - { - if (!line.StartsWith("Exec=\"")) - continue; - string path = line.Split('"').Where((item, index) => index == 1).SingleOrDefault(); - if (string.IsNullOrEmpty(path)) - continue; - - if (installInfos.Any(a => a.Path == path)) // avoid adding similar build as from toolbox - continue; - installInfos.Add(new RiderInfo(path, false)); - } - } - } - - // snap install - string snapInstallPath = "/snap/rider/current/bin/rider.sh"; - if (new FileInfo(snapInstallPath).Exists) - installInfos.Add(new RiderInfo(snapInstallPath, false)); - - return installInfos.ToArray(); - } - - private static RiderInfo[] CollectRiderInfosMac() - { - var installInfos = new List(); - // "/Applications/*Rider*.app" - // should be combined with "Contents/MacOS/rider" - var folder = new DirectoryInfo("/Applications"); - if (folder.Exists) - { - installInfos.AddRange(folder.GetDirectories("*Rider*.app") - .Select(a => new RiderInfo(Path.Combine(a.FullName, "Contents/MacOS/rider"), false)) - .ToList()); - } - - // /Users/user/Library/Application Support/JetBrains/Toolbox/apps/Rider/ch-1/181.3870.267/Rider EAP.app - // should be combined with "Contents/MacOS/rider" - string toolboxRiderRootPath = GetToolboxBaseDir(); - var paths = CollectPathsFromToolbox(toolboxRiderRootPath, "", "Rider*.app", true) - .Select(a => new RiderInfo(Path.Combine(a, "Contents/MacOS/rider"), true)); - installInfos.AddRange(paths); - - return installInfos.ToArray(); - } - - [SupportedOSPlatform("windows")] - private static RiderInfo[] CollectRiderInfosWindows() - { - var installInfos = new List(); - var toolboxRiderRootPath = GetToolboxBaseDir(); - var installPathsToolbox = CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider64.exe", false).ToList(); - installInfos.AddRange(installPathsToolbox.Select(a => new RiderInfo(a, true)).ToList()); - - var installPaths = new List(); - const string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; - CollectPathsFromRegistry(registryKey, installPaths); - const string wowRegistryKey = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"; - CollectPathsFromRegistry(wowRegistryKey, installPaths); - - installInfos.AddRange(installPaths.Select(a => new RiderInfo(a, false)).ToList()); - - return installInfos.ToArray(); - } - - private static string GetToolboxBaseDir() - { - if (OS.IsWindows) - { - string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - return GetToolboxRiderRootPath(localAppData); - } - - if (OS.IsMacOS) - { - var home = Environment.GetEnvironmentVariable("HOME"); - if (string.IsNullOrEmpty(home)) - return string.Empty; - var localAppData = Path.Combine(home, @"Library/Application Support"); - return GetToolboxRiderRootPath(localAppData); - } - - if (OS.IsUnixLike) - { - var home = Environment.GetEnvironmentVariable("HOME"); - if (string.IsNullOrEmpty(home)) - return string.Empty; - var localAppData = Path.Combine(home, @".local/share"); - return GetToolboxRiderRootPath(localAppData); - } - - return string.Empty; - } - - - private static string GetToolboxRiderRootPath(string localAppData) - { - var toolboxPath = Path.Combine(localAppData, @"JetBrains/Toolbox"); - var settingsJson = Path.Combine(toolboxPath, ".settings.json"); - - if (File.Exists(settingsJson)) - { - var path = SettingsJson.GetInstallLocationFromJson(File.ReadAllText(settingsJson)); - if (!string.IsNullOrEmpty(path)) - toolboxPath = path; - } - - var toolboxRiderRootPath = Path.Combine(toolboxPath, @"apps/Rider"); - return toolboxRiderRootPath; - } - - internal static ProductInfo GetBuildVersion(string path) - { - var buildTxtFileInfo = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt())); - var dir = buildTxtFileInfo.DirectoryName; - if (!Directory.Exists(dir)) - return null; - var buildVersionFile = new FileInfo(Path.Combine(dir, "product-info.json")); - if (!buildVersionFile.Exists) - return null; - var json = File.ReadAllText(buildVersionFile.FullName); - return ProductInfo.GetProductInfo(json); - } - - internal static Version GetBuildNumber(string path) - { - var file = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt())); - if (!file.Exists) - return null; - var text = File.ReadAllText(file.FullName); - if (text.Length <= 3) - return null; - - var versionText = text.Substring(3); - return Version.TryParse(versionText, out var v) ? v : null; - } - - internal static bool IsToolbox(string path) - { - return path.StartsWith(GetToolboxBaseDir()); - } - - private static string GetRelativePathToBuildTxt() - { - if (OS.IsWindows || OS.IsUnixLike) - return "../../build.txt"; - if (OS.IsMacOS) - return "Contents/Resources/build.txt"; - throw new InvalidOperationException("Unknown OS."); - } - - [SupportedOSPlatform("windows")] - private static void CollectPathsFromRegistry(string registryKey, List installPaths) - { - using (var key = Registry.CurrentUser.OpenSubKey(registryKey)) - { - CollectPathsFromRegistry(installPaths, key); - } - using (var key = Registry.LocalMachine.OpenSubKey(registryKey)) - { - CollectPathsFromRegistry(installPaths, key); - } - } - - [SupportedOSPlatform("windows")] - private static void CollectPathsFromRegistry(List installPaths, RegistryKey key) - { - if (key == null) return; - foreach (var subkeyName in key.GetSubKeyNames().Where(a => a.Contains("Rider"))) - { - using (var subkey = key.OpenSubKey(subkeyName)) - { - var folderObject = subkey?.GetValue("InstallLocation"); - if (folderObject == null) continue; - var folder = folderObject.ToString(); - var possiblePath = Path.Combine(folder, @"bin\rider64.exe"); - if (File.Exists(possiblePath)) - installPaths.Add(possiblePath); - } - } - } - - private static string[] CollectPathsFromToolbox(string toolboxRiderRootPath, string dirName, string searchPattern, - bool isMac) - { - if (!Directory.Exists(toolboxRiderRootPath)) - return Array.Empty(); - - var channelDirs = Directory.GetDirectories(toolboxRiderRootPath); - var paths = channelDirs.SelectMany(channelDir => - { - try - { - // use history.json - last entry stands for the active build https://jetbrains.slack.com/archives/C07KNP99D/p1547807024066500?thread_ts=1547731708.057700&cid=C07KNP99D - var historyFile = Path.Combine(channelDir, ".history.json"); - if (File.Exists(historyFile)) - { - var json = File.ReadAllText(historyFile); - var build = ToolboxHistory.GetLatestBuildFromJson(json); - if (build != null) - { - var buildDir = Path.Combine(channelDir, build); - var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir); - if (executablePaths.Any()) - return executablePaths; - } - } - - var channelFile = Path.Combine(channelDir, ".channel.settings.json"); - if (File.Exists(channelFile)) - { - var json = File.ReadAllText(channelFile).Replace("active-application", "active_application"); - var build = ToolboxInstallData.GetLatestBuildFromJson(json); - if (build != null) - { - var buildDir = Path.Combine(channelDir, build); - var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir); - if (executablePaths.Any()) - return executablePaths; - } - } - - // changes in toolbox json files format may brake the logic above, so return all found Rider installations - return Directory.GetDirectories(channelDir) - .SelectMany(buildDir => GetExecutablePaths(dirName, searchPattern, isMac, buildDir)); - } - catch (Exception e) - { - // do not write to Debug.Log, just log it. - Logger.Warn($"Failed to get RiderPath from {channelDir}", e); - } - - return Array.Empty(); - }) - .Where(c => !string.IsNullOrEmpty(c)) - .ToArray(); - return paths; - } - - private static string[] GetExecutablePaths(string dirName, string searchPattern, bool isMac, string buildDir) - { - var folder = new DirectoryInfo(Path.Combine(buildDir, dirName)); - if (!folder.Exists) - return Array.Empty(); - - if (!isMac) - return new[] { Path.Combine(folder.FullName, searchPattern) }.Where(File.Exists).ToArray(); - return folder.GetDirectories(searchPattern).Select(f => f.FullName) - .Where(Directory.Exists).ToArray(); - } - - // Disable the "field is never assigned" compiler warning. We never assign it, but Unity does. - // Note that Unity disable this warning in the generated C# projects -#pragma warning disable 0649 - - [Serializable] - class SettingsJson - { - public string install_location; - - [return: MaybeNull] - public static string GetInstallLocationFromJson(string json) - { - try - { - return JsonConvert.DeserializeObject(json).install_location; - } - catch (Exception) - { - Logger.Warn($"Failed to get install_location from json {json}"); - } - - return null; - } - } - - [Serializable] - class ToolboxHistory - { - public List history; - - public static string GetLatestBuildFromJson(string json) - { - try - { - return JsonConvert.DeserializeObject(json).history.LastOrDefault()?.item.build; - } - catch (Exception) - { - Logger.Warn($"Failed to get latest build from json {json}"); - } - - return null; - } - } - - [Serializable] - class ItemNode - { - public BuildNode item; - } - - [Serializable] - class BuildNode - { - public string build; - } - - [Serializable] - public class ProductInfo - { - public string version; - public string versionSuffix; - - [return: MaybeNull] - internal static ProductInfo GetProductInfo(string json) - { - try - { - var productInfo = JsonConvert.DeserializeObject(json); - return productInfo; - } - catch (Exception) - { - Logger.Warn($"Failed to get version from json {json}"); - } - - return null; - } - } - - // ReSharper disable once ClassNeverInstantiated.Global - [Serializable] - class ToolboxInstallData - { - // ReSharper disable once InconsistentNaming - public ActiveApplication active_application; - - [return: MaybeNull] - public static string GetLatestBuildFromJson(string json) - { - try - { - var toolbox = JsonConvert.DeserializeObject(json); - var builds = toolbox.active_application.builds; - if (builds != null && builds.Any()) - return builds.First(); - } - catch (Exception) - { - Logger.Warn($"Failed to get latest build from json {json}"); - } - - return null; - } - } - - [Serializable] - class ActiveApplication - { - public List builds; - } - -#pragma warning restore 0649 - - public struct RiderInfo - { - // ReSharper disable once NotAccessedField.Global - public bool IsToolbox; - public string Presentation; - public Version BuildNumber; - public ProductInfo ProductInfo; - public string Path; - - public RiderInfo(string path, bool isToolbox) - { - BuildNumber = GetBuildNumber(path); - ProductInfo = GetBuildVersion(path); - Path = new FileInfo(path).FullName; // normalize separators - var presentation = $"Rider {BuildNumber}"; - - if (ProductInfo != null && !string.IsNullOrEmpty(ProductInfo.version)) - { - var suffix = string.IsNullOrEmpty(ProductInfo.versionSuffix) ? "" : $" {ProductInfo.versionSuffix}"; - presentation = $"Rider {ProductInfo.version}{suffix}"; - } - - if (isToolbox) - presentation += " (JetBrains Toolbox)"; - - Presentation = presentation; - IsToolbox = isToolbox; - } - } - - private static class Logger - { - internal static void Warn(string message, Exception e = null) - { - throw new Exception(message, e); - } - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index f55ca4c7d79b..5c09f1f83af6 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -4,11 +4,19 @@ using System.IO; using System.Linq; using Godot; using GodotTools.Internals; +using JetBrains.Rider.PathLocator; namespace GodotTools.Ides.Rider { public static class RiderPathManager { + private static readonly RiderPathLocator RiderPathLocator; + + static RiderPathManager() + { + RiderPathLocator = new RiderPathLocator(new RiderLocatorEnvironment()); + } + public static readonly string EditorPathSettingName = "dotnet/editor/editor_path_optional"; private static string GetRiderPathFromSettings()