Path: blob/main/test/lib/python3.9/site-packages/setuptools/_distutils/util.py
4799 views
"""distutils.util12Miscellaneous utility functions -- anything that doesn't fit into3one of the other *util.py modules.4"""56import os7import re8import importlib.util9import string10import sys11import sysconfig12from distutils.errors import DistutilsPlatformError13from distutils.dep_util import newer14from distutils.spawn import spawn15from distutils import log16from distutils.errors import DistutilsByteCompileError17from .py35compat import _optim_args_from_interpreter_flags181920def get_host_platform():21"""Return a string that identifies the current platform. This is used mainly to22distinguish platform-specific build directories and platform-specific built23distributions.24"""2526# We initially exposed platforms as defined in Python 3.927# even with older Python versions when distutils was split out.28# Now that we delegate to stdlib sysconfig we need to restore this29# in case anyone has started to depend on it.3031if sys.version_info < (3, 8):32if os.name == 'nt':33if '(arm)' in sys.version.lower():34return 'win-arm32'35if '(arm64)' in sys.version.lower():36return 'win-arm64'3738if sys.version_info < (3, 9):39if os.name == "posix" and hasattr(os, 'uname'):40osname, host, release, version, machine = os.uname()41if osname[:3] == "aix":42from .py38compat import aix_platform43return aix_platform(osname, version, release)4445return sysconfig.get_platform()4647def get_platform():48if os.name == 'nt':49TARGET_TO_PLAT = {50'x86' : 'win32',51'x64' : 'win-amd64',52'arm' : 'win-arm32',53'arm64': 'win-arm64',54}55return TARGET_TO_PLAT.get(os.environ.get('VSCMD_ARG_TGT_ARCH')) or get_host_platform()56else:57return get_host_platform()585960if sys.platform == 'darwin':61_syscfg_macosx_ver = None # cache the version pulled from sysconfig62MACOSX_VERSION_VAR = 'MACOSX_DEPLOYMENT_TARGET'6364def _clear_cached_macosx_ver():65"""For testing only. Do not call."""66global _syscfg_macosx_ver67_syscfg_macosx_ver = None6869def get_macosx_target_ver_from_syscfg():70"""Get the version of macOS latched in the Python interpreter configuration.71Returns the version as a string or None if can't obtain one. Cached."""72global _syscfg_macosx_ver73if _syscfg_macosx_ver is None:74from distutils import sysconfig75ver = sysconfig.get_config_var(MACOSX_VERSION_VAR) or ''76if ver:77_syscfg_macosx_ver = ver78return _syscfg_macosx_ver7980def get_macosx_target_ver():81"""Return the version of macOS for which we are building.8283The target version defaults to the version in sysconfig latched at time84the Python interpreter was built, unless overridden by an environment85variable. If neither source has a value, then None is returned"""8687syscfg_ver = get_macosx_target_ver_from_syscfg()88env_ver = os.environ.get(MACOSX_VERSION_VAR)8990if env_ver:91# Validate overridden version against sysconfig version, if have both.92# Ensure that the deployment target of the build process is not less93# than 10.3 if the interpreter was built for 10.3 or later. This94# ensures extension modules are built with correct compatibility95# values, specifically LDSHARED which can use96# '-undefined dynamic_lookup' which only works on >= 10.3.97if syscfg_ver and split_version(syscfg_ver) >= [10, 3] and \98split_version(env_ver) < [10, 3]:99my_msg = ('$' + MACOSX_VERSION_VAR + ' mismatch: '100'now "%s" but "%s" during configure; '101'must use 10.3 or later'102% (env_ver, syscfg_ver))103raise DistutilsPlatformError(my_msg)104return env_ver105return syscfg_ver106107108def split_version(s):109"""Convert a dot-separated string into a list of numbers for comparisons"""110return [int(n) for n in s.split('.')]111112113def convert_path (pathname):114"""Return 'pathname' as a name that will work on the native filesystem,115i.e. split it on '/' and put it back together again using the current116directory separator. Needed because filenames in the setup script are117always supplied in Unix style, and have to be converted to the local118convention before we can actually use them in the filesystem. Raises119ValueError on non-Unix-ish systems if 'pathname' either starts or120ends with a slash.121"""122if os.sep == '/':123return pathname124if not pathname:125return pathname126if pathname[0] == '/':127raise ValueError("path '%s' cannot be absolute" % pathname)128if pathname[-1] == '/':129raise ValueError("path '%s' cannot end with '/'" % pathname)130131paths = pathname.split('/')132while '.' in paths:133paths.remove('.')134if not paths:135return os.curdir136return os.path.join(*paths)137138# convert_path ()139140141def change_root (new_root, pathname):142"""Return 'pathname' with 'new_root' prepended. If 'pathname' is143relative, this is equivalent to "os.path.join(new_root,pathname)".144Otherwise, it requires making 'pathname' relative and then joining the145two, which is tricky on DOS/Windows and Mac OS.146"""147if os.name == 'posix':148if not os.path.isabs(pathname):149return os.path.join(new_root, pathname)150else:151return os.path.join(new_root, pathname[1:])152153elif os.name == 'nt':154(drive, path) = os.path.splitdrive(pathname)155if path[0] == '\\':156path = path[1:]157return os.path.join(new_root, path)158159else:160raise DistutilsPlatformError("nothing known about platform '%s'" % os.name)161162163_environ_checked = 0164def check_environ ():165"""Ensure that 'os.environ' has all the environment variables we166guarantee that users can use in config files, command-line options,167etc. Currently this includes:168HOME - user's home directory (Unix only)169PLAT - description of the current platform, including hardware170and OS (see 'get_platform()')171"""172global _environ_checked173if _environ_checked:174return175176if os.name == 'posix' and 'HOME' not in os.environ:177try:178import pwd179os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]180except (ImportError, KeyError):181# bpo-10496: if the current user identifier doesn't exist in the182# password database, do nothing183pass184185if 'PLAT' not in os.environ:186os.environ['PLAT'] = get_platform()187188_environ_checked = 1189190191def subst_vars (s, local_vars):192"""193Perform variable substitution on 'string'.194Variables are indicated by format-style braces ("{var}").195Variable is substituted by the value found in the 'local_vars'196dictionary or in 'os.environ' if it's not in 'local_vars'.197'os.environ' is first checked/augmented to guarantee that it contains198certain values: see 'check_environ()'. Raise ValueError for any199variables not found in either 'local_vars' or 'os.environ'.200"""201check_environ()202lookup = dict(os.environ)203lookup.update((name, str(value)) for name, value in local_vars.items())204try:205return _subst_compat(s).format_map(lookup)206except KeyError as var:207raise ValueError(f"invalid variable {var}")208209# subst_vars ()210211212def _subst_compat(s):213"""214Replace shell/Perl-style variable substitution with215format-style. For compatibility.216"""217def _subst(match):218return f'{{{match.group(1)}}}'219repl = re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s)220if repl != s:221import warnings222warnings.warn(223"shell/Perl-style substitions are deprecated",224DeprecationWarning,225)226return repl227228229def grok_environment_error (exc, prefix="error: "):230# Function kept for backward compatibility.231# Used to try clever things with EnvironmentErrors,232# but nowadays str(exception) produces good messages.233return prefix + str(exc)234235236# Needed by 'split_quoted()'237_wordchars_re = _squote_re = _dquote_re = None238def _init_regex():239global _wordchars_re, _squote_re, _dquote_re240_wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)241_squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")242_dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')243244def split_quoted (s):245"""Split a string up according to Unix shell-like rules for quotes and246backslashes. In short: words are delimited by spaces, as long as those247spaces are not escaped by a backslash, or inside a quoted string.248Single and double quotes are equivalent, and the quote characters can249be backslash-escaped. The backslash is stripped from any two-character250escape sequence, leaving only the escaped character. The quote251characters are stripped from any quoted string. Returns a list of252words.253"""254255# This is a nice algorithm for splitting up a single string, since it256# doesn't require character-by-character examination. It was a little257# bit of a brain-bender to get it working right, though...258if _wordchars_re is None: _init_regex()259260s = s.strip()261words = []262pos = 0263264while s:265m = _wordchars_re.match(s, pos)266end = m.end()267if end == len(s):268words.append(s[:end])269break270271if s[end] in string.whitespace: # unescaped, unquoted whitespace: now272words.append(s[:end]) # we definitely have a word delimiter273s = s[end:].lstrip()274pos = 0275276elif s[end] == '\\': # preserve whatever is being escaped;277# will become part of the current word278s = s[:end] + s[end+1:]279pos = end+1280281else:282if s[end] == "'": # slurp singly-quoted string283m = _squote_re.match(s, end)284elif s[end] == '"': # slurp doubly-quoted string285m = _dquote_re.match(s, end)286else:287raise RuntimeError("this can't happen (bad char '%c')" % s[end])288289if m is None:290raise ValueError("bad string (mismatched %s quotes?)" % s[end])291292(beg, end) = m.span()293s = s[:beg] + s[beg+1:end-1] + s[end:]294pos = m.end() - 2295296if pos >= len(s):297words.append(s)298break299300return words301302# split_quoted ()303304305def execute (func, args, msg=None, verbose=0, dry_run=0):306"""Perform some action that affects the outside world (eg. by307writing to the filesystem). Such actions are special because they308are disabled by the 'dry_run' flag. This method takes care of all309that bureaucracy for you; all you have to do is supply the310function to call and an argument tuple for it (to embody the311"external action" being performed), and an optional message to312print.313"""314if msg is None:315msg = "%s%r" % (func.__name__, args)316if msg[-2:] == ',)': # correct for singleton tuple317msg = msg[0:-2] + ')'318319log.info(msg)320if not dry_run:321func(*args)322323324def strtobool (val):325"""Convert a string representation of truth to true (1) or false (0).326327True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values328are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if329'val' is anything else.330"""331val = val.lower()332if val in ('y', 'yes', 't', 'true', 'on', '1'):333return 1334elif val in ('n', 'no', 'f', 'false', 'off', '0'):335return 0336else:337raise ValueError("invalid truth value %r" % (val,))338339340def byte_compile (py_files,341optimize=0, force=0,342prefix=None, base_dir=None,343verbose=1, dry_run=0,344direct=None):345"""Byte-compile a collection of Python source files to .pyc346files in a __pycache__ subdirectory. 'py_files' is a list347of files to compile; any files that don't end in ".py" are silently348skipped. 'optimize' must be one of the following:3490 - don't optimize3501 - normal optimization (like "python -O")3512 - extra optimization (like "python -OO")352If 'force' is true, all files are recompiled regardless of353timestamps.354355The source filename encoded in each bytecode file defaults to the356filenames listed in 'py_files'; you can modify these with 'prefix' and357'basedir'. 'prefix' is a string that will be stripped off of each358source filename, and 'base_dir' is a directory name that will be359prepended (after 'prefix' is stripped). You can supply either or both360(or neither) of 'prefix' and 'base_dir', as you wish.361362If 'dry_run' is true, doesn't actually do anything that would363affect the filesystem.364365Byte-compilation is either done directly in this interpreter process366with the standard py_compile module, or indirectly by writing a367temporary script and executing it. Normally, you should let368'byte_compile()' figure out to use direct compilation or not (see369the source for details). The 'direct' flag is used by the script370generated in indirect mode; unless you know what you're doing, leave371it set to None.372"""373374# Late import to fix a bootstrap issue: _posixsubprocess is built by375# setup.py, but setup.py uses distutils.376import subprocess377378# nothing is done if sys.dont_write_bytecode is True379if sys.dont_write_bytecode:380raise DistutilsByteCompileError('byte-compiling is disabled.')381382# First, if the caller didn't force us into direct or indirect mode,383# figure out which mode we should be in. We take a conservative384# approach: choose direct mode *only* if the current interpreter is385# in debug mode and optimize is 0. If we're not in debug mode (-O386# or -OO), we don't know which level of optimization this387# interpreter is running with, so we can't do direct388# byte-compilation and be certain that it's the right thing. Thus,389# always compile indirectly if the current interpreter is in either390# optimize mode, or if either optimization level was requested by391# the caller.392if direct is None:393direct = (__debug__ and optimize == 0)394395# "Indirect" byte-compilation: write a temporary script and then396# run it with the appropriate flags.397if not direct:398try:399from tempfile import mkstemp400(script_fd, script_name) = mkstemp(".py")401except ImportError:402from tempfile import mktemp403(script_fd, script_name) = None, mktemp(".py")404log.info("writing byte-compilation script '%s'", script_name)405if not dry_run:406if script_fd is not None:407script = os.fdopen(script_fd, "w")408else:409script = open(script_name, "w")410411with script:412script.write("""\413from distutils.util import byte_compile414files = [415""")416417# XXX would be nice to write absolute filenames, just for418# safety's sake (script should be more robust in the face of419# chdir'ing before running it). But this requires abspath'ing420# 'prefix' as well, and that breaks the hack in build_lib's421# 'byte_compile()' method that carefully tacks on a trailing422# slash (os.sep really) to make sure the prefix here is "just423# right". This whole prefix business is rather delicate -- the424# problem is that it's really a directory, but I'm treating it425# as a dumb string, so trailing slashes and so forth matter.426427#py_files = map(os.path.abspath, py_files)428#if prefix:429# prefix = os.path.abspath(prefix)430431script.write(",\n".join(map(repr, py_files)) + "]\n")432script.write("""433byte_compile(files, optimize=%r, force=%r,434prefix=%r, base_dir=%r,435verbose=%r, dry_run=0,436direct=1)437""" % (optimize, force, prefix, base_dir, verbose))438439cmd = [sys.executable]440cmd.extend(_optim_args_from_interpreter_flags())441cmd.append(script_name)442spawn(cmd, dry_run=dry_run)443execute(os.remove, (script_name,), "removing %s" % script_name,444dry_run=dry_run)445446# "Direct" byte-compilation: use the py_compile module to compile447# right here, right now. Note that the script generated in indirect448# mode simply calls 'byte_compile()' in direct mode, a weird sort of449# cross-process recursion. Hey, it works!450else:451from py_compile import compile452453for file in py_files:454if file[-3:] != ".py":455# This lets us be lazy and not filter filenames in456# the "install_lib" command.457continue458459# Terminology from the py_compile module:460# cfile - byte-compiled file461# dfile - purported source filename (same as 'file' by default)462if optimize >= 0:463opt = '' if optimize == 0 else optimize464cfile = importlib.util.cache_from_source(465file, optimization=opt)466else:467cfile = importlib.util.cache_from_source(file)468dfile = file469if prefix:470if file[:len(prefix)] != prefix:471raise ValueError("invalid prefix: filename %r doesn't start with %r"472% (file, prefix))473dfile = dfile[len(prefix):]474if base_dir:475dfile = os.path.join(base_dir, dfile)476477cfile_base = os.path.basename(cfile)478if direct:479if force or newer(file, cfile):480log.info("byte-compiling %s to %s", file, cfile_base)481if not dry_run:482compile(file, cfile, dfile)483else:484log.debug("skipping byte-compilation of %s to %s",485file, cfile_base)486487# byte_compile ()488489def rfc822_escape (header):490"""Return a version of the string escaped for inclusion in an491RFC-822 header, by ensuring there are 8 spaces space after each newline.492"""493lines = header.split('\n')494sep = '\n' + 8 * ' '495return sep.join(lines)496497498