6
6
using System . Collections . Generic ;
7
7
using System . Diagnostics ;
8
8
using System . IO ;
9
+ using System . IO . MemoryMappedFiles ;
9
10
using System . Reflection ;
10
11
using System . Runtime . InteropServices ;
12
+ using System . Runtime . Versioning ;
11
13
using System . Text ;
12
14
using System . Threading ;
13
15
@@ -261,17 +263,18 @@ private void InitializeModules() {
261
263
262
264
var name = Path . GetFileNameWithoutExtension ( executable ) ;
263
265
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 ) ) {
266
269
executable = runner ;
267
270
} 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
269
272
runner = Path . Combine ( prefix , name + ".bat" ) ;
270
273
if ( File . Exists ( runner ) ) executable = runner ;
271
274
}
272
275
} else if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) {
273
276
var runner = Path . Combine ( prefix , name ) ;
274
- if ( File . Exists ( runner ) ) {
277
+ if ( File . Exists ( runner ) || FindRunner ( prefix , name , executable , out runner ) ) {
275
278
executable = runner ;
276
279
} else {
277
280
runner = Path . Combine ( prefix , name + ".sh" ) ;
@@ -289,7 +292,7 @@ private void InitializeModules() {
289
292
if ( File . Exists ( path ) ) {
290
293
foreach ( var line in File . ReadAllLines ( path , Encoding . UTF8 ) ) { // TODO: this actually needs to be decoded with surrogateescape
291
294
if ( line . StartsWith ( '#' ) ) continue ;
292
- var split = line . Split ( new [ ] { '=' } , 2 ) ;
295
+ var split = line . Split ( [ '=' ] , 2 ) ;
293
296
if ( split . Length != 2 ) continue ;
294
297
if ( split [ 0 ] . Trim ( ) == "home" ) {
295
298
pyvenv_prefix = split [ 1 ] . Trim ( ) ;
@@ -309,6 +312,59 @@ private void InitializeModules() {
309
312
}
310
313
311
314
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
312
368
}
313
369
314
370
/// <summary>
0 commit comments