From f13e2ba740a4c32547dcbce42f87d2fcac008f8d Mon Sep 17 00:00:00 2001 From: viogroza Date: Wed, 18 Feb 2026 15:39:35 +0200 Subject: [PATCH 1/2] Python: Autodetect version issue (26.2) [STUD-78782] Search for python and python3 when detecting python Search in the install folder and in ../bin folder Remove no longer needed preprocessor directive --- .../PythonService.cs | 2 - .../Python/UiPath.Python/EngineProvider.cs | 99 +++++++++++++------ .../Python/UiPath.Python/Impl/Engine.cs | 2 - .../UiPath.Python/Service/PythonProxy.cs | 2 - .../Client/Controller.cs | 2 - 5 files changed, 68 insertions(+), 39 deletions(-) diff --git a/Activities/Python/UiPath.Python.Host.Shared/PythonService.cs b/Activities/Python/UiPath.Python.Host.Shared/PythonService.cs index 46e98ac66..c9ad3ddbc 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 2a7f802ad..b6c64f980 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 @@ -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,72 @@ 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)).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 detected = 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)) + { + detected = 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 (!detected) + { + 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 + { + 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(); + // 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(); + 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 555e6ced5..496749710 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 e81c4b089..d629c1fd8 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 df0d776f9..e73fc857e 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()) From 3da41d20e1dd1ac2eb11d7e65fd5501e5aa4b96e Mon Sep 17 00:00:00 2001 From: viogroza Date: Wed, 18 Feb 2026 17:15:31 +0200 Subject: [PATCH 2/2] Python: Autodetect version issue (26.2) [STUD-78782] Address review findings --- Activities/Python/UiPath.Python/EngineProvider.cs | 15 ++++++++------- .../UiPath.Shared.Service/Client/HostWrapper.cs | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Activities/Python/UiPath.Python/EngineProvider.cs b/Activities/Python/UiPath.Python/EngineProvider.cs index b6c64f980..d00598f90 100644 --- a/Activities/Python/UiPath.Python/EngineProvider.cs +++ b/Activities/Python/UiPath.Python/EngineProvider.cs @@ -33,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()) { @@ -68,7 +68,7 @@ public static void Autodetect(string path, out Version version) Trace.TraceInformation($"Trying to autodetect Python version from path {path}"); var exes = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? PythonExeWin : PythonLinux; - var pythonCandidates = PythonBinFolders.SelectMany(folder => exes, Path.Combine).Select(p => Path.Combine(path, p)).Select(Path.GetFullPath).ToList(); + 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) @@ -77,13 +77,13 @@ public static void Autodetect(string path, out Version version) } Dictionary errors = new Dictionary(); - bool detected = false; + bool versionDetected = false; foreach (var python in existingFiles) { if (Autodetect(python, out version, out Exception ex)) { - detected = true; + versionDetected = true; break; } else @@ -93,7 +93,7 @@ public static void Autodetect(string path, out Version version) } // 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 (!detected) + if (!versionDetected) { throw new AggregateException(errors.Where(kv => kv.Value != null).Select(kv => new Exception($"{kv.Key}: {kv.Value.Message}", kv.Value))); } @@ -105,7 +105,7 @@ private static bool Autodetect(string pythonFullPath, out Version version, out E exception = null; try { - Process process = new Process(); + using Process process = new Process(); process.StartInfo = new ProcessStartInfo() { UseShellExecute = false, @@ -117,11 +117,12 @@ private static bool Autodetect(string pythonFullPath, out Version version, out E 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(); - process.WaitForExit(); + version = ver.GetVersionFromStr(); return version != Version.Auto; } diff --git a/Activities/Shared/UiPath.Shared.Service/Client/HostWrapper.cs b/Activities/Shared/UiPath.Shared.Service/Client/HostWrapper.cs index 5f79b47a9..910cdc90e 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; }