From 5eb62c27e32415d0b361a7381d90494634eec2a6 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Thu, 27 Mar 2025 10:57:20 -0700 Subject: [PATCH 1/4] Locate dotnet tool ipy launcher --- .../IronPython/Hosting/PythonCommandLine.cs | 63 +++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/src/core/IronPython/Hosting/PythonCommandLine.cs b/src/core/IronPython/Hosting/PythonCommandLine.cs index afc65ea3c..f526da87c 100644 --- a/src/core/IronPython/Hosting/PythonCommandLine.cs +++ b/src/core/IronPython/Hosting/PythonCommandLine.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.MemoryMappedFiles; using System.Reflection; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Text; using System.Threading; @@ -261,17 +263,18 @@ private void InitializeModules() { var name = Path.GetFileNameWithoutExtension(executable); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - var runner = Path.Combine(prefix, name + ".exe"); - if (File.Exists(runner)) { + var exename = name + ".exe"; + var runner = Path.Combine(prefix, exename); + if (File.Exists(runner) || FindRunner(prefix, exename, executable, out runner)) { executable = runner; } else { - // TODO: was for .NET Core 2.1, can we drop this? + // ipy.bat is created Install-IronPython.ps1, which installs from a zip file runner = Path.Combine(prefix, name + ".bat"); if (File.Exists(runner)) executable = runner; } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { var runner = Path.Combine(prefix, name); - if (File.Exists(runner)) { + if (File.Exists(runner) || FindRunner(prefix, name, executable, out runner)) { executable = runner; } else { runner = Path.Combine(prefix, name + ".sh"); @@ -289,7 +292,7 @@ private void InitializeModules() { if (File.Exists(path)) { foreach (var line in File.ReadAllLines(path, Encoding.UTF8)) { // TODO: this actually needs to be decoded with surrogateescape if (line.StartsWith('#')) continue; - var split = line.Split(new[] { '=' }, 2); + var split = line.Split(['='], 2); if (split.Length != 2) continue; if (split[0].Trim() == "home") { pyvenv_prefix = split[1].Trim(); @@ -309,6 +312,56 @@ private void InitializeModules() { } PythonContext.SetHostVariables(prefix ?? "", executable, null); + + // -------- + static bool FindRunner(string prefix, string name, string assembly, out string runner) { + runner = null; + while (prefix != null) { + runner = Path.Combine(prefix, name); + if (File.Exists(runner)) { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExecutable(runner)) { + break; + } + } + prefix = Path.GetDirectoryName(prefix); + } + if (runner != null && Path.GetExtension(assembly).Equals(".dll", StringComparison.OrdinalIgnoreCase)) { + // make sure that the runner refers to this DLL + var relativeAssemblyPath = assembly.Substring(prefix.Length + 1); // skip over the path separator + byte[] fsAssemblyPath = Encoding.UTF8.GetBytes(relativeAssemblyPath); + byte fsap0 = fsAssemblyPath[0]; + + try { + using var mmf = MemoryMappedFile.CreateFromFile(runner, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); + using var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); + + for (long i = accessor.Capacity - fsAssemblyPath.Length; i >= 0; i--) { // the path should be close to the end of the file + if (accessor.ReadByte(i) != fsap0) { + continue; + } + bool found = true; + for (int j = 1; j < fsAssemblyPath.Length; j++) { + if (accessor.ReadByte(i + j) != fsAssemblyPath[j]) { + found = false; + break; + } + } + if (found) { + return true; + } + } + } catch { } + } + return false; + } + + [UnsupportedOSPlatform("windows")] + static bool IsExecutable(string filePath) { + var fileInfo = new Mono.Unix.UnixFileInfo(filePath); + var fileMode = fileInfo.FileAccessPermissions; + + return (fileMode & (Mono.Unix.FileAccessPermissions.UserExecute | Mono.Unix.FileAccessPermissions.GroupExecute | Mono.Unix.FileAccessPermissions.OtherExecute)) != 0; + } } /// From 7d9621eb8ed4bf09417407a5e0a883e4cce5f619 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Sun, 30 Mar 2025 21:53:57 -0700 Subject: [PATCH 2/4] Optimization: don't scan for runner on .NET Framework --- .../IronPython/Hosting/PythonCommandLine.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/core/IronPython/Hosting/PythonCommandLine.cs b/src/core/IronPython/Hosting/PythonCommandLine.cs index f526da87c..4347a460f 100644 --- a/src/core/IronPython/Hosting/PythonCommandLine.cs +++ b/src/core/IronPython/Hosting/PythonCommandLine.cs @@ -313,9 +313,12 @@ private void InitializeModules() { PythonContext.SetHostVariables(prefix ?? "", executable, null); - // -------- + + // --- Local functions ------- + static bool FindRunner(string prefix, string name, string assembly, out string runner) { runner = null; +#if NET while (prefix != null) { runner = Path.Combine(prefix, name); if (File.Exists(runner)) { @@ -336,9 +339,8 @@ static bool FindRunner(string prefix, string name, string assembly, out string r using var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read); for (long i = accessor.Capacity - fsAssemblyPath.Length; i >= 0; i--) { // the path should be close to the end of the file - if (accessor.ReadByte(i) != fsap0) { - continue; - } + if (accessor.ReadByte(i) != fsap0) continue; + bool found = true; for (int j = 1; j < fsAssemblyPath.Length; j++) { if (accessor.ReadByte(i + j) != fsAssemblyPath[j]) { @@ -346,15 +348,15 @@ static bool FindRunner(string prefix, string name, string assembly, out string r break; } } - if (found) { - return true; - } + if (found) return true; } } catch { } } +#endif return false; } +#if NET [UnsupportedOSPlatform("windows")] static bool IsExecutable(string filePath) { var fileInfo = new Mono.Unix.UnixFileInfo(filePath); @@ -362,6 +364,7 @@ static bool IsExecutable(string filePath) { return (fileMode & (Mono.Unix.FileAccessPermissions.UserExecute | Mono.Unix.FileAccessPermissions.GroupExecute | Mono.Unix.FileAccessPermissions.OtherExecute)) != 0; } +#endif } /// From 879f232ba68d1c046ea02cf26c50b6c843fe53c0 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Sun, 30 Mar 2025 21:54:22 -0700 Subject: [PATCH 3/4] Fix typos in comments --- src/core/IronPython/Runtime/ClrModule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/IronPython/Runtime/ClrModule.cs b/src/core/IronPython/Runtime/ClrModule.cs index 168e4da8b..124e553aa 100644 --- a/src/core/IronPython/Runtime/ClrModule.cs +++ b/src/core/IronPython/Runtime/ClrModule.cs @@ -40,8 +40,8 @@ [assembly: PythonModule("clr", typeof(IronPython.Runtime.ClrModule))] namespace IronPython.Runtime { /// - /// this class contains objecs and static methods used for - /// .NET/CLS interop with Python. + /// This class contains objects and static methods used for + /// .NET/CLS interop with Python. /// public static class ClrModule { From cfdd74e94dc77b57a45cc70a58784555a773c4a5 Mon Sep 17 00:00:00 2001 From: Pavel Koneski Date: Mon, 31 Mar 2025 14:53:44 -0700 Subject: [PATCH 4/4] Fix test condition --- src/core/IronPython/Hosting/PythonCommandLine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/IronPython/Hosting/PythonCommandLine.cs b/src/core/IronPython/Hosting/PythonCommandLine.cs index 4347a460f..781836ddd 100644 --- a/src/core/IronPython/Hosting/PythonCommandLine.cs +++ b/src/core/IronPython/Hosting/PythonCommandLine.cs @@ -328,7 +328,7 @@ static bool FindRunner(string prefix, string name, string assembly, out string r } prefix = Path.GetDirectoryName(prefix); } - if (runner != null && Path.GetExtension(assembly).Equals(".dll", StringComparison.OrdinalIgnoreCase)) { + if (prefix != null && Path.GetExtension(assembly).Equals(".dll", StringComparison.OrdinalIgnoreCase)) { // make sure that the runner refers to this DLL var relativeAssemblyPath = assembly.Substring(prefix.Length + 1); // skip over the path separator byte[] fsAssemblyPath = Encoding.UTF8.GetBytes(relativeAssemblyPath); @@ -350,7 +350,7 @@ static bool FindRunner(string prefix, string name, string assembly, out string r } if (found) return true; } - } catch { } + } catch { } // if reading the file fails, it is not our runner } #endif return false;