32
32
# this is python 3.3 specific
33
33
from types import ModuleType , FunctionType
34
34
35
+ from .find_libpython import find_libpython , normalize_path
36
+
35
37
#-----------------------------------------------------------------------------
36
38
# Classes and funtions
37
39
#-----------------------------------------------------------------------------
@@ -42,6 +44,12 @@ def iteritems(d): return iter(d.items())
42
44
else :
43
45
iteritems = dict .iteritems
44
46
47
+
48
+ # As setting up Julia modifies os.environ, we need to cache it for
49
+ # launching subprocesses later in the original environment.
50
+ _enviorn = os .environ .copy ()
51
+
52
+
45
53
class JuliaError (Exception ):
46
54
pass
47
55
@@ -254,7 +262,9 @@ def determine_if_statically_linked():
254
262
255
263
JuliaInfo = namedtuple (
256
264
'JuliaInfo' ,
257
- ['JULIA_HOME' , 'libjulia_path' , 'image_file' , 'pyprogramname' ])
265
+ ['JULIA_HOME' , 'libjulia_path' , 'image_file' ,
266
+ # Variables in PyCall/deps/deps.jl:
267
+ 'pyprogramname' , 'libpython' ])
258
268
259
269
260
270
def juliainfo (runtime = 'julia' ):
@@ -277,24 +287,61 @@ def juliainfo(runtime='julia'):
277
287
if PyCall_depsfile !== nothing && isfile(PyCall_depsfile)
278
288
include(PyCall_depsfile)
279
289
println(pyprogramname)
290
+ println(libpython)
280
291
end
281
- """ ])
292
+ """ ],
293
+ # Use the original environment variables to avoid a cryptic
294
+ # error "fake-julia/../lib/julia/sys.so: cannot open shared
295
+ # object file: No such file or directory":
296
+ env = _enviorn )
282
297
args = output .decode ("utf-8" ).rstrip ().split ("\n " )
283
- if len (args ) == 3 :
284
- args .append (None ) # no pyprogramname set
298
+ args .extend ([None ] * (len (JuliaInfo ._fields ) - len (args )))
285
299
return JuliaInfo (* args )
286
300
287
301
288
302
def is_same_path (a , b ):
289
- a = os .path .normpath (os .path .normcase (a ))
290
- b = os .path .normpath (os .path .normcase (b ))
303
+ a = os .path .realpath (os .path .normcase (a ))
304
+ b = os .path .realpath (os .path .normcase (b ))
291
305
return a == b
292
306
293
307
294
- def is_different_exe (pyprogramname , sys_executable ):
295
- if pyprogramname is None :
308
+ def is_compatible_exe (jlinfo , _debug = lambda * _ : None ):
309
+ """
310
+ Determine if Python used by PyCall.jl is compatible with this Python.
311
+
312
+ Current Python executable is considered compatible if it is dynamically
313
+ linked to libpython (usually the case in macOS and Windows) and
314
+ both of them are using identical libpython. If this function returns
315
+ `True`, PyJulia use the same precompilation cache of PyCall.jl used by
316
+ Julia itself.
317
+
318
+ Parameters
319
+ ----------
320
+ jlinfo : JuliaInfo
321
+ A `JuliaInfo` object returned by `juliainfo` function.
322
+ """
323
+ _debug ("jlinfo.libpython =" , jlinfo .libpython )
324
+ if jlinfo .libpython is None :
325
+ _debug ("libpython cannot be read from PyCall/deps/deps.jl" )
326
+ return False
327
+
328
+ if determine_if_statically_linked ():
329
+ _debug (sys .executable , "is statically linked." )
330
+ return False
331
+
332
+ # Note that the following check is OK since statically linked case
333
+ # is already excluded.
334
+ if is_same_path (jlinfo .pyprogramname , sys .executable ):
335
+ # In macOS and Windows, find_libpython does not work as good
336
+ # as in Linux. We add this shortcut so that PyJulia can work
337
+ # in those environments.
296
338
return True
297
- return not is_same_path (pyprogramname , sys_executable )
339
+
340
+ py_libpython = find_libpython ()
341
+ jl_libpython = normalize_path (jlinfo .libpython )
342
+ _debug ("py_libpython =" , py_libpython )
343
+ _debug ("jl_libpython =" , jl_libpython )
344
+ return py_libpython == jl_libpython
298
345
299
346
300
347
_julia_runtime = [False ]
@@ -349,11 +396,10 @@ def __init__(self, init_julia=True, jl_runtime_path=None, jl_init_path=None,
349
396
runtime = jl_runtime_path
350
397
else :
351
398
runtime = 'julia'
352
- JULIA_HOME , libjulia_path , image_file , depsjlexe = juliainfo (runtime )
399
+ jlinfo = juliainfo (runtime )
400
+ JULIA_HOME , libjulia_path , image_file , depsjlexe = jlinfo [:4 ]
353
401
self ._debug ("pyprogramname =" , depsjlexe )
354
402
self ._debug ("sys.executable =" , sys .executable )
355
- exe_differs = is_different_exe (depsjlexe , sys .executable )
356
- self ._debug ("exe_differs =" , exe_differs )
357
403
self ._debug ("JULIA_HOME = %s, libjulia_path = %s" % (JULIA_HOME , libjulia_path ))
358
404
if not os .path .exists (libjulia_path ):
359
405
raise JuliaError ("Julia library (\" libjulia\" ) not found! {}" .format (libjulia_path ))
@@ -371,7 +417,8 @@ def __init__(self, init_julia=True, jl_runtime_path=None, jl_init_path=None,
371
417
else :
372
418
jl_init_path = JULIA_HOME .encode ("utf-8" ) # initialize with JULIA_HOME
373
419
374
- use_separate_cache = exe_differs or determine_if_statically_linked ()
420
+ use_separate_cache = not is_compatible_exe (jlinfo , _debug = self ._debug )
421
+ self ._debug ("use_separate_cache =" , use_separate_cache )
375
422
if use_separate_cache :
376
423
PYCALL_JULIA_HOME = os .path .join (
377
424
os .path .dirname (os .path .realpath (__file__ )),"fake-julia" ).replace ("\\ " ,"\\ \\ " )
@@ -441,16 +488,37 @@ def __init__(self, init_julia=True, jl_runtime_path=None, jl_init_path=None,
441
488
# configuration and so do any packages that depend on it.
442
489
self ._call (u"unshift!(Base.LOAD_CACHE_PATH, abspath(Pkg.Dir._pkgroot()," +
443
490
"\" lib\" , \" pyjulia%s-v$(VERSION.major).$(VERSION.minor)\" ))" % sys .version_info [0 ])
444
- # If PyCall.ji does not exist, create an empty file to force
445
- # recompilation
491
+
492
+ # If PyCall.jl is already pre-compiled, for the global
493
+ # environment, hide it while we are loading PyCall.jl
494
+ # for PyJulia which has to compile a new cache if it
495
+ # does not exist. However, Julia does not compile a
496
+ # new cache if it exists in Base.LOAD_CACHE_PATH[2:end].
497
+ # https://github.com/JuliaPy/pyjulia/issues/92#issuecomment-289303684
446
498
self ._call (u"""
447
- isdir(Base.LOAD_CACHE_PATH[1]) ||
448
- mkpath(Base.LOAD_CACHE_PATH[1])
449
- depsfile = joinpath(Base.LOAD_CACHE_PATH[1],"PyCall.ji")
450
- isfile(depsfile) || touch(depsfile)
499
+ for path in Base.LOAD_CACHE_PATH[2:end]
500
+ cache = joinpath(path, "PyCall.ji")
501
+ backup = joinpath(path, "PyCall.ji.backup")
502
+ if isfile(cache)
503
+ mv(cache, backup; remove_destination=true)
504
+ end
505
+ end
451
506
""" )
452
507
453
508
self ._call (u"using PyCall" )
509
+
510
+ if use_separate_cache :
511
+ self ._call (u"""
512
+ for path in Base.LOAD_CACHE_PATH[2:end]
513
+ cache = joinpath(path, "PyCall.ji")
514
+ backup = joinpath(path, "PyCall.ji.backup")
515
+ if !isfile(cache) && isfile(backup)
516
+ mv(backup, cache)
517
+ end
518
+ rm(backup; force=true)
519
+ end
520
+ """ )
521
+
454
522
# Whether we initialized Julia or not, we MUST create at least one
455
523
# instance of PyObject and the convert function. Since these will be
456
524
# needed on every call, we hold them in the Julia object itself so
0 commit comments