-
Notifications
You must be signed in to change notification settings - Fork 3
/
pypath.py
269 lines (223 loc) · 10.2 KB
/
pypath.py
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# https://github.com/scikit-build/scikit-build/blob/d06e8724e51885d1d147accfc9f65dd84f1ae7f7/skbuild/cmaker.py より
import configparser
import contextlib
import itertools
import os
import sys
import sysconfig
from pathlib import Path
import distutils.sysconfig as du_sysconfig
def get_python_version() -> str:
"""Get version associated with the current python interpreter.
Returns:
str: python version string
Example:
>>> # xdoc: +IGNORE_WANT
>>> from skbuild.cmaker import CMaker
>>> python_version = CMaker.get_python_version()
>>> print('python_version = {!r}'.format(python_version))
python_version = '3.7'
"""
python_version = sysconfig.get_config_var("VERSION")
if not python_version:
python_version = sysconfig.get_config_var("py_version_short")
if not python_version:
python_version = ".".join(map(str, sys.version_info[:2]))
assert isinstance(python_version, str)
return python_version
# NOTE(opadron): The try-excepts raise the cyclomatic complexity, but we
# need them for this function.
def get_python_include_dir(python_version: str):
"""Get include directory associated with the current python
interpreter.
Args:
python_version (str): python version, may be partial.
Returns:
PathLike: python include dir
Example:
>>> # xdoc: +IGNORE_WANT
>>> from skbuild.cmaker import CMaker
>>> python_version = CMaker.get_python_version()
>>> python_include_dir = CMaker.get_python_include_dir(python_version)
>>> print('python_include_dir = {!r}'.format(python_include_dir))
python_include_dir = '.../conda/envs/py37/include/python3.7m'
"""
# determine python include dir
python_include_dir: str | None = sysconfig.get_config_var("INCLUDEPY")
# if Python.h not found (or python_include_dir is None), try to find a
# suitable include dir
found_python_h = python_include_dir is not None and os.path.exists(
os.path.join(python_include_dir, "Python.h")
)
if not found_python_h:
# NOTE(opadron): these possible prefixes must be guarded against
# AttributeErrors and KeyErrors because they each can throw on
# different platforms or even different builds on the same platform.
include_py: str | None = sysconfig.get_config_var("INCLUDEPY")
include_dir: str | None = sysconfig.get_config_var("INCLUDEDIR")
include: str | None = None
plat_include: str | None = None
python_inc: str | None = None
python_inc2: str | None = None
with contextlib.suppress(AttributeError, KeyError):
include = sysconfig.get_path("include")
with contextlib.suppress(AttributeError, KeyError):
plat_include = sysconfig.get_path("platinclude")
with contextlib.suppress(AttributeError):
python_inc = sysconfig.get_python_inc() # type: ignore[attr-defined]
if include_py is not None:
include_py = os.path.dirname(include_py)
if include is not None:
include = os.path.dirname(include)
if plat_include is not None:
plat_include = os.path.dirname(plat_include)
if python_inc is not None:
python_inc2 = os.path.join(python_inc, ".".join(map(str, sys.version_info[:2])))
all_candidate_prefixes = [
include_py,
include_dir,
include,
plat_include,
python_inc,
python_inc2,
]
candidate_prefixes: list[str] = [pre for pre in all_candidate_prefixes if pre]
candidate_versions: tuple[str, ...] = (python_version,)
if python_version:
candidate_versions += ("",)
pymalloc = None
with contextlib.suppress(AttributeError):
pymalloc = bool(sysconfig.get_config_var("WITH_PYMALLOC"))
if pymalloc:
candidate_versions += (python_version + "m",)
candidates = (
os.path.join(prefix, "".join(("python", ver)))
for (prefix, ver) in itertools.product(candidate_prefixes, candidate_versions)
)
for candidate in candidates:
if os.path.exists(os.path.join(candidate, "Python.h")):
# we found an include directory
python_include_dir = candidate
break
# TODO(opadron): what happens if we don't find an include directory?
# Throw SKBuildError?
return python_include_dir
def get_python_library(python_version: str):
"""Get path to the python library associated with the current python
interpreter.
Args:
python_version (str): python version, may be partial.
Returns:
PathLike: python_library : python shared library
Example:
>>> # xdoc: +IGNORE_WANT
>>> from skbuild.cmaker import CMaker
>>> python_version = CMaker.get_python_version()
>>> python_library = CMaker.get_python_include_dir(python_version)
>>> print('python_library = {!r}'.format(python_library))
python_library = '.../conda/envs/py37/include/python3.7m'
"""
# On Windows, support cross-compiling in the same way as setuptools
# When cross-compiling, check DIST_EXTRA_CONFIG first
config_file = os.environ.get("DIST_EXTRA_CONFIG", None)
if config_file and Path(config_file).is_file():
cp = configparser.ConfigParser()
cp.read(config_file)
result = cp.get("build_ext", "library_dirs", fallback="")
if result:
minor = sys.version_info[1]
return str(Path(result) / f"python3{minor}.lib")
# This seems to be the simplest way to detect the library path with
# modern python versions that avoids the complicated construct below.
# It avoids guessing the library name. Tested with cpython 3.8 and
# pypy 3.8 on Ubuntu.
libdir: str | None = sysconfig.get_config_var("LIBDIR")
ldlibrary: str | None = sysconfig.get_config_var("LDLIBRARY")
if libdir and ldlibrary and os.path.exists(libdir):
if sysconfig.get_config_var("MULTIARCH"):
masd = sysconfig.get_config_var("multiarchsubdir")
if masd:
if masd.startswith(os.sep):
masd = masd[len(os.sep) :]
libdir_masd = os.path.join(libdir, masd)
if os.path.exists(libdir_masd):
libdir = libdir_masd
libpath = os.path.join(libdir, ldlibrary)
if libpath and os.path.exists(libpath):
return libpath
return _guess_python_library(python_version)
def _guess_python_library(python_version: str):
# determine direct path to libpython
python_library: str | None = sysconfig.get_config_var("LIBRARY")
# if static (or nonexistent), try to find a suitable dynamic libpython
if not python_library or os.path.splitext(python_library)[1][-2:] == ".a":
candidate_lib_prefixes = ["", "lib"]
candidate_suffixes = [""]
candidate_implementations = ["python"]
if sys.implementation.name == "pypy":
candidate_implementations[:0] = ["pypy-c", "pypy3-c", "pypy"]
candidate_suffixes.append("-c")
candidate_extensions = [".lib", ".so", ".a"]
# On pypy + MacOS, the variable WITH_DYLD is not set. It would
# actually be possible to determine the python library there using
# LDLIBRARY + LIBDIR. As a simple fix, we check if the LDLIBRARY
# ends with .dylib and add it to the candidate matrix in this case.
with_ld = sysconfig.get_config_var("WITH_DYLD")
ld_lib = sysconfig.get_config_var("LDLIBRARY")
if with_ld or (ld_lib and ld_lib.endswith(".dylib")):
candidate_extensions.insert(0, ".dylib")
candidate_versions = [python_version]
if python_version:
candidate_versions.append("")
candidate_versions.insert(0, "".join(python_version.split(".")[:2]))
abiflags = getattr(sys, "abiflags", "")
candidate_abiflags = [abiflags]
if abiflags:
candidate_abiflags.append("")
# Ensure the value injected by virtualenv is
# returned on windows.
# Because calling `sysconfig.get_config_var('multiarchsubdir')`
# returns an empty string on Linux, `du_sysconfig` is only used to
# get the value of `LIBDIR`.
candidate_libdirs = []
libdir_a = du_sysconfig.get_config_var("LIBDIR")
assert not isinstance(libdir_a, int)
if libdir_a is None:
libdest = sysconfig.get_config_var("LIBDEST")
candidate_libdirs.append(
os.path.abspath(os.path.join(libdest, "..", "libs") if libdest else "libs")
)
libdir_b = sysconfig.get_config_var("LIBDIR")
for libdir in (libdir_a, libdir_b):
if libdir is None:
continue
if sysconfig.get_config_var("MULTIARCH"):
masd = sysconfig.get_config_var("multiarchsubdir")
if masd:
if masd.startswith(os.sep):
masd = masd[len(os.sep) :]
candidate_libdirs.append(os.path.join(libdir, masd))
candidate_libdirs.append(libdir)
candidates = (
os.path.join(libdir, "".join((pre, impl, ver, abi, suf, ext)))
for (libdir, pre, impl, ext, ver, abi, suf) in itertools.product(
candidate_libdirs,
candidate_lib_prefixes,
candidate_implementations,
candidate_extensions,
candidate_versions,
candidate_abiflags,
candidate_suffixes,
)
)
for candidate in candidates:
if os.path.exists(candidate):
# we found a (likely alternate) libpython
python_library = candidate
break
# Temporary workaround for some libraries (opencv) processing the
# string output. Will return None instead of empty string in future
# versions if the library does not exist.
if python_library is None:
return None
return python_library if python_library and os.path.exists(python_library) else ""