2019-12-11 14:46:28 +01:00
using System ;
2019-12-06 11:30:49 +01:00
using System.Collections.Generic ;
2021-09-12 20:23:05 +02:00
using System.Diagnostics.CodeAnalysis ;
2019-12-06 11:30:49 +01:00
using System.IO ;
using System.Linq ;
2021-09-12 20:23:05 +02:00
using System.Runtime.Versioning ;
2019-12-06 11:30:49 +01:00
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 ;
2020-02-27 15:22:12 +01:00
// ReSharper disable UnassignedField.Local
// ReSharper disable InconsistentNaming
// ReSharper disable UnassignedField.Global
// ReSharper disable MemberHidesStaticFromOuterClass
2019-12-06 11:30:49 +01:00
namespace GodotTools.Ides.Rider
{
2019-12-11 14:46:28 +01:00
/// <summary>
/// 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
/// </summary>
public static class RiderPathLocator
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
public static RiderInfo [ ] GetAllRiderPaths ( )
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
try
{
if ( OS . IsWindows )
{
return CollectRiderInfosWindows ( ) ;
}
2020-10-08 13:34:42 +02:00
if ( OS . IsMacOS )
2019-12-11 14:46:28 +01:00
{
return CollectRiderInfosMac ( ) ;
}
2020-05-09 20:55:50 +02:00
if ( OS . IsUnixLike )
2019-12-11 14:46:28 +01:00
{
return CollectAllRiderPathsLinux ( ) ;
}
throw new Exception ( "Unexpected OS." ) ;
}
catch ( Exception e )
{
GD . PushWarning ( e . Message ) ;
}
2019-12-06 11:30:49 +01:00
2021-07-25 13:46:31 +02:00
return Array . Empty < RiderInfo > ( ) ;
2019-12-06 11:30:49 +01:00
}
2019-12-11 14:46:28 +01:00
private static RiderInfo [ ] CollectAllRiderPathsLinux ( )
{
var installInfos = new List < RiderInfo > ( ) ;
var home = Environment . GetEnvironmentVariable ( "HOME" ) ;
if ( ! string . IsNullOrEmpty ( home ) )
{
var 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 )
{
var lines = File . ReadAllLines ( shortcut . FullName ) ;
foreach ( var line in lines )
{
if ( ! line . StartsWith ( "Exec=\"" ) )
continue ;
var 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 ) ) ;
}
}
}
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
// snap install
var snapInstallPath = "/snap/rider/current/bin/rider.sh" ;
if ( new FileInfo ( snapInstallPath ) . Exists )
installInfos . Add ( new RiderInfo ( snapInstallPath , false ) ) ;
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
return installInfos . ToArray ( ) ;
2019-12-06 11:30:49 +01:00
}
2019-12-11 14:46:28 +01:00
private static RiderInfo [ ] CollectRiderInfosMac ( )
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
var installInfos = new List < RiderInfo > ( ) ;
// "/Applications/*Rider*.app"
2019-12-25 00:14:31 +01:00
// should be combined with "Contents/MacOS/rider"
2019-12-11 14:46:28 +01:00
var folder = new DirectoryInfo ( "/Applications" ) ;
if ( folder . Exists )
{
installInfos . AddRange ( folder . GetDirectories ( "*Rider*.app" )
2019-12-25 00:14:31 +01:00
. Select ( a = > new RiderInfo ( Path . Combine ( a . FullName , "Contents/MacOS/rider" ) , false ) )
2019-12-11 14:46:28 +01:00
. ToList ( ) ) ;
}
// /Users/user/Library/Application Support/JetBrains/Toolbox/apps/Rider/ch-1/181.3870.267/Rider EAP.app
2019-12-25 00:14:31 +01:00
// should be combined with "Contents/MacOS/rider"
2019-12-11 14:46:28 +01:00
var toolboxRiderRootPath = GetToolboxBaseDir ( ) ;
var paths = CollectPathsFromToolbox ( toolboxRiderRootPath , "" , "Rider*.app" , true )
2019-12-25 00:14:31 +01:00
. Select ( a = > new RiderInfo ( Path . Combine ( a , "Contents/MacOS/rider" ) , true ) ) ;
2019-12-11 14:46:28 +01:00
installInfos . AddRange ( paths ) ;
return installInfos . ToArray ( ) ;
2019-12-06 11:30:49 +01:00
}
2021-09-12 20:23:05 +02:00
[SupportedOSPlatform("windows")]
2019-12-11 14:46:28 +01:00
private static RiderInfo [ ] CollectRiderInfosWindows ( )
{
var installInfos = new List < RiderInfo > ( ) ;
var toolboxRiderRootPath = GetToolboxBaseDir ( ) ;
var installPathsToolbox = CollectPathsFromToolbox ( toolboxRiderRootPath , "bin" , "rider64.exe" , false ) . ToList ( ) ;
installInfos . AddRange ( installPathsToolbox . Select ( a = > new RiderInfo ( a , true ) ) . ToList ( ) ) ;
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
var installPaths = new List < string > ( ) ;
const string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" ;
CollectPathsFromRegistry ( registryKey , installPaths ) ;
const string wowRegistryKey = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" ;
CollectPathsFromRegistry ( wowRegistryKey , installPaths ) ;
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
installInfos . AddRange ( installPaths . Select ( a = > new RiderInfo ( a , false ) ) . ToList ( ) ) ;
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
return installInfos . ToArray ( ) ;
2019-12-06 11:30:49 +01:00
}
2019-12-11 14:46:28 +01:00
private static string GetToolboxBaseDir ( )
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
if ( OS . IsWindows )
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
var localAppData = Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ;
2020-02-27 15:22:12 +01:00
return GetToolboxRiderRootPath ( localAppData ) ;
2019-12-06 11:30:49 +01:00
}
2020-10-08 13:34:42 +02:00
if ( OS . IsMacOS )
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
var home = Environment . GetEnvironmentVariable ( "HOME" ) ;
2020-05-09 20:55:50 +02:00
if ( string . IsNullOrEmpty ( home ) )
2020-02-27 15:22:12 +01:00
return string . Empty ;
var localAppData = Path . Combine ( home , @"Library/Application Support" ) ;
return GetToolboxRiderRootPath ( localAppData ) ;
2019-12-06 11:30:49 +01:00
}
2020-05-09 20:55:50 +02:00
if ( OS . IsUnixLike )
2019-12-11 14:46:28 +01:00
{
var home = Environment . GetEnvironmentVariable ( "HOME" ) ;
2020-05-09 20:55:50 +02:00
if ( string . IsNullOrEmpty ( home ) )
2020-02-27 15:22:12 +01:00
return string . Empty ;
var localAppData = Path . Combine ( home , @".local/share" ) ;
return GetToolboxRiderRootPath ( localAppData ) ;
}
return string . Empty ;
}
private static string GetToolboxRiderRootPath ( string localAppData )
{
2020-03-18 09:23:29 +01:00
var toolboxPath = Path . Combine ( localAppData , @"JetBrains/Toolbox" ) ;
2020-02-27 15:22:12 +01:00
var settingsJson = Path . Combine ( toolboxPath , ".settings.json" ) ;
if ( File . Exists ( settingsJson ) )
{
var path = SettingsJson . GetInstallLocationFromJson ( File . ReadAllText ( settingsJson ) ) ;
if ( ! string . IsNullOrEmpty ( path ) )
toolboxPath = path ;
2019-12-11 14:46:28 +01:00
}
2019-12-06 11:30:49 +01:00
2020-03-18 09:23:29 +01:00
var toolboxRiderRootPath = Path . Combine ( toolboxPath , @"apps/Rider" ) ;
2020-02-27 15:22:12 +01:00
return toolboxRiderRootPath ;
2019-12-11 14:46:28 +01:00
}
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
internal static ProductInfo GetBuildVersion ( string path )
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
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 ) ;
2019-12-06 11:30:49 +01:00
}
2019-12-11 14:46:28 +01:00
internal static Version GetBuildNumber ( string path )
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
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 ;
2019-12-06 11:30:49 +01:00
}
2019-12-11 14:46:28 +01:00
internal static bool IsToolbox ( string path )
{
return path . StartsWith ( GetToolboxBaseDir ( ) ) ;
}
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
private static string GetRelativePathToBuildTxt ( )
{
2020-05-09 20:55:50 +02:00
if ( OS . IsWindows | | OS . IsUnixLike )
2019-12-11 14:46:28 +01:00
return "../../build.txt" ;
2020-10-08 13:34:42 +02:00
if ( OS . IsMacOS )
2019-12-11 14:46:28 +01:00
return "Contents/Resources/build.txt" ;
throw new Exception ( "Unknown OS." ) ;
}
2019-12-06 11:30:49 +01:00
2021-09-12 20:23:05 +02:00
[SupportedOSPlatform("windows")]
2019-12-11 14:46:28 +01:00
private static void CollectPathsFromRegistry ( string registryKey , List < string > installPaths )
{
2020-02-28 21:34:20 +01:00
using ( var key = Registry . CurrentUser . OpenSubKey ( registryKey ) )
{
CollectPathsFromRegistry ( installPaths , key ) ;
}
2019-12-11 14:46:28 +01:00
using ( var key = Registry . LocalMachine . OpenSubKey ( registryKey ) )
{
2020-02-28 21:34:20 +01:00
CollectPathsFromRegistry ( installPaths , key ) ;
}
}
2021-09-12 20:23:05 +02:00
[SupportedOSPlatform("windows")]
2020-02-28 21:34:20 +01:00
private static void CollectPathsFromRegistry ( List < string > installPaths , RegistryKey key )
{
if ( key = = null ) return ;
foreach ( var subkeyName in key . GetSubKeyNames ( ) . Where ( a = > a . Contains ( "Rider" ) ) )
{
using ( var subkey = key . OpenSubKey ( subkeyName ) )
2019-12-11 14:46:28 +01:00
{
2020-02-28 21:34:20 +01:00
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 ) ;
2019-12-11 14:46:28 +01:00
}
}
}
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
private static string [ ] CollectPathsFromToolbox ( string toolboxRiderRootPath , string dirName , string searchPattern ,
bool isMac )
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
if ( ! Directory . Exists ( toolboxRiderRootPath ) )
2021-07-25 13:46:31 +02:00
return Array . Empty < string > ( ) ;
2019-12-11 14:46:28 +01:00
var channelDirs = Directory . GetDirectories ( toolboxRiderRootPath ) ;
var paths = channelDirs . SelectMany ( channelDir = >
{
try
{
2020-02-27 15:22:12 +01:00
// 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" ) ;
2019-12-11 14:46:28 +01:00
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 ;
}
}
2020-02-27 15:22:12 +01:00
// 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 ) ) ;
2019-12-11 14:46:28 +01:00
}
catch ( Exception e )
{
2020-02-27 15:22:12 +01:00
// do not write to Debug.Log, just log it.
Logger . Warn ( $"Failed to get RiderPath from {channelDir}" , e ) ;
2019-12-11 14:46:28 +01:00
}
2021-07-25 13:46:31 +02:00
return Array . Empty < string > ( ) ;
2019-12-11 14:46:28 +01:00
} )
. Where ( c = > ! string . IsNullOrEmpty ( c ) )
. ToArray ( ) ;
return paths ;
2019-12-06 11:30:49 +01:00
}
2019-12-11 14:46:28 +01:00
private static string [ ] GetExecutablePaths ( string dirName , string searchPattern , bool isMac , string buildDir )
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
var folder = new DirectoryInfo ( Path . Combine ( buildDir , dirName ) ) ;
if ( ! folder . Exists )
2021-07-25 13:46:31 +02:00
return Array . Empty < string > ( ) ;
2019-12-11 14:46:28 +01:00
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 ( ) ;
2019-12-06 11:30:49 +01:00
}
2019-12-11 14:46:28 +01:00
// 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
2019-12-06 11:30:49 +01:00
2020-02-27 15:22:12 +01:00
[Serializable]
class SettingsJson
{
public string install_location ;
2020-05-09 20:55:50 +02:00
2021-09-12 20:23:05 +02:00
[return: MaybeNull]
2020-02-27 15:22:12 +01:00
public static string GetInstallLocationFromJson ( string json )
{
try
{
return JsonConvert . DeserializeObject < SettingsJson > ( json ) . install_location ;
}
catch ( Exception )
{
Logger . Warn ( $"Failed to get install_location from json {json}" ) ;
}
return null ;
}
}
2019-12-11 14:46:28 +01:00
[Serializable]
class ToolboxHistory
{
public List < ItemNode > history ;
public static string GetLatestBuildFromJson ( string json )
{
try
{
return JsonConvert . DeserializeObject < ToolboxHistory > ( json ) . history . LastOrDefault ( ) ? . item . build ;
}
catch ( Exception )
{
Logger . Warn ( $"Failed to get latest build from json {json}" ) ;
}
return null ;
}
}
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
[Serializable]
class ItemNode
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
public BuildNode item ;
2019-12-06 11:30:49 +01:00
}
2019-12-11 14:46:28 +01:00
[Serializable]
class BuildNode
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
public string build ;
2019-12-06 11:30:49 +01:00
}
2019-12-11 14:46:28 +01:00
[Serializable]
public class ProductInfo
{
public string version ;
public string versionSuffix ;
2019-12-06 11:30:49 +01:00
2021-09-12 20:23:05 +02:00
[return: MaybeNull]
2019-12-11 14:46:28 +01:00
internal static ProductInfo GetProductInfo ( string json )
{
try
{
var productInfo = JsonConvert . DeserializeObject < ProductInfo > ( json ) ;
return productInfo ;
}
catch ( Exception )
{
Logger . Warn ( $"Failed to get version from json {json}" ) ;
}
return null ;
}
}
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
// ReSharper disable once ClassNeverInstantiated.Global
[Serializable]
class ToolboxInstallData
{
// ReSharper disable once InconsistentNaming
public ActiveApplication active_application ;
2019-12-06 11:30:49 +01:00
2021-09-12 20:23:05 +02:00
[return: MaybeNull]
2019-12-11 14:46:28 +01:00
public static string GetLatestBuildFromJson ( string json )
{
try
{
var toolbox = JsonConvert . DeserializeObject < ToolboxInstallData > ( 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
2019-12-06 11:30:49 +01:00
{
2019-12-11 14:46:28 +01:00
public List < string > builds ;
2019-12-06 11:30:49 +01:00
}
2019-12-11 14:46:28 +01:00
#pragma warning restore 0649
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
public struct RiderInfo
{
2020-02-27 15:22:12 +01:00
// ReSharper disable once NotAccessedField.Global
2019-12-11 14:46:28 +01:00
public bool IsToolbox ;
public string Presentation ;
public Version BuildNumber ;
public ProductInfo ProductInfo ;
public string Path ;
2019-12-06 11:30:49 +01:00
2019-12-11 14:46:28 +01:00
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 ) ;
}
}
2019-12-06 11:30:49 +01:00
}
}