blob: 71b7162fb7b76c65d9cdf4d974de54855202c53f [file] [edit]
# Copyright 2024 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Replacement module for sysconfig used for CrOS.
Historically, we used to patch sysconfig.py, and many distutils files to handle
cross-compilation correctly. This proved to be difficult and error prone, and
we'd rather simply provide the correct config data we know for CrOS.
"""
# pylint: disable=redefined-builtin
import os
import platform
import sys
def get_python_version():
"""Get the Python version we're compiling for (via EPYTHON)."""
epython = os.environ.get("EPYTHON", "")
if epython.startswith("python"):
return epython[6:]
# EPYTHON unset? Assume we're compiling for this python.
return f"{sys.version_info.major}.{sys.version_info.minor}"
def _get_abi():
"""Get the ABI environment variable, or a fallback if unavailable."""
abi = os.environ.get("ABI")
if abi:
return abi
machine = platform.machine().lower()
is_arm = machine.startswith("arm") or machine.startswith("aarch64")
is_32bit = platform.architecture()[0] == "32bit"
if is_arm and is_32bit:
return "arm"
elif is_arm:
return "arm64"
else:
return "amd64"
_ABI = _get_abi()
_ABIFULL = {
"amd64": "x86_64-linux-gnu",
"arm": "arm-linux-gnueabihf",
"arm64": "aarch64-linux-gnu",
}.get(_ABI, "unknown")
_SOABI = f"cpython-{get_python_version().replace('.', '')}-{_ABIFULL}"
_LIBDIR_NAME = os.environ.get(
f"LIBDIR_{_ABI}", "lib64" if "64" in _ABI else "lib"
)
_CC = os.environ.get("CC", "clang")
_CXX = os.environ.get("CXX", "clang++")
_SYSROOT = os.environ.get("SYSROOT", "/").rstrip("/")
_PREFIX = f"{_SYSROOT}/usr"
_BASE_PREFIX = _PREFIX
_EXEC_PREFIX = _PREFIX
_BASE_EXEC_PREFIX = _PREFIX
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L185C1-L190C1
def _safe_realpath(path):
try:
return os.path.realpath(path)
except OSError:
return path
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L191C1-L197C1
if sys.executable:
_PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
else:
# sys.executable can be empty if argv[0] has been changed and Python is
# unable to retrieve the real program name
_PROJECT_BASE = _safe_realpath(os.getcwd())
# We swap-in this module after the Python build is finished.
_PYTHON_BUILD = False
# Detect and respect venv.
if sys.prefix != sys.base_prefix:
_PREFIX = sys.prefix
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L109
def _getuserbase():
env_base = os.environ.get("PYTHONUSERBASE", None)
if env_base:
return env_base
return os.path.expanduser(os.path.join("~", ".local"))
_CONFIG_VARS = {
"ABIFLAGS": "",
"AR": os.environ.get("AR", "llvm-ar"),
"ARFLAGS": os.environ.get("ARFLAGS", "rcs"),
"BLDLIBRARY": f"-L. -lpython{get_python_version()}",
"BLDSHARED": f"{_CC} -shared",
"BUILDPYTHON": sys.executable,
"CC": _CC,
"CCSHARED": "-fPIC",
"CFLAGS": os.environ.get("CFLAGS", ""),
"CFLAGSFORSHARED": "-fPIC",
"CONFIG_ARGS": os.environ.get("CONFIG_ARGS", ""),
"CONFINCLUDEDIR": f"{_PREFIX}/include",
"CONFINCLUDEPY": f"{_PREFIX}/include/python{get_python_version()}",
"CPPFLAGS": os.environ.get("CPPFLAGS", ""),
"CXX": _CXX,
"CXXFLAGS": os.environ.get("CXXFLAGS", ""),
"DESTLIB": f"/usr/lib/python{get_python_version()}",
"DIRMODE": 755,
"ENSUREPIP": "no",
"EXE": "",
"EXEMODE": 755,
"EXPAT_LDFLAGS": "-lexpat",
"EXT_SUFFIX": f".{_SOABI}.so",
"FILEMODE": 644,
"INCLUDEDIR": f"{_PREFIX}/include",
"INCLUDEPY": f"{_PREFIX}/include/python{get_python_version()}",
"LDCXXSHARED": f"{_CXX} -shared",
"LDFLAGS": os.environ.get("LDFLAGS", ""),
"LDLIBRARY": f"libpython{get_python_version()}.so",
"LDSHARED": f"{_CC} -shared",
"LDVERSION": get_python_version(),
"LIBDEST": f"/usr/lib/python{get_python_version()}",
"LIBDIR": f"{_PREFIX}/{_LIBDIR_NAME}",
"LIBM": "-lm",
"LIBRARY": f"libpython{get_python_version()}.a",
"LIBS": "-ldl",
"LINKCC": _CC,
"LN": "ln",
"MAINCC": _CC,
"OPENSSL_LIBS": "-lssl -lcrypto",
"Py_ENABLE_SHARED": 1,
"SHLIB_SUFFIX": ".so",
"SOABI": _SOABI,
"VERSION": get_python_version(),
# Required for the ensurepip module to work. It contains the path where the
# dev-python/ensurepip-pip and dev-python/ensurepip-setuptools packages are
# installed. This config variable is used here:
# https://github.com/python/cpython/blob/e33b6fccd3969dc2351f6a64d7b0362c36c2be96/Lib/ensurepip/__init__.py#L29
"WHEEL_PKG_DIR": "/usr/lib/python/ensurepip",
"abiflags": "",
"base": _PREFIX,
"exec_prefix": _PREFIX,
"installed_base": _PREFIX,
"installed_platbase": _PREFIX,
"libdir_name": _LIBDIR_NAME,
"platbase": _PREFIX,
"platlibdir": getattr(sys, "platlibdir", "lib"),
"prefix": _PREFIX,
"projectbase": os.path.dirname(sys.executable),
"py_version": get_python_version(),
"py_version_nodot": get_python_version().replace(".", ""),
"py_version_nodot_plat": "",
"py_version_short": get_python_version(),
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L675
"userbase": _getuserbase(),
}
# Keys for get_config_var() that are never converted to Python integers.
_ALWAYS_STR = {
"MACOSX_DEPLOYMENT_TARGET",
}
# Required by distutils.
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/distutils/command/install.py#L44C22-L44C31
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L26C1-L58C11
_INSTALL_SCHEMES = {
"posix_prefix": {
"stdlib": "{installed_base}/{platlibdir}/python{py_version_short}",
"platstdlib": "{platbase}/{platlibdir}/python{py_version_short}",
"purelib": "{base}/lib/python{py_version_short}/site-packages",
# pylint: disable=line-too-long
"platlib": "{platbase}/{platlibdir}/python{py_version_short}/site-packages",
# pylint: disable=line-too-long
"include": "{installed_base}/include/python{py_version_short}{abiflags}",
# pylint: disable=line-too-long
"platinclude": "{installed_platbase}/include/python{py_version_short}{abiflags}",
"scripts": "{base}/bin",
"data": "{base}",
},
"posix_home": {
"stdlib": "{installed_base}/lib/python",
"platstdlib": "{base}/lib/python",
"purelib": "{base}/lib/python",
"platlib": "{base}/lib/python",
"include": "{installed_base}/include/python",
"platinclude": "{installed_base}/include/python",
"scripts": "{base}/bin",
"data": "{base}",
},
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L145
"posix_user": {
"stdlib": "{userbase}/{platlibdir}/python{py_version_short}",
"platstdlib": "{userbase}/{platlibdir}/python{py_version_short}",
"purelib": "{userbase}/lib/python{py_version_short}/site-packages",
"platlib": "{userbase}/lib/python{py_version_short}/site-packages",
"include": "{userbase}/include/python{py_version_short}",
"scripts": "{userbase}/bin",
"data": "{userbase}",
},
"nt": {
# These keys are needed by Python 3.11's distutils module, see
# https://github.com/python/cpython/blob/db85d51d3ea4adfc6147d6af400e167659689eed/Lib/distutils/command/install.py#L42
# lines 42-52. That said, we can leave their values empty because we
# will never build for Windows.
"stdlib": "",
"platstdlib": "",
"purelib": "",
"platlib": "",
"include": "",
"platinclude": "",
"scripts": "",
"data": "",
},
}
def get_config_vars(*args):
if args:
return [_CONFIG_VARS.get(x) for x in args]
return _CONFIG_VARS
def get_config_var(name):
return _CONFIG_VARS.get(name)
def get_platform():
return os.environ.get("_PYTHON_HOST_PLATFORM", "linux-x86_64")
def get_preferred_scheme(key):
if key == "prefix" and sys.prefix != sys.base_prefix:
return "venv"
return {
"prefix": "posix_prefix",
"home": "posix_home",
"user": "posix_user",
}[key]
def get_default_scheme():
return get_preferred_scheme("prefix")
# Meson uses _get_default_scheme :/
_get_default_scheme = get_default_scheme
def get_scheme_names():
return ("posix_prefix", "posix_home", "posix_user", "posix_venv", "venv")
def get_paths(scheme=None, vars=None, expand=True):
if scheme in [None, "posix_venv", "venv"]:
scheme = "posix_prefix"
paths = _INSTALL_SCHEMES[scheme]
if not expand:
return paths
vars = {**_CONFIG_VARS, **(vars or {})}
return {k: v.format(**vars) for k, v in paths.items()}
def get_path(name, scheme=None, vars=None, expand=True):
return get_paths(scheme, vars, expand)[name]
def is_python_build(check_home: bool = False) -> bool:
# We swap-in this module after the Python build is finished.
del check_home
return False
# Required by distutils.
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/distutils/sysconfig.py#L29
# pylint: disable=unused-argument
def _init_posix(vars):
pass
# Required by distutils.
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/distutils/sysconfig.py#L32
# pylint: disable=unused-argument
def _init_non_posix(vars):
pass
# Required by distutils.
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/distutils/sysconfig.py#L34
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L180C1-L182C46
_variable_rx = r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)"
_findvar1_rx = r"\$\(([A-Za-z][A-Za-z0-9_]*)\)"
_findvar2_rx = r"\${([A-Za-z][A-Za-z0-9_]*)}"
# Required by distutils.
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/distutils/sysconfig.py#L30
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L553
def parse_config_h(fp, vars=None):
"""Parse a config.h-style file.
A dictionary containing name/value pairs is returned. If an
optional dictionary is passed in as the second argument, it is
used instead of a new dictionary.
"""
if vars is None:
vars = {}
import re
define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
while True:
line = fp.readline()
if not line:
break
m = define_rx.match(line)
if m:
n, v = m.group(1, 2)
try:
if n in _ALWAYS_STR:
raise ValueError
v = int(v)
except ValueError:
pass
vars[n] = v
else:
m = undef_rx.match(line)
if m:
vars[m.group(1)] = 0
return vars
# Required by distutils.
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/distutils/sysconfig.py#L38C5-L38C25
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L803
def expand_makefile_vars(s, vars):
import re
while True:
m = re.search(_findvar1_rx, s) or re.search(_findvar2_rx, s)
if m:
(beg, end) = m.span()
s = s[0:beg] + vars.get(m.group(1)) + s[end:]
else:
break
return s
# Required by distutils.
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/distutils/sysconfig.py#L40
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L587
def get_config_h_filename():
return os.path.join(get_path("platinclude"), "pyconfig.h")
# Required by distutils.
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/distutils/sysconfig.py#L43
# https://github.com/python/cpython/blob/795f2597a4be988e2bb19b69ff9958e981cb894e/Lib/sysconfig.py#L443
def get_makefile_filename():
return os.path.join(get_path("stdlib"), "config", "Makefile")
def _main():
print(f"Platform: {get_platform()!r}")
print(f"Python version: {get_python_version()!r}")
print(f"Current installation scheme: {get_default_scheme()!r}")
print()
print("Paths:")
for name, value in sorted(get_paths().items()):
print(f"\t{name} = {value!r}")
print()
print("Variables:")
for name, value in sorted(_CONFIG_VARS.items()):
print(f"\t{name} = {value!r}")
if __name__ == "__main__":
_main()