@@ -351,8 +351,84 @@ def is_compatible_exe(jlinfo, _debug=lambda *_: None):
351
351
return py_libpython == jl_libpython
352
352
353
353
354
+ _separate_cache_error_common_header = """\
355
+ It seems your Julia and PyJulia setup are not supported.
356
+
357
+ Julia interpreter:
358
+ {runtime}
359
+ Python interpreter and libpython used by PyCall.jl:
360
+ {jlinfo.pyprogramname}
361
+ {jl_libpython}
362
+ Python interpreter used to import PyJulia and its libpython.
363
+ {sys.executable}
364
+ {py_libpython}
365
+ """
366
+
367
+
368
+ _separate_cache_error_common_footer = """
369
+ For more information, see:
370
+ https://github.com/JuliaPy/pyjulia
371
+ https://github.com/JuliaPy/PyCall.jl
372
+ """
373
+
374
+
375
+ _separate_cache_error_statically_linked = """
376
+ Your Python interpreter "{sys.executable}"
377
+ is statically linked to libpython. Currently, PyJulia does not support
378
+ such Python interpreter. For available workarounds, see:
379
+ https://github.com/JuliaPy/pyjulia/issues/185
380
+ """
381
+
382
+
383
+ _separate_cache_error_incompatible_libpython = """
384
+ In Julia >= 0.7, above two paths to `libpython` have to match exactly
385
+ in order for PyJulia to work. To configure PyCall.jl to use Python
386
+ interpreter "{sys.executable}",
387
+ run the following commands in the Julia interpreter:
388
+
389
+ ENV["PYTHON"] = "{sys.executable}"
390
+ using Pkg
391
+ Pkg.build("PyCall")
392
+ """
393
+
394
+
395
+ def raise_separate_cache_error (
396
+ runtime , jlinfo ,
397
+ # For test:
398
+ _determine_if_statically_linked = determine_if_statically_linked ):
399
+ template = _separate_cache_error_common_header
400
+ if _determine_if_statically_linked ():
401
+ template += _separate_cache_error_statically_linked
402
+ else :
403
+ template += _separate_cache_error_incompatible_libpython
404
+ template += _separate_cache_error_common_footer
405
+ message = template .format (
406
+ runtime = runtime ,
407
+ jlinfo = jlinfo ,
408
+ py_libpython = find_libpython (),
409
+ jl_libpython = normalize_path (jlinfo .libpython ),
410
+ sys = sys )
411
+ raise RuntimeError (message )
412
+
413
+
354
414
_julia_runtime = [False ]
355
415
416
+
417
+ UNBOXABLE_TYPES = (
418
+ 'bool' ,
419
+ 'int8' ,
420
+ 'uint8' ,
421
+ 'int16' ,
422
+ 'uint16' ,
423
+ 'int32' ,
424
+ 'uint32' ,
425
+ 'int64' ,
426
+ 'uint64' ,
427
+ 'float32' ,
428
+ 'float64' ,
429
+ )
430
+
431
+
356
432
class Julia (object ):
357
433
"""
358
434
Implements a bridge to the Julia interpreter or library.
@@ -483,6 +559,17 @@ def __init__(self, init_julia=True, jl_init_path=None, runtime=None,
483
559
self .api .jl_unbox_voidpointer .argtypes = [void_p ]
484
560
self .api .jl_unbox_voidpointer .restype = py_object
485
561
562
+ for c_type in UNBOXABLE_TYPES :
563
+ jl_unbox = getattr (self .api , "jl_unbox_{}" .format (c_type ))
564
+ jl_unbox .argtypes = [void_p ]
565
+ jl_unbox .restype = getattr (ctypes , "c_{}" .format ({
566
+ "float32" : "float" ,
567
+ "float64" : "double" ,
568
+ }.get (c_type , c_type )))
569
+
570
+ self .api .jl_typeof .argtypes = [void_p ]
571
+ self .api .jl_typeof .restype = void_p
572
+
486
573
self .api .jl_exception_clear .restype = None
487
574
self .api .jl_stderr_obj .argtypes = []
488
575
self .api .jl_stderr_obj .restype = void_p
@@ -494,14 +581,20 @@ def __init__(self, init_julia=True, jl_init_path=None, runtime=None,
494
581
if init_julia :
495
582
if use_separate_cache :
496
583
# First check that this is supported
497
- self ._call ("""
498
- if VERSION < v"0.5-"
499
- error(\" ""Using pyjulia with a statically-compiled version
500
- of python or with a version of python that
501
- differs from that used by PyCall.jl is not
502
- supported on julia 0.4""\" )
503
- end
504
- """ )
584
+ version_range = self ._unbox_as (self ._call ("""
585
+ Int64(if VERSION < v"0.6-"
586
+ 2
587
+ elseif VERSION >= v"0.7-"
588
+ 1
589
+ else
590
+ 0
591
+ end)
592
+ """ ), "int64" )
593
+ if version_range == 2 :
594
+ raise RuntimeError (
595
+ "PyJulia does not support Julia < 0.6 anymore" )
596
+ elif version_range == 1 :
597
+ raise_separate_cache_error (runtime , jlinfo )
505
598
# Intercept precompilation
506
599
os .environ ["PYCALL_PYTHON_EXE" ] = sys .executable
507
600
os .environ ["PYCALL_JULIA_HOME" ] = PYCALL_JULIA_HOME
@@ -580,6 +673,29 @@ def _call(self, src):
580
673
581
674
return ans
582
675
676
+ @staticmethod
677
+ def _check_unboxable (c_type ):
678
+ if c_type not in UNBOXABLE_TYPES :
679
+ raise ValueError ("Julia value cannot be unboxed as c_type={!r}.\n "
680
+ "c_type supported by PyJulia are:\n "
681
+ "{}" .format (c_type , "\n " .join (UNBOXABLE_TYPES )))
682
+
683
+ def _is_unboxable_as (self , pointer , c_type ):
684
+ self ._check_unboxable (c_type )
685
+ jl_type = getattr (self .api , 'jl_{}_type' .format (c_type ))
686
+ desired = ctypes .cast (jl_type , ctypes .POINTER (ctypes .c_void_p ))[0 ]
687
+ actual = self .api .jl_typeof (pointer )
688
+ return actual == desired
689
+
690
+ def _unbox_as (self , pointer , c_type ):
691
+ self ._check_unboxable (c_type )
692
+ jl_unbox = getattr (self .api , 'jl_unbox_{}' .format (c_type ))
693
+ if self ._is_unboxable_as (pointer , c_type ):
694
+ return jl_unbox (pointer )
695
+ else :
696
+ raise TypeError ("Cannot unbox pointer {} as {}"
697
+ .format (pointer , c_type ))
698
+
583
699
def check_exception (self , src = "<unknown code>" ):
584
700
exoc = self .api .jl_exception_occurred ()
585
701
self ._debug ("exception occured? " + str (exoc ))
0 commit comments