@@ -351,8 +351,84 @@ def is_compatible_exe(jlinfo, _debug=lambda *_: None):
351351 return py_libpython == jl_libpython
352352
353353
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+
354414_julia_runtime = [False ]
355415
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+
356432class Julia (object ):
357433 """
358434 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,
483559 self .api .jl_unbox_voidpointer .argtypes = [void_p ]
484560 self .api .jl_unbox_voidpointer .restype = py_object
485561
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+
486573 self .api .jl_exception_clear .restype = None
487574 self .api .jl_stderr_obj .argtypes = []
488575 self .api .jl_stderr_obj .restype = void_p
@@ -494,14 +581,20 @@ def __init__(self, init_julia=True, jl_init_path=None, runtime=None,
494581 if init_julia :
495582 if use_separate_cache :
496583 # 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 )
505598 # Intercept precompilation
506599 os .environ ["PYCALL_PYTHON_EXE" ] = sys .executable
507600 os .environ ["PYCALL_JULIA_HOME" ] = PYCALL_JULIA_HOME
@@ -580,6 +673,29 @@ def _call(self, src):
580673
581674 return ans
582675
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+
583699 def check_exception (self , src = "<unknown code>" ):
584700 exoc = self .api .jl_exception_occurred ()
585701 self ._debug ("exception occured? " + str (exoc ))
0 commit comments