66using System . Collections . Generic ;
77using System . Diagnostics ;
88using System . IO ;
9+ using System . IO . MemoryMappedFiles ;
910using System . Reflection ;
1011using System . Runtime . InteropServices ;
12+ using System . Runtime . Versioning ;
1113using System . Text ;
1214using 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>
0 commit comments