forked from JuliaPy/PyCall.jl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdepsutils.jl
157 lines (143 loc) · 5.94 KB
/
depsutils.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# Included from build.jl, ../test/test_build.jl and ../src/PyCall.jl
import Libdl
hassym(lib, sym) = Libdl.dlsym_e(lib, sym) != C_NULL
# call dlsym_e on a sequence of symbols and return the symbol that gives
# the first non-null result
function findsym(lib, syms...)
for sym in syms
if hassym(lib, sym)
return sym
end
end
error("no symbol found from: ", syms)
end
# Static buffer to make sure the string passed to libpython persists
# for the lifetime of the program, as CPython API requires:
const __buf_programname = UInt8[]
const __buf_pythonhome = UInt8[]
# Need to set PythonHome before calling GetVersion to avoid warning (#299).
# Unfortunately, this poses something of a chicken-and-egg problem because
# we need to know the Python version to set PythonHome via the API. Note
# that the string (or array) passed to Py_SetPythonHome needs to be a
# constant that lasts for the lifetime of the program, which is why we
# prepare static buffer __buf_pythonhome, copy the string to it, and then
# pass the pointer to the buffer to the CPython API.
function Py_SetPythonHome(libpy, pyversion, PYTHONHOME::AbstractString)
isempty(PYTHONHOME) && return
if pyversion.major < 3
ccall(Libdl.dlsym(libpy, :Py_SetPythonHome), Cvoid, (Cstring,),
_preserveas!(__buf_pythonhome, Cstring, PYTHONHOME))
else
ccall(Libdl.dlsym(libpy, :Py_SetPythonHome), Cvoid, (Ptr{Cwchar_t},),
_preserveas!(__buf_pythonhome, Cwstring, PYTHONHOME))
end
end
function Py_SetProgramName(libpy, pyversion, programname::AbstractString)
isempty(programname) && return
if pyversion.major < 3
ccall(Libdl.dlsym(libpy, :Py_SetProgramName), Cvoid, (Cstring,),
_preserveas!(__buf_programname, Cstring, programname))
else
ccall(Libdl.dlsym(libpy, :Py_SetProgramName), Cvoid, (Ptr{Cwchar_t},),
_preserveas!(__buf_programname, Cwstring, programname))
end
end
"""
_preserveas!(dest::Vector{UInt8}, (Cstring|Cwstring), x::String) :: Ptr
Copy `x` as `Cstring` or `Cwstring` to `dest` and return a pointer to
`dest`. Thus, this pointer is safe to use as long as `dest` is
protected from GC.
"""
function _preserveas!(dest::Vector{UInt8}, ::Type{Cstring}, x::AbstractString)
s = transcode(UInt8, String(x))
copyto!(resize!(dest, length(s) + 1), s)
dest[length(s) + 1] = 0
return pointer(dest)
end
function _preserveas!(dest::Vector{UInt8}, ::Type{Cwstring}, x::AbstractString)
s = reinterpret(UInt8, Base.cconvert(Cwstring, x))
copyto!(resize!(dest, length(s)), s)
return pointer(dest)
end
# need to be able to get the version before Python is initialized
Py_GetVersion(libpy) = unsafe_string(ccall(Libdl.dlsym(libpy, :Py_GetVersion), Ptr{UInt8}, ()))
# Fix the environment for running `python`, and setts IO encoding to UTF-8.
# If cmd is the Conda python, then additionally removes all PYTHON* and
# CONDA* environment variables.
function pythonenv(cmd::Cmd)
env = copy(ENV)
if dirname(cmd.exec[1]) == abspath(Conda.PYTHONDIR)
pythonvars = String[]
for var in keys(env)
if startswith(var, "CONDA") || startswith(var, "PYTHON")
push!(pythonvars, var)
end
end
for var in pythonvars
pop!(env, var)
end
end
# set PYTHONIOENCODING when running python executable, so that
# we get UTF-8 encoded text as output (this is not the default on Windows).
env["PYTHONIOENCODING"] = "UTF-8"
setenv(cmd, env)
end
function pythonhome_of(pyprogramname::AbstractString)
if Sys.iswindows()
# PYTHONHOME tells python where to look for both pure python
# and binary modules. When it is set, it replaces both
# `prefix` and `exec_prefix` and we thus need to set it to
# both in case they differ. This is also what the
# documentation recommends. However, they are documented
# to always be the same on Windows, where it causes
# problems if we try to include both.
script = """
import sys
if hasattr(sys, "base_exec_prefix"):
sys.stdout.write(sys.base_exec_prefix)
else:
sys.stdout.write(sys.exec_prefix)
"""
else
script = """
import sys
if hasattr(sys, "base_exec_prefix"):
sys.stdout.write(sys.base_prefix)
sys.stdout.write(":")
sys.stdout.write(sys.base_exec_prefix)
else:
sys.stdout.write(sys.prefix)
sys.stdout.write(":")
sys.stdout.write(sys.exec_prefix)
"""
# https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHOME
end
return read(pythonenv(`$pyprogramname -c $script`), String)
end
# To support `venv` standard library (as well as `virtualenv`), we
# need to use `sys.base_prefix` and `sys.base_exec_prefix` here.
# Otherwise, initializing Python in `__init__` below fails with
# unrecoverable error:
#
# Fatal Python error: initfsencoding: unable to load the file system codec
# ModuleNotFoundError: No module named 'encodings'
#
# This is because `venv` does not symlink standard libraries like
# `virtualenv`. For example, `lib/python3.X/encodings` does not
# exist. Rather, `venv` relies on the behavior of Python runtime:
#
# If a file named "pyvenv.cfg" exists one directory above
# sys.executable, sys.prefix and sys.exec_prefix are set to that
# directory and it is also checked for site-packages
# --- https://docs.python.org/3/library/venv.html
#
# Thus, we need point `PYTHONHOME` to `sys.base_prefix` and
# `sys.base_exec_prefix`. If the virtual environment is created by
# `virtualenv`, those `sys.base_*` paths point to the virtual
# environment. Thus, above code supports both use cases.
#
# See also:
# * https://docs.python.org/3/library/venv.html
# * https://docs.python.org/3/library/site.html
# * https://docs.python.org/3/library/sys.html#sys.base_exec_prefix
# * https://github.com/JuliaPy/PyCall.jl/issues/410