Skip to content

Commit 14359fd

Browse files
committed
Support virtual environment
1 parent 51499ed commit 14359fd

1 file changed

Lines changed: 107 additions & 1 deletion

File tree

src/pyinit.jl

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,71 @@ function pyjlwrap_init()
6060
end
6161
end
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

65130
function __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

Comments
 (0)