@@ -60,6 +60,71 @@ function pyjlwrap_init()
6060 end
6161end
6262
63+ # ########################################################################
64+ # Virtual environment support
65+
66+ _clength (x:: Cstring ) = ccall (:strlen , Csize_t, (Cstring,), x) + 1
67+ _clength (x) = length (x)
68+
69+ function __leak (:: Type{T} , x) where T
70+ n = _clength (x)
71+ ptr = ccall (:malloc , Ptr{T}, (Csize_t,), n * sizeof (T))
72+ unsafe_copyto! (ptr, pointer (x), n)
73+ return ptr
74+ end
75+
76+ """
77+ _leak(T::Type, x::AbstractString) :: Ptr
78+ _leak(x::Array) :: Ptr
79+
80+ Leak `x` from Julia's GCer. This is meant to be used only for
81+ `Py_SetPythonHome` and `Py_SetProgramName` where the Python
82+ documentation demands that the passed argument must points to "static
83+ storage whose contents will not change for the duration of the
84+ program's execution" (although it seems that in newer CPython versions
85+ the contents are copied internally).
86+ """
87+ _leak (x:: Union{Cstring, Array} ) = __leak (eltype (x), x)
88+ _leak (T:: Type , x:: AbstractString ) =
89+ _leak (Base. unsafe_convert (T, Base. cconvert (T, x)))
90+ _leak (:: Type{Cwstring} , x:: AbstractString ) =
91+ _leak (Base. cconvert (Cwstring, x))
92+
93+ function pythonhome_of (pyprogramname:: AbstractString )
94+ if Sys. iswindows ()
95+ script = """
96+ import sys
97+ sys.stdout.write(sys.exec_prefix)
98+ """
99+ # See where PYTHONHOME is mentioned in ../deps/build.jl
100+ else
101+ script = """
102+ import sys
103+ sys.stdout.write(sys.prefix)
104+ sys.stdout.write(":")
105+ sys.stdout.write(sys.exec_prefix)
106+ """
107+ # https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHOME
108+ end
109+ cmd = ` $pyprogramname -c $script `
110+
111+ # For Windows:
112+ env = copy (ENV )
113+ env[" PYTHONIOENCODING" ] = " UTF-8"
114+ cmd = setenv (cmd, env)
115+
116+ return read (cmd, String)
117+ end
118+
119+ function find_libpython (python:: AbstractString )
120+ script = joinpath (@__DIR__ , " .." , " deps" , " find_libpython.py" )
121+ try
122+ return read (` $python $script ` , String)
123+ catch
124+ return nothing
125+ end
126+ end
127+
63128# ########################################################################
64129
65130function __init__ ()
@@ -76,7 +141,48 @@ function __init__()
76141
77142 already_inited = 0 != ccall ((@pysym :Py_IsInitialized ), Cint, ())
78143
79- if ! already_inited
144+ if already_inited
145+ # Importing from PyJulia takes this path.
146+ elseif isfile (get (ENV , " PYCALL_JL_RUNTIME_PYTHON" , " " ))
147+ venv_python = ENV [" PYCALL_JL_RUNTIME_PYTHON" ]
148+
149+ # Check libpython compatibility.
150+ venv_libpython = find_libpython (venv_python)
151+ if venv_libpython === nothing
152+ error ("""
153+ `libpython` for $venv_python cannot be found.
154+ PyCall.jl cannot initialize Python safely.
155+ """ )
156+ elseif venv_libpython != libpython
157+ error ("""
158+ Incompatible `libpython` detected.
159+ `libpython` for $venv_python is:
160+ $venv_libpython
161+ `libpython` for $pyprogramname is:
162+ $libpython
163+ PyCall.jl only supports loading Python environment using
164+ the same `libpython`.
165+ """ )
166+ end
167+
168+ if haskey (ENV , " PYCALL_JL_RUNTIME_PYTHONHOME" )
169+ venv_home = ENV [" PYCALL_JL_RUNTIME_PYTHONHOME" ]
170+ else
171+ venv_home = pythonhome_of (venv_python)
172+ end
173+ if pyversion. major < 3
174+ ccall ((@pysym :Py_SetPythonHome ), Cvoid, (Cstring,),
175+ _leak (Cstring, venv_home))
176+ ccall ((@pysym :Py_SetProgramName ), Cvoid, (Cstring,),
177+ _leak (Cstring, venv_python))
178+ else
179+ ccall ((@pysym :Py_SetPythonHome ), Cvoid, (Ptr{Cwchar_t},),
180+ _leak (Cwstring, venv_home))
181+ ccall ((@pysym :Py_SetProgramName ), Cvoid, (Ptr{Cwchar_t},),
182+ _leak (Cwstring, venv_python))
183+ end
184+ ccall ((@pysym :Py_InitializeEx ), Cvoid, (Cint,), 0 )
185+ else
80186 Py_SetPythonHome (libpy_handle, PYTHONHOME, wPYTHONHOME, pyversion)
81187 if ! isempty (pyprogramname)
82188 if pyversion. major < 3
0 commit comments