Skip to content

Commit 765abf2

Browse files
authored
Locate dotnet tool ipy launcher (#1936)
* Locate dotnet tool ipy launcher * Optimization: don't scan for runner on .NET Framework * Fix typos in comments * Fix test condition
1 parent c214027 commit 765abf2

File tree

2 files changed

+63
-7
lines changed

2 files changed

+63
-7
lines changed

src/core/IronPython/Hosting/PythonCommandLine.cs

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
using System.Collections.Generic;
77
using System.Diagnostics;
88
using System.IO;
9+
using System.IO.MemoryMappedFiles;
910
using System.Reflection;
1011
using System.Runtime.InteropServices;
12+
using System.Runtime.Versioning;
1113
using System.Text;
1214
using System.Threading;
1315

@@ -261,17 +263,18 @@ private void InitializeModules() {
261263

262264
var name = Path.GetFileNameWithoutExtension(executable);
263265
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
264-
var runner = Path.Combine(prefix, name + ".exe");
265-
if (File.Exists(runner)) {
266+
var exename = name + ".exe";
267+
var runner = Path.Combine(prefix, exename);
268+
if (File.Exists(runner) || FindRunner(prefix, exename, executable, out runner)) {
266269
executable = runner;
267270
} else {
268-
// TODO: was for .NET Core 2.1, can we drop this?
271+
// ipy.bat is created Install-IronPython.ps1, which installs from a zip file
269272
runner = Path.Combine(prefix, name + ".bat");
270273
if (File.Exists(runner)) executable = runner;
271274
}
272275
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
273276
var runner = Path.Combine(prefix, name);
274-
if (File.Exists(runner)) {
277+
if (File.Exists(runner) || FindRunner(prefix, name, executable, out runner)) {
275278
executable = runner;
276279
} else {
277280
runner = Path.Combine(prefix, name + ".sh");
@@ -289,7 +292,7 @@ private void InitializeModules() {
289292
if (File.Exists(path)) {
290293
foreach (var line in File.ReadAllLines(path, Encoding.UTF8)) { // TODO: this actually needs to be decoded with surrogateescape
291294
if (line.StartsWith('#')) continue;
292-
var split = line.Split(new[] { '=' }, 2);
295+
var split = line.Split(['='], 2);
293296
if (split.Length != 2) continue;
294297
if (split[0].Trim() == "home") {
295298
pyvenv_prefix = split[1].Trim();
@@ -309,6 +312,59 @@ private void InitializeModules() {
309312
}
310313

311314
PythonContext.SetHostVariables(prefix ?? "", executable, null);
315+
316+
317+
// --- Local functions -------
318+
319+
static bool FindRunner(string prefix, string name, string assembly, out string runner) {
320+
runner = null;
321+
#if NET
322+
while (prefix != null) {
323+
runner = Path.Combine(prefix, name);
324+
if (File.Exists(runner)) {
325+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExecutable(runner)) {
326+
break;
327+
}
328+
}
329+
prefix = Path.GetDirectoryName(prefix);
330+
}
331+
if (prefix != null && Path.GetExtension(assembly).Equals(".dll", StringComparison.OrdinalIgnoreCase)) {
332+
// make sure that the runner refers to this DLL
333+
var relativeAssemblyPath = assembly.Substring(prefix.Length + 1); // skip over the path separator
334+
byte[] fsAssemblyPath = Encoding.UTF8.GetBytes(relativeAssemblyPath);
335+
byte fsap0 = fsAssemblyPath[0];
336+
337+
try {
338+
using var mmf = MemoryMappedFile.CreateFromFile(runner, FileMode.Open, null, 0, MemoryMappedFileAccess.Read);
339+
using var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);
340+
341+
for (long i = accessor.Capacity - fsAssemblyPath.Length; i >= 0; i--) { // the path should be close to the end of the file
342+
if (accessor.ReadByte(i) != fsap0) continue;
343+
344+
bool found = true;
345+
for (int j = 1; j < fsAssemblyPath.Length; j++) {
346+
if (accessor.ReadByte(i + j) != fsAssemblyPath[j]) {
347+
found = false;
348+
break;
349+
}
350+
}
351+
if (found) return true;
352+
}
353+
} catch { } // if reading the file fails, it is not our runner
354+
}
355+
#endif
356+
return false;
357+
}
358+
359+
#if NET
360+
[UnsupportedOSPlatform("windows")]
361+
static bool IsExecutable(string filePath) {
362+
var fileInfo = new Mono.Unix.UnixFileInfo(filePath);
363+
var fileMode = fileInfo.FileAccessPermissions;
364+
365+
return (fileMode & (Mono.Unix.FileAccessPermissions.UserExecute | Mono.Unix.FileAccessPermissions.GroupExecute | Mono.Unix.FileAccessPermissions.OtherExecute)) != 0;
366+
}
367+
#endif
312368
}
313369

314370
/// <summary>

src/core/IronPython/Runtime/ClrModule.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
[assembly: PythonModule("clr", typeof(IronPython.Runtime.ClrModule))]
4141
namespace IronPython.Runtime {
4242
/// <summary>
43-
/// this class contains objecs and static methods used for
44-
/// .NET/CLS interop with Python.
43+
/// This class contains objects and static methods used for
44+
/// .NET/CLS interop with Python.
4545
/// </summary>
4646
public static class ClrModule {
4747

0 commit comments

Comments
 (0)