up
This commit is contained in:
@@ -4,35 +4,22 @@ Miscellaneous utility functions -- anything that doesn't fit into
|
||||
one of the other *util.py modules.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import importlib.util
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import tempfile
|
||||
from collections.abc import Callable, Iterable, Mapping
|
||||
from typing import TYPE_CHECKING, AnyStr
|
||||
import functools
|
||||
|
||||
from jaraco.functools import pass_none
|
||||
|
||||
from ._log import log
|
||||
from ._modified import newer
|
||||
from .errors import DistutilsByteCompileError, DistutilsPlatformError
|
||||
from .spawn import spawn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import TypeVarTuple, Unpack
|
||||
|
||||
_Ts = TypeVarTuple("_Ts")
|
||||
from distutils.errors import DistutilsPlatformError, DistutilsByteCompileError
|
||||
from distutils.dep_util import newer
|
||||
from distutils.spawn import spawn
|
||||
from distutils import log
|
||||
|
||||
|
||||
def get_host_platform() -> str:
|
||||
def get_host_platform():
|
||||
"""
|
||||
Return a string that identifies the current platform. Use this
|
||||
function to distinguish platform-specific build directories and
|
||||
@@ -41,12 +28,27 @@ def get_host_platform() -> str:
|
||||
|
||||
# This function initially exposed platforms as defined in Python 3.9
|
||||
# even with older Python versions when distutils was split out.
|
||||
# Now it delegates to stdlib sysconfig.
|
||||
# Now it delegates to stdlib sysconfig, but maintains compatibility.
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
if os.name == 'nt':
|
||||
if '(arm)' in sys.version.lower():
|
||||
return 'win-arm32'
|
||||
if '(arm64)' in sys.version.lower():
|
||||
return 'win-arm64'
|
||||
|
||||
if sys.version_info < (3, 9):
|
||||
if os.name == "posix" and hasattr(os, 'uname'):
|
||||
osname, host, release, version, machine = os.uname()
|
||||
if osname[:3] == "aix":
|
||||
from .py38compat import aix_platform
|
||||
|
||||
return aix_platform(osname, version, release)
|
||||
|
||||
return sysconfig.get_platform()
|
||||
|
||||
|
||||
def get_platform() -> str:
|
||||
def get_platform():
|
||||
if os.name == 'nt':
|
||||
TARGET_TO_PLAT = {
|
||||
'x86': 'win32',
|
||||
@@ -107,41 +109,49 @@ def get_macosx_target_ver():
|
||||
):
|
||||
my_msg = (
|
||||
'$' + MACOSX_VERSION_VAR + ' mismatch: '
|
||||
f'now "{env_ver}" but "{syscfg_ver}" during configure; '
|
||||
'must use 10.3 or later'
|
||||
'now "%s" but "%s" during configure; '
|
||||
'must use 10.3 or later' % (env_ver, syscfg_ver)
|
||||
)
|
||||
raise DistutilsPlatformError(my_msg)
|
||||
return env_ver
|
||||
return syscfg_ver
|
||||
|
||||
|
||||
def split_version(s: str) -> list[int]:
|
||||
def split_version(s):
|
||||
"""Convert a dot-separated string into a list of numbers for comparisons"""
|
||||
return [int(n) for n in s.split('.')]
|
||||
|
||||
|
||||
@pass_none
|
||||
def convert_path(pathname: str | os.PathLike[str]) -> str:
|
||||
r"""
|
||||
Allow for pathlib.Path inputs, coax to a native path string.
|
||||
|
||||
If None is passed, will just pass it through as
|
||||
Setuptools relies on this behavior.
|
||||
|
||||
>>> convert_path(None) is None
|
||||
True
|
||||
|
||||
Removes empty paths.
|
||||
|
||||
>>> convert_path('foo/./bar').replace('\\', '/')
|
||||
'foo/bar'
|
||||
def convert_path(pathname):
|
||||
"""Return 'pathname' as a name that will work on the native filesystem,
|
||||
i.e. split it on '/' and put it back together again using the current
|
||||
directory separator. Needed because filenames in the setup script are
|
||||
always supplied in Unix style, and have to be converted to the local
|
||||
convention before we can actually use them in the filesystem. Raises
|
||||
ValueError on non-Unix-ish systems if 'pathname' either starts or
|
||||
ends with a slash.
|
||||
"""
|
||||
return os.fspath(pathlib.PurePath(pathname))
|
||||
if os.sep == '/':
|
||||
return pathname
|
||||
if not pathname:
|
||||
return pathname
|
||||
if pathname[0] == '/':
|
||||
raise ValueError("path '%s' cannot be absolute" % pathname)
|
||||
if pathname[-1] == '/':
|
||||
raise ValueError("path '%s' cannot end with '/'" % pathname)
|
||||
|
||||
paths = pathname.split('/')
|
||||
while '.' in paths:
|
||||
paths.remove('.')
|
||||
if not paths:
|
||||
return os.curdir
|
||||
return os.path.join(*paths)
|
||||
|
||||
|
||||
def change_root(
|
||||
new_root: AnyStr | os.PathLike[AnyStr], pathname: AnyStr | os.PathLike[AnyStr]
|
||||
) -> AnyStr:
|
||||
# convert_path ()
|
||||
|
||||
|
||||
def change_root(new_root, pathname):
|
||||
"""Return 'pathname' with 'new_root' prepended. If 'pathname' is
|
||||
relative, this is equivalent to "os.path.join(new_root,pathname)".
|
||||
Otherwise, it requires making 'pathname' relative and then joining the
|
||||
@@ -155,15 +165,15 @@ def change_root(
|
||||
|
||||
elif os.name == 'nt':
|
||||
(drive, path) = os.path.splitdrive(pathname)
|
||||
if path[0] == os.sep:
|
||||
if path[0] == '\\':
|
||||
path = path[1:]
|
||||
return os.path.join(new_root, path)
|
||||
|
||||
raise DistutilsPlatformError(f"nothing known about platform '{os.name}'")
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def check_environ() -> None:
|
||||
@functools.lru_cache()
|
||||
def check_environ():
|
||||
"""Ensure that 'os.environ' has all the environment variables we
|
||||
guarantee that users can use in config files, command-line options,
|
||||
etc. Currently this includes:
|
||||
@@ -185,7 +195,7 @@ def check_environ() -> None:
|
||||
os.environ['PLAT'] = get_platform()
|
||||
|
||||
|
||||
def subst_vars(s, local_vars: Mapping[str, object]) -> str:
|
||||
def subst_vars(s, local_vars):
|
||||
"""
|
||||
Perform variable substitution on 'string'.
|
||||
Variables are indicated by format-style braces ("{var}").
|
||||
@@ -218,13 +228,13 @@ def _subst_compat(s):
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"shell/Perl-style substitutions are deprecated",
|
||||
"shell/Perl-style substitions are deprecated",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return repl
|
||||
|
||||
|
||||
def grok_environment_error(exc: object, prefix: str = "error: ") -> str:
|
||||
def grok_environment_error(exc, prefix="error: "):
|
||||
# Function kept for backward compatibility.
|
||||
# Used to try clever things with EnvironmentErrors,
|
||||
# but nowadays str(exception) produces good messages.
|
||||
@@ -237,12 +247,12 @@ _wordchars_re = _squote_re = _dquote_re = None
|
||||
|
||||
def _init_regex():
|
||||
global _wordchars_re, _squote_re, _dquote_re
|
||||
_wordchars_re = re.compile(rf'[^\\\'\"{string.whitespace} ]*')
|
||||
_wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)
|
||||
_squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")
|
||||
_dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
|
||||
|
||||
|
||||
def split_quoted(s: str) -> list[str]:
|
||||
def split_quoted(s):
|
||||
"""Split a string up according to Unix shell-like rules for quotes and
|
||||
backslashes. In short: words are delimited by spaces, as long as those
|
||||
spaces are not escaped by a backslash, or inside a quoted string.
|
||||
@@ -289,10 +299,10 @@ def split_quoted(s: str) -> list[str]:
|
||||
elif s[end] == '"': # slurp doubly-quoted string
|
||||
m = _dquote_re.match(s, end)
|
||||
else:
|
||||
raise RuntimeError(f"this can't happen (bad char '{s[end]}')")
|
||||
raise RuntimeError("this can't happen (bad char '%c')" % s[end])
|
||||
|
||||
if m is None:
|
||||
raise ValueError(f"bad string (mismatched {s[end]} quotes?)")
|
||||
raise ValueError("bad string (mismatched %s quotes?)" % s[end])
|
||||
|
||||
(beg, end) = m.span()
|
||||
s = s[:beg] + s[beg + 1 : end - 1] + s[end:]
|
||||
@@ -308,24 +318,17 @@ def split_quoted(s: str) -> list[str]:
|
||||
# split_quoted ()
|
||||
|
||||
|
||||
def execute(
|
||||
func: Callable[[Unpack[_Ts]], object],
|
||||
args: tuple[Unpack[_Ts]],
|
||||
msg: object = None,
|
||||
verbose: bool = False,
|
||||
dry_run: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Perform some action that affects the outside world (e.g. by
|
||||
writing to the filesystem). Such actions are special because they
|
||||
are disabled by the 'dry_run' flag. This method handles that
|
||||
complication; simply supply the
|
||||
def execute(func, args, msg=None, verbose=0, dry_run=0):
|
||||
"""Perform some action that affects the outside world (eg. by
|
||||
writing to the filesystem). Such actions are special because they
|
||||
are disabled by the 'dry_run' flag. This method takes care of all
|
||||
that bureaucracy for you; all you have to do is supply the
|
||||
function to call and an argument tuple for it (to embody the
|
||||
"external action" being performed) and an optional message to
|
||||
emit.
|
||||
"external action" being performed), and an optional message to
|
||||
print.
|
||||
"""
|
||||
if msg is None:
|
||||
msg = f"{func.__name__}{args!r}"
|
||||
msg = "{}{!r}".format(func.__name__, args)
|
||||
if msg[-2:] == ',)': # correct for singleton tuple
|
||||
msg = msg[0:-2] + ')'
|
||||
|
||||
@@ -334,7 +337,7 @@ def execute(
|
||||
func(*args)
|
||||
|
||||
|
||||
def strtobool(val: str) -> bool:
|
||||
def strtobool(val):
|
||||
"""Convert a string representation of truth to true (1) or false (0).
|
||||
|
||||
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
|
||||
@@ -343,23 +346,23 @@ def strtobool(val: str) -> bool:
|
||||
"""
|
||||
val = val.lower()
|
||||
if val in ('y', 'yes', 't', 'true', 'on', '1'):
|
||||
return True
|
||||
return 1
|
||||
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
|
||||
return False
|
||||
return 0
|
||||
else:
|
||||
raise ValueError(f"invalid truth value {val!r}")
|
||||
raise ValueError("invalid truth value {!r}".format(val))
|
||||
|
||||
|
||||
def byte_compile( # noqa: C901
|
||||
py_files: Iterable[str],
|
||||
optimize: int = 0,
|
||||
force: bool = False,
|
||||
prefix: str | None = None,
|
||||
base_dir: str | None = None,
|
||||
verbose: bool = True,
|
||||
dry_run: bool = False,
|
||||
direct: bool | None = None,
|
||||
) -> None:
|
||||
py_files,
|
||||
optimize=0,
|
||||
force=0,
|
||||
prefix=None,
|
||||
base_dir=None,
|
||||
verbose=1,
|
||||
dry_run=0,
|
||||
direct=None,
|
||||
):
|
||||
"""Byte-compile a collection of Python source files to .pyc
|
||||
files in a __pycache__ subdirectory. 'py_files' is a list
|
||||
of files to compile; any files that don't end in ".py" are silently
|
||||
@@ -409,10 +412,20 @@ def byte_compile( # noqa: C901
|
||||
# "Indirect" byte-compilation: write a temporary script and then
|
||||
# run it with the appropriate flags.
|
||||
if not direct:
|
||||
(script_fd, script_name) = tempfile.mkstemp(".py")
|
||||
try:
|
||||
from tempfile import mkstemp
|
||||
|
||||
(script_fd, script_name) = mkstemp(".py")
|
||||
except ImportError:
|
||||
from tempfile import mktemp
|
||||
|
||||
(script_fd, script_name) = None, mktemp(".py")
|
||||
log.info("writing byte-compilation script '%s'", script_name)
|
||||
if not dry_run:
|
||||
script = os.fdopen(script_fd, "w", encoding='utf-8')
|
||||
if script_fd is not None:
|
||||
script = os.fdopen(script_fd, "w")
|
||||
else:
|
||||
script = open(script_name, "w")
|
||||
|
||||
with script:
|
||||
script.write(
|
||||
@@ -434,19 +447,20 @@ files = [
|
||||
|
||||
script.write(",\n".join(map(repr, py_files)) + "]\n")
|
||||
script.write(
|
||||
f"""
|
||||
byte_compile(files, optimize={optimize!r}, force={force!r},
|
||||
prefix={prefix!r}, base_dir={base_dir!r},
|
||||
verbose={verbose!r}, dry_run=False,
|
||||
direct=True)
|
||||
"""
|
||||
byte_compile(files, optimize=%r, force=%r,
|
||||
prefix=%r, base_dir=%r,
|
||||
verbose=%r, dry_run=0,
|
||||
direct=1)
|
||||
"""
|
||||
% (optimize, force, prefix, base_dir, verbose)
|
||||
)
|
||||
|
||||
cmd = [sys.executable]
|
||||
cmd.extend(subprocess._optim_args_from_interpreter_flags())
|
||||
cmd.append(script_name)
|
||||
spawn(cmd, dry_run=dry_run)
|
||||
execute(os.remove, (script_name,), f"removing {script_name}", dry_run=dry_run)
|
||||
execute(os.remove, (script_name,), "removing %s" % script_name, dry_run=dry_run)
|
||||
|
||||
# "Direct" byte-compilation: use the py_compile module to compile
|
||||
# right here, right now. Note that the script generated in indirect
|
||||
@@ -473,7 +487,8 @@ byte_compile(files, optimize={optimize!r}, force={force!r},
|
||||
if prefix:
|
||||
if file[: len(prefix)] != prefix:
|
||||
raise ValueError(
|
||||
f"invalid prefix: filename {file!r} doesn't start with {prefix!r}"
|
||||
"invalid prefix: filename %r doesn't start with %r"
|
||||
% (file, prefix)
|
||||
)
|
||||
dfile = dfile[len(prefix) :]
|
||||
if base_dir:
|
||||
@@ -489,30 +504,10 @@ byte_compile(files, optimize={optimize!r}, force={force!r},
|
||||
log.debug("skipping byte-compilation of %s to %s", file, cfile_base)
|
||||
|
||||
|
||||
def rfc822_escape(header: str) -> str:
|
||||
def rfc822_escape(header):
|
||||
"""Return a version of the string escaped for inclusion in an
|
||||
RFC-822 header, by ensuring there are 8 spaces space after each newline.
|
||||
"""
|
||||
indent = 8 * " "
|
||||
lines = header.splitlines(keepends=True)
|
||||
|
||||
# Emulate the behaviour of `str.split`
|
||||
# (the terminal line break in `splitlines` does not result in an extra line):
|
||||
ends_in_newline = lines and lines[-1].splitlines()[0] != lines[-1]
|
||||
suffix = indent if ends_in_newline else ""
|
||||
|
||||
return indent.join(lines) + suffix
|
||||
|
||||
|
||||
def is_mingw() -> bool:
|
||||
"""Returns True if the current platform is mingw.
|
||||
|
||||
Python compiled with Mingw-w64 has sys.platform == 'win32' and
|
||||
get_platform() starts with 'mingw'.
|
||||
"""
|
||||
return sys.platform == 'win32' and get_platform().startswith('mingw')
|
||||
|
||||
|
||||
def is_freethreaded():
|
||||
"""Return True if the Python interpreter is built with free threading support."""
|
||||
return bool(sysconfig.get_config_var('Py_GIL_DISABLED'))
|
||||
lines = header.split('\n')
|
||||
sep = '\n' + 8 * ' '
|
||||
return sep.join(lines)
|
||||
|
||||
Reference in New Issue
Block a user