Path: blob/master/venv/Lib/site-packages/setuptools/_distutils/util.py
811 views
"""distutils.util12Miscellaneous utility functions -- anything that doesn't fit into3one of the other *util.py modules.4"""56import os7import re8import importlib.util9import string10import sys11from distutils.errors import DistutilsPlatformError12from distutils.dep_util import newer13from distutils.spawn import spawn14from distutils import log15from distutils.errors import DistutilsByteCompileError1617def get_host_platform():18"""Return a string that identifies the current platform. This is used mainly to19distinguish platform-specific build directories and platform-specific built20distributions. Typically includes the OS name and version and the21architecture (as supplied by 'os.uname()'), although the exact information22included depends on the OS; eg. on Linux, the kernel version isn't23particularly important.2425Examples of returned values:26linux-i58627linux-alpha (?)28solaris-2.6-sun4u2930Windows will return one of:31win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)32win32 (all others - specifically, sys.platform is returned)3334For other non-POSIX platforms, currently just returns 'sys.platform'.3536"""37if os.name == 'nt':38if 'amd64' in sys.version.lower():39return 'win-amd64'40if '(arm)' in sys.version.lower():41return 'win-arm32'42if '(arm64)' in sys.version.lower():43return 'win-arm64'44return sys.platform4546# Set for cross builds explicitly47if "_PYTHON_HOST_PLATFORM" in os.environ:48return os.environ["_PYTHON_HOST_PLATFORM"]4950if os.name != "posix" or not hasattr(os, 'uname'):51# XXX what about the architecture? NT is Intel or Alpha,52# Mac OS is M68k or PPC, etc.53return sys.platform5455# Try to distinguish various flavours of Unix5657(osname, host, release, version, machine) = os.uname()5859# Convert the OS name to lowercase, remove '/' characters, and translate60# spaces (for "Power Macintosh")61osname = osname.lower().replace('/', '')62machine = machine.replace(' ', '_')63machine = machine.replace('/', '-')6465if osname[:5] == "linux":66# At least on Linux/Intel, 'machine' is the processor --67# i386, etc.68# XXX what about Alpha, SPARC, etc?69return "%s-%s" % (osname, machine)70elif osname[:5] == "sunos":71if release[0] >= "5": # SunOS 5 == Solaris 272osname = "solaris"73release = "%d.%s" % (int(release[0]) - 3, release[2:])74# We can't use "platform.architecture()[0]" because a75# bootstrap problem. We use a dict to get an error76# if some suspicious happens.77bitness = {2147483647:"32bit", 9223372036854775807:"64bit"}78machine += ".%s" % bitness[sys.maxsize]79# fall through to standard osname-release-machine representation80elif osname[:3] == "aix":81from _aix_support import aix_platform82return aix_platform()83elif osname[:6] == "cygwin":84osname = "cygwin"85rel_re = re.compile (r'[\d.]+', re.ASCII)86m = rel_re.match(release)87if m:88release = m.group()89elif osname[:6] == "darwin":90import _osx_support, distutils.sysconfig91osname, release, machine = _osx_support.get_platform_osx(92distutils.sysconfig.get_config_vars(),93osname, release, machine)9495return "%s-%s-%s" % (osname, release, machine)9697def get_platform():98if os.name == 'nt':99TARGET_TO_PLAT = {100'x86' : 'win32',101'x64' : 'win-amd64',102'arm' : 'win-arm32',103}104return TARGET_TO_PLAT.get(os.environ.get('VSCMD_ARG_TGT_ARCH')) or get_host_platform()105else:106return get_host_platform()107108def convert_path (pathname):109"""Return 'pathname' as a name that will work on the native filesystem,110i.e. split it on '/' and put it back together again using the current111directory separator. Needed because filenames in the setup script are112always supplied in Unix style, and have to be converted to the local113convention before we can actually use them in the filesystem. Raises114ValueError on non-Unix-ish systems if 'pathname' either starts or115ends with a slash.116"""117if os.sep == '/':118return pathname119if not pathname:120return pathname121if pathname[0] == '/':122raise ValueError("path '%s' cannot be absolute" % pathname)123if pathname[-1] == '/':124raise ValueError("path '%s' cannot end with '/'" % pathname)125126paths = pathname.split('/')127while '.' in paths:128paths.remove('.')129if not paths:130return os.curdir131return os.path.join(*paths)132133# convert_path ()134135136def change_root (new_root, pathname):137"""Return 'pathname' with 'new_root' prepended. If 'pathname' is138relative, this is equivalent to "os.path.join(new_root,pathname)".139Otherwise, it requires making 'pathname' relative and then joining the140two, which is tricky on DOS/Windows and Mac OS.141"""142if os.name == 'posix':143if not os.path.isabs(pathname):144return os.path.join(new_root, pathname)145else:146return os.path.join(new_root, pathname[1:])147148elif os.name == 'nt':149(drive, path) = os.path.splitdrive(pathname)150if path[0] == '\\':151path = path[1:]152return os.path.join(new_root, path)153154else:155raise DistutilsPlatformError("nothing known about platform '%s'" % os.name)156157158_environ_checked = 0159def check_environ ():160"""Ensure that 'os.environ' has all the environment variables we161guarantee that users can use in config files, command-line options,162etc. Currently this includes:163HOME - user's home directory (Unix only)164PLAT - description of the current platform, including hardware165and OS (see 'get_platform()')166"""167global _environ_checked168if _environ_checked:169return170171if os.name == 'posix' and 'HOME' not in os.environ:172try:173import pwd174os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]175except (ImportError, KeyError):176# bpo-10496: if the current user identifier doesn't exist in the177# password database, do nothing178pass179180if 'PLAT' not in os.environ:181os.environ['PLAT'] = get_platform()182183_environ_checked = 1184185186def subst_vars (s, local_vars):187"""Perform shell/Perl-style variable substitution on 'string'. Every188occurrence of '$' followed by a name is considered a variable, and189variable is substituted by the value found in the 'local_vars'190dictionary, or in 'os.environ' if it's not in 'local_vars'.191'os.environ' is first checked/augmented to guarantee that it contains192certain values: see 'check_environ()'. Raise ValueError for any193variables not found in either 'local_vars' or 'os.environ'.194"""195check_environ()196def _subst (match, local_vars=local_vars):197var_name = match.group(1)198if var_name in local_vars:199return str(local_vars[var_name])200else:201return os.environ[var_name]202203try:204return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s)205except KeyError as var:206raise ValueError("invalid variable '$%s'" % var)207208# subst_vars ()209210211def grok_environment_error (exc, prefix="error: "):212# Function kept for backward compatibility.213# Used to try clever things with EnvironmentErrors,214# but nowadays str(exception) produces good messages.215return prefix + str(exc)216217218# Needed by 'split_quoted()'219_wordchars_re = _squote_re = _dquote_re = None220def _init_regex():221global _wordchars_re, _squote_re, _dquote_re222_wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)223_squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")224_dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')225226def split_quoted (s):227"""Split a string up according to Unix shell-like rules for quotes and228backslashes. In short: words are delimited by spaces, as long as those229spaces are not escaped by a backslash, or inside a quoted string.230Single and double quotes are equivalent, and the quote characters can231be backslash-escaped. The backslash is stripped from any two-character232escape sequence, leaving only the escaped character. The quote233characters are stripped from any quoted string. Returns a list of234words.235"""236237# This is a nice algorithm for splitting up a single string, since it238# doesn't require character-by-character examination. It was a little239# bit of a brain-bender to get it working right, though...240if _wordchars_re is None: _init_regex()241242s = s.strip()243words = []244pos = 0245246while s:247m = _wordchars_re.match(s, pos)248end = m.end()249if end == len(s):250words.append(s[:end])251break252253if s[end] in string.whitespace: # unescaped, unquoted whitespace: now254words.append(s[:end]) # we definitely have a word delimiter255s = s[end:].lstrip()256pos = 0257258elif s[end] == '\\': # preserve whatever is being escaped;259# will become part of the current word260s = s[:end] + s[end+1:]261pos = end+1262263else:264if s[end] == "'": # slurp singly-quoted string265m = _squote_re.match(s, end)266elif s[end] == '"': # slurp doubly-quoted string267m = _dquote_re.match(s, end)268else:269raise RuntimeError("this can't happen (bad char '%c')" % s[end])270271if m is None:272raise ValueError("bad string (mismatched %s quotes?)" % s[end])273274(beg, end) = m.span()275s = s[:beg] + s[beg+1:end-1] + s[end:]276pos = m.end() - 2277278if pos >= len(s):279words.append(s)280break281282return words283284# split_quoted ()285286287def execute (func, args, msg=None, verbose=0, dry_run=0):288"""Perform some action that affects the outside world (eg. by289writing to the filesystem). Such actions are special because they290are disabled by the 'dry_run' flag. This method takes care of all291that bureaucracy for you; all you have to do is supply the292function to call and an argument tuple for it (to embody the293"external action" being performed), and an optional message to294print.295"""296if msg is None:297msg = "%s%r" % (func.__name__, args)298if msg[-2:] == ',)': # correct for singleton tuple299msg = msg[0:-2] + ')'300301log.info(msg)302if not dry_run:303func(*args)304305306def strtobool (val):307"""Convert a string representation of truth to true (1) or false (0).308309True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values310are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if311'val' is anything else.312"""313val = val.lower()314if val in ('y', 'yes', 't', 'true', 'on', '1'):315return 1316elif val in ('n', 'no', 'f', 'false', 'off', '0'):317return 0318else:319raise ValueError("invalid truth value %r" % (val,))320321322def byte_compile (py_files,323optimize=0, force=0,324prefix=None, base_dir=None,325verbose=1, dry_run=0,326direct=None):327"""Byte-compile a collection of Python source files to .pyc328files in a __pycache__ subdirectory. 'py_files' is a list329of files to compile; any files that don't end in ".py" are silently330skipped. 'optimize' must be one of the following:3310 - don't optimize3321 - normal optimization (like "python -O")3332 - extra optimization (like "python -OO")334If 'force' is true, all files are recompiled regardless of335timestamps.336337The source filename encoded in each bytecode file defaults to the338filenames listed in 'py_files'; you can modify these with 'prefix' and339'basedir'. 'prefix' is a string that will be stripped off of each340source filename, and 'base_dir' is a directory name that will be341prepended (after 'prefix' is stripped). You can supply either or both342(or neither) of 'prefix' and 'base_dir', as you wish.343344If 'dry_run' is true, doesn't actually do anything that would345affect the filesystem.346347Byte-compilation is either done directly in this interpreter process348with the standard py_compile module, or indirectly by writing a349temporary script and executing it. Normally, you should let350'byte_compile()' figure out to use direct compilation or not (see351the source for details). The 'direct' flag is used by the script352generated in indirect mode; unless you know what you're doing, leave353it set to None.354"""355356# Late import to fix a bootstrap issue: _posixsubprocess is built by357# setup.py, but setup.py uses distutils.358import subprocess359360# nothing is done if sys.dont_write_bytecode is True361if sys.dont_write_bytecode:362raise DistutilsByteCompileError('byte-compiling is disabled.')363364# First, if the caller didn't force us into direct or indirect mode,365# figure out which mode we should be in. We take a conservative366# approach: choose direct mode *only* if the current interpreter is367# in debug mode and optimize is 0. If we're not in debug mode (-O368# or -OO), we don't know which level of optimization this369# interpreter is running with, so we can't do direct370# byte-compilation and be certain that it's the right thing. Thus,371# always compile indirectly if the current interpreter is in either372# optimize mode, or if either optimization level was requested by373# the caller.374if direct is None:375direct = (__debug__ and optimize == 0)376377# "Indirect" byte-compilation: write a temporary script and then378# run it with the appropriate flags.379if not direct:380try:381from tempfile import mkstemp382(script_fd, script_name) = mkstemp(".py")383except ImportError:384from tempfile import mktemp385(script_fd, script_name) = None, mktemp(".py")386log.info("writing byte-compilation script '%s'", script_name)387if not dry_run:388if script_fd is not None:389script = os.fdopen(script_fd, "w")390else:391script = open(script_name, "w")392393with script:394script.write("""\395from distutils.util import byte_compile396files = [397""")398399# XXX would be nice to write absolute filenames, just for400# safety's sake (script should be more robust in the face of401# chdir'ing before running it). But this requires abspath'ing402# 'prefix' as well, and that breaks the hack in build_lib's403# 'byte_compile()' method that carefully tacks on a trailing404# slash (os.sep really) to make sure the prefix here is "just405# right". This whole prefix business is rather delicate -- the406# problem is that it's really a directory, but I'm treating it407# as a dumb string, so trailing slashes and so forth matter.408409#py_files = map(os.path.abspath, py_files)410#if prefix:411# prefix = os.path.abspath(prefix)412413script.write(",\n".join(map(repr, py_files)) + "]\n")414script.write("""415byte_compile(files, optimize=%r, force=%r,416prefix=%r, base_dir=%r,417verbose=%r, dry_run=0,418direct=1)419""" % (optimize, force, prefix, base_dir, verbose))420421cmd = [sys.executable]422cmd.extend(subprocess._optim_args_from_interpreter_flags())423cmd.append(script_name)424spawn(cmd, dry_run=dry_run)425execute(os.remove, (script_name,), "removing %s" % script_name,426dry_run=dry_run)427428# "Direct" byte-compilation: use the py_compile module to compile429# right here, right now. Note that the script generated in indirect430# mode simply calls 'byte_compile()' in direct mode, a weird sort of431# cross-process recursion. Hey, it works!432else:433from py_compile import compile434435for file in py_files:436if file[-3:] != ".py":437# This lets us be lazy and not filter filenames in438# the "install_lib" command.439continue440441# Terminology from the py_compile module:442# cfile - byte-compiled file443# dfile - purported source filename (same as 'file' by default)444if optimize >= 0:445opt = '' if optimize == 0 else optimize446cfile = importlib.util.cache_from_source(447file, optimization=opt)448else:449cfile = importlib.util.cache_from_source(file)450dfile = file451if prefix:452if file[:len(prefix)] != prefix:453raise ValueError("invalid prefix: filename %r doesn't start with %r"454% (file, prefix))455dfile = dfile[len(prefix):]456if base_dir:457dfile = os.path.join(base_dir, dfile)458459cfile_base = os.path.basename(cfile)460if direct:461if force or newer(file, cfile):462log.info("byte-compiling %s to %s", file, cfile_base)463if not dry_run:464compile(file, cfile, dfile)465else:466log.debug("skipping byte-compilation of %s to %s",467file, cfile_base)468469# byte_compile ()470471def rfc822_escape (header):472"""Return a version of the string escaped for inclusion in an473RFC-822 header, by ensuring there are 8 spaces space after each newline.474"""475lines = header.split('\n')476sep = '\n' + 8 * ' '477return sep.join(lines)478479# 2to3 support480481def run_2to3(files, fixer_names=None, options=None, explicit=None):482"""Invoke 2to3 on a list of Python files.483The files should all come from the build area, as the484modification is done in-place. To reduce the build time,485only files modified since the last invocation of this486function should be passed in the files argument."""487488if not files:489return490491# Make this class local, to delay import of 2to3492from lib2to3.refactor import RefactoringTool, get_fixers_from_package493class DistutilsRefactoringTool(RefactoringTool):494def log_error(self, msg, *args, **kw):495log.error(msg, *args)496497def log_message(self, msg, *args):498log.info(msg, *args)499500def log_debug(self, msg, *args):501log.debug(msg, *args)502503if fixer_names is None:504fixer_names = get_fixers_from_package('lib2to3.fixes')505r = DistutilsRefactoringTool(fixer_names, options=options)506r.refactor(files, write=True)507508def copydir_run_2to3(src, dest, template=None, fixer_names=None,509options=None, explicit=None):510"""Recursively copy a directory, only copying new and changed files,511running run_2to3 over all newly copied Python modules afterward.512513If you give a template string, it's parsed like a MANIFEST.in.514"""515from distutils.dir_util import mkpath516from distutils.file_util import copy_file517from distutils.filelist import FileList518filelist = FileList()519curdir = os.getcwd()520os.chdir(src)521try:522filelist.findall()523finally:524os.chdir(curdir)525filelist.files[:] = filelist.allfiles526if template:527for line in template.splitlines():528line = line.strip()529if not line: continue530filelist.process_template_line(line)531copied = []532for filename in filelist.files:533outname = os.path.join(dest, filename)534mkpath(os.path.dirname(outname))535res = copy_file(os.path.join(src, filename), outname, update=1)536if res[1]: copied.append(outname)537run_2to3([fn for fn in copied if fn.lower().endswith('.py')],538fixer_names=fixer_names, options=options, explicit=explicit)539return copied540541class Mixin2to3:542'''Mixin class for commands that run 2to3.543To configure 2to3, setup scripts may either change544the class variables, or inherit from individual commands545to override how 2to3 is invoked.'''546547# provide list of fixers to run;548# defaults to all from lib2to3.fixers549fixer_names = None550551# options dictionary552options = None553554# list of fixers to invoke even though they are marked as explicit555explicit = None556557def run_2to3(self, files):558return run_2to3(files, self.fixer_names, self.options, self.explicit)559560561