diff --git a/Activities/Python/UiPath.Python.Host.Shared/PythonService.cs b/Activities/Python/UiPath.Python.Host.Shared/PythonService.cs index 46e98ac6..c9ad3ddb 100644 --- a/Activities/Python/UiPath.Python.Host.Shared/PythonService.cs +++ b/Activities/Python/UiPath.Python.Host.Shared/PythonService.cs @@ -141,10 +141,8 @@ internal async void RunServer() private bool IsWindows() { bool isWindows = true; -#if NETCOREAPP if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) isWindows = false; -#endif return isWindows; } private void WaitForPipeDrain(NamedPipeServerStream pipe) diff --git a/Activities/Python/UiPath.Python/EngineProvider.cs b/Activities/Python/UiPath.Python/EngineProvider.cs index 2a7f802a..d00598f9 100644 --- a/Activities/Python/UiPath.Python/EngineProvider.cs +++ b/Activities/Python/UiPath.Python/EngineProvider.cs @@ -2,9 +2,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Runtime.InteropServices; using UiPath.Python.Impl; using UiPath.Python.Properties; -using System.Runtime.InteropServices; namespace UiPath.Python { @@ -14,8 +15,9 @@ namespace UiPath.Python public static class EngineProvider { private const string PythonHomeEnv = "PYTHONHOME"; - private const string PythonExe = "python.exe"; - private const string PythonLinux = "python"; + private static readonly string[] PythonExeWin = ["python.exe", "python3.exe"]; + private static readonly string[] PythonLinux = ["python", "python3"]; + private static readonly string[] PythonBinFolders = ["", "bin"]; private const string PythonVersionArgument = "--version"; // engines cache @@ -31,7 +33,7 @@ public static IEngine Get(Version version, string path, string libraryPath, bool { // read path from env variable path = Environment.GetEnvironmentVariable(PythonHomeEnv); - Trace.TraceInformation($"Found Pyhton path {path}"); + Trace.TraceInformation($"Found Python path {path}"); } if (!version.IsValid()) { @@ -43,7 +45,7 @@ public static IEngine Get(Version version, string path, string libraryPath, bool } // TODO: target&visible are meaningless when running in-process (at least now), maybe it should be split - if(inProcess) + if (inProcess) { if (!_cache.TryGetValue(version, out engine)) { @@ -62,37 +64,73 @@ public static IEngine Get(Version version, string path, string libraryPath, bool public static void Autodetect(string path, out Version version) { + version = Version.Auto; Trace.TraceInformation($"Trying to autodetect Python version from path {path}"); - var pythonExec = PythonExe; -#if NETCOREAPP - if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - pythonExec = PythonLinux; -#endif - string pyExe = Path.GetFullPath(Path.Combine(path, pythonExec)); - if (!File.Exists(pyExe)) + + var exes = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? PythonExeWin : PythonLinux; + var pythonCandidates = PythonBinFolders.SelectMany(folder => exes, Path.Combine).Select(p => Path.Combine(path, p)).Distinct().Select(Path.GetFullPath).ToList(); + var existingFiles = pythonCandidates.Where(File.Exists).ToList(); + + if (existingFiles.Count == 0) { - throw new FileNotFoundException(Resources.PythonExeNotFoundException, pyExe); + throw new FileNotFoundException(Resources.PythonExeNotFoundException, string.Join(", ", pythonCandidates)); } - Process process = new Process(); - process.StartInfo = new ProcessStartInfo() + + Dictionary errors = new Dictionary(); + bool versionDetected = false; + + foreach (var python in existingFiles) { - UseShellExecute = false, - CreateNoWindow = true, - WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden, - FileName = pyExe, - Arguments = PythonVersionArgument, - RedirectStandardError = true, - RedirectStandardOutput = true - }; - process.Start(); - // Now read the value, parse to int and add 1 (from the original script) - string ver = process.StandardError.ReadToEnd(); - if(string.IsNullOrEmpty(ver)) - ver = process.StandardOutput.ReadToEnd(); - process.WaitForExit(); - version = ver.GetVersionFromStr(); - Trace.TraceInformation($"Autodetected Python version {version}"); + if (Autodetect(python, out version, out Exception ex)) + { + versionDetected = true; + break; + } + else + { + errors[python] = ex; + } + } + + // if we are here, it means that we found some candidates but all of them failed to be detected, so we throw an aggregate exception with all the details + if (!versionDetected) + { + throw new AggregateException(errors.Where(kv => kv.Value != null).Select(kv => new Exception($"{kv.Key}: {kv.Value.Message}", kv.Value))); + } + } + + private static bool Autodetect(string pythonFullPath, out Version version, out Exception exception) + { + version = Version.Auto; + exception = null; + try + { + using Process process = new Process(); + process.StartInfo = new ProcessStartInfo() + { + UseShellExecute = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + FileName = pythonFullPath, + Arguments = PythonVersionArgument, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + process.Start(); + process.WaitForExit(3000); + // Now read the value, parse to int and add 1 (from the original script) + string ver = process.StandardError.ReadToEnd(); + if (string.IsNullOrEmpty(ver)) + ver = process.StandardOutput.ReadToEnd(); + + version = ver.GetVersionFromStr(); + return version != Version.Auto; + } + catch (Exception ex) + { + exception = ex; + return false; + } } - } } diff --git a/Activities/Python/UiPath.Python/Impl/Engine.cs b/Activities/Python/UiPath.Python/Impl/Engine.cs index 555e6ced..49674971 100644 --- a/Activities/Python/UiPath.Python/Impl/Engine.cs +++ b/Activities/Python/UiPath.Python/Impl/Engine.cs @@ -73,10 +73,8 @@ internal Engine(Version version, string path, string libraryPath) _version = version; _path = path; _libraryPath = libraryPath; -#if NETCOREAPP if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) _isWindows = false; -#endif } #region IEngine diff --git a/Activities/Python/UiPath.Python/Service/PythonProxy.cs b/Activities/Python/UiPath.Python/Service/PythonProxy.cs index e81c4b08..d629c1fd 100644 --- a/Activities/Python/UiPath.Python/Service/PythonProxy.cs +++ b/Activities/Python/UiPath.Python/Service/PythonProxy.cs @@ -272,10 +272,8 @@ private PythonResponse ReadResponse(PythonRequest request, CancellationToken ct) private bool IsWindows() { bool isWindows = true; -#if NETCOREAPP if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) isWindows = false; -#endif return isWindows; } private void WaitForPipeDrain() diff --git a/Activities/Shared/UiPath.Shared.Service/Client/Controller.cs b/Activities/Shared/UiPath.Shared.Service/Client/Controller.cs index df0d776f..e73fc857 100644 --- a/Activities/Shared/UiPath.Shared.Service/Client/Controller.cs +++ b/Activities/Shared/UiPath.Shared.Service/Client/Controller.cs @@ -40,10 +40,8 @@ internal void ForceStop() private void StartHostService() { var isWindows = true; -#if NETCOREAPP if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) isWindows = false; -#endif string folder = Path.GetDirectoryName(HostLibFile); var hostLibFullPath = HostLibFile; if (folder.IsNullOrEmpty()) diff --git a/Activities/Shared/UiPath.Shared.Service/Client/HostWrapper.cs b/Activities/Shared/UiPath.Shared.Service/Client/HostWrapper.cs index 5f79b47a..910cdc90 100644 --- a/Activities/Shared/UiPath.Shared.Service/Client/HostWrapper.cs +++ b/Activities/Shared/UiPath.Shared.Service/Client/HostWrapper.cs @@ -28,6 +28,7 @@ public void Dispose() //Prevent process leak in certain error scenarios Proc?.Kill(); + Proc?.Dispose(); Proc = null; }