bpo-41282: (PEP 632) Deprecate distutils.sysconfig (partial implementation of the PEP) (GH-23142)

This change:
* merges `distutils.sysconfig` into `sysconfig` while keeping the original functionality and
* marks `distutils.sysconfig` as deprecated

https://bugs.python.org/issue41282
This commit is contained in:
Lumír 'Frenzy' Balhar 2021-04-23 14:02:41 +02:00 committed by GitHub
parent b9ad88be03
commit 90d02e5e63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 209 additions and 443 deletions

View file

@ -1452,6 +1452,8 @@ name.
.. module:: distutils.sysconfig .. module:: distutils.sysconfig
:synopsis: Low-level access to configuration information of the Python interpreter. :synopsis: Low-level access to configuration information of the Python interpreter.
.. deprecated:: 3.10
:mod:`distutils.sysconfig` has been merged into :mod:`sysconfig`.
.. moduleauthor:: Fred L. Drake, Jr. <fdrake@acm.org> .. moduleauthor:: Fred L. Drake, Jr. <fdrake@acm.org>
.. moduleauthor:: Greg Ward <gward@python.net> .. moduleauthor:: Greg Ward <gward@python.net>
.. sectionauthor:: Fred L. Drake, Jr. <fdrake@acm.org> .. sectionauthor:: Fred L. Drake, Jr. <fdrake@acm.org>
@ -1510,6 +1512,9 @@ for other parts of the :mod:`distutils` package.
meaning for other platforms will vary. The file is a platform-specific text meaning for other platforms will vary. The file is a platform-specific text
file, if it exists. This function is only useful on POSIX platforms. file, if it exists. This function is only useful on POSIX platforms.
The following functions are deprecated together with this module and they
have no direct replacement.
.. function:: get_python_inc([plat_specific[, prefix]]) .. function:: get_python_inc([plat_specific[, prefix]])

View file

@ -224,6 +224,7 @@ Other functions
Return the path of :file:`Makefile`. Return the path of :file:`Makefile`.
Using :mod:`sysconfig` as a script Using :mod:`sysconfig` as a script
---------------------------------- ----------------------------------

View file

@ -4,6 +4,7 @@ Provides the Extension class, used to describe C/C++ extension
modules in setup scripts.""" modules in setup scripts."""
import os import os
import re
import warnings import warnings
# This class is really only used by the "build_ext" command, so it might # This class is really only used by the "build_ext" command, so it might
@ -161,7 +162,7 @@ def read_setup_file(filename):
line = file.readline() line = file.readline()
if line is None: # eof if line is None: # eof
break break
if _variable_rx.match(line): # VAR=VALUE, handled in first pass if re.match(_variable_rx, line): # VAR=VALUE, handled in first pass
continue continue
if line[0] == line[-1] == "*": if line[0] == line[-1] == "*":

View file

@ -13,56 +13,71 @@ import _imp
import os import os
import re import re
import sys import sys
import warnings
from functools import partial
from .errors import DistutilsPlatformError from .errors import DistutilsPlatformError
# These are needed in a couple of spots, so just compute them once. from sysconfig import (
PREFIX = os.path.normpath(sys.prefix) _PREFIX as PREFIX,
EXEC_PREFIX = os.path.normpath(sys.exec_prefix) _BASE_PREFIX as BASE_PREFIX,
BASE_PREFIX = os.path.normpath(sys.base_prefix) _EXEC_PREFIX as EXEC_PREFIX,
BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix) _BASE_EXEC_PREFIX as BASE_EXEC_PREFIX,
_PROJECT_BASE as project_base,
_PYTHON_BUILD as python_build,
_init_posix as sysconfig_init_posix,
parse_config_h as sysconfig_parse_config_h,
_parse_makefile as sysconfig_parse_makefile,
# Path to the base directory of the project. On Windows the binary may _init_non_posix,
# live in project/PCbuild/win32 or project/PCbuild/amd64. _is_python_source_dir,
# set for cross builds _sys_home,
if "_PYTHON_PROJECT_BASE" in os.environ:
project_base = os.path.abspath(os.environ["_PYTHON_PROJECT_BASE"]) _variable_rx,
else: _findvar1_rx,
if sys.executable: _findvar2_rx,
project_base = os.path.dirname(os.path.abspath(sys.executable))
else: expand_makefile_vars,
# sys.executable can be empty if argv[0] has been changed and Python is is_python_build,
# unable to retrieve the real program name get_config_h_filename,
project_base = os.getcwd() get_config_var,
get_config_vars,
get_makefile_filename,
get_python_version,
)
# This is better than
# from sysconfig import _CONFIG_VARS as _config_vars
# because it makes sure that the global dictionary is initialized
# which might not be true in the time of import.
_config_vars = get_config_vars()
if os.name == "nt":
from sysconfig import _fix_pcbuild
warnings.warn(
'the distutils.sysconfig module is deprecated, use sysconfig instead',
DeprecationWarning,
stacklevel=2
)
# python_build: (Boolean) if true, we're either building Python or # Following functions are the same as in sysconfig but with different API
# building an extension with an un-installed Python, so we use def parse_config_h(fp, g=None):
# different (hard-wired) directories. return sysconfig_parse_config_h(fp, vars=g)
def _is_python_source_dir(d):
for fn in ("Setup", "Setup.local"):
if os.path.isfile(os.path.join(d, "Modules", fn)):
return True
return False
_sys_home = getattr(sys, '_home', None)
if os.name == 'nt': def parse_makefile(fn, g=None):
def _fix_pcbuild(d): return sysconfig_parse_makefile(fn, vars=g, keep_unresolved=False)
if d and os.path.normcase(d).startswith(
os.path.normcase(os.path.join(PREFIX, "PCbuild"))):
return PREFIX
return d
project_base = _fix_pcbuild(project_base)
_sys_home = _fix_pcbuild(_sys_home)
def _python_build(): _python_build = partial(is_python_build, check_home=True)
if _sys_home: _init_posix = partial(sysconfig_init_posix, _config_vars)
return _is_python_source_dir(_sys_home) _init_nt = partial(_init_non_posix, _config_vars)
return _is_python_source_dir(project_base)
python_build = _python_build()
# Following functions are deprecated together with this module and they
# have no direct replacement
# Calculate the build qualifier flags if they are defined. Adding the flags # Calculate the build qualifier flags if they are defined. Adding the flags
# to the include and lib directories only makes sense for an installation, not # to the include and lib directories only makes sense for an installation, not
@ -76,12 +91,76 @@ except AttributeError:
# this attribute, which is fine. # this attribute, which is fine.
pass pass
def get_python_version():
"""Return a string containing the major and minor Python version, def customize_compiler(compiler):
leaving off the patchlevel. Sample return values could be '1.5' """Do any platform-specific customization of a CCompiler instance.
or '2.2'.
Mainly needed on Unix, so we can plug in the information that
varies across Unices and is stored in Python's Makefile.
""" """
return '%d.%d' % sys.version_info[:2] if compiler.compiler_type == "unix":
if sys.platform == "darwin":
# Perform first-time customization of compiler-related
# config vars on OS X now that we know we need a compiler.
# This is primarily to support Pythons from binary
# installers. The kind and paths to build tools on
# the user system may vary significantly from the system
# that Python itself was built on. Also the user OS
# version and build tools may not support the same set
# of CPU architectures for universal builds.
if not _config_vars.get('CUSTOMIZED_OSX_COMPILER'):
import _osx_support
_osx_support.customize_compiler(_config_vars)
_config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True'
(cc, cxx, cflags, ccshared, ldshared, shlib_suffix, ar, ar_flags) = \
get_config_vars('CC', 'CXX', 'CFLAGS',
'CCSHARED', 'LDSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS')
if 'CC' in os.environ:
newcc = os.environ['CC']
if (sys.platform == 'darwin'
and 'LDSHARED' not in os.environ
and ldshared.startswith(cc)):
# On OS X, if CC is overridden, use that as the default
# command for LDSHARED as well
ldshared = newcc + ldshared[len(cc):]
cc = newcc
if 'CXX' in os.environ:
cxx = os.environ['CXX']
if 'LDSHARED' in os.environ:
ldshared = os.environ['LDSHARED']
if 'CPP' in os.environ:
cpp = os.environ['CPP']
else:
cpp = cc + " -E" # not always
if 'LDFLAGS' in os.environ:
ldshared = ldshared + ' ' + os.environ['LDFLAGS']
if 'CFLAGS' in os.environ:
cflags = cflags + ' ' + os.environ['CFLAGS']
ldshared = ldshared + ' ' + os.environ['CFLAGS']
if 'CPPFLAGS' in os.environ:
cpp = cpp + ' ' + os.environ['CPPFLAGS']
cflags = cflags + ' ' + os.environ['CPPFLAGS']
ldshared = ldshared + ' ' + os.environ['CPPFLAGS']
if 'AR' in os.environ:
ar = os.environ['AR']
if 'ARFLAGS' in os.environ:
archiver = ar + ' ' + os.environ['ARFLAGS']
else:
archiver = ar + ' ' + ar_flags
cc_cmd = cc + ' ' + cflags
compiler.set_executables(
preprocessor=cpp,
compiler=cc_cmd,
compiler_so=cc_cmd + ' ' + ccshared,
compiler_cxx=cxx,
linker_so=ldshared,
linker_exe=cc,
archiver=archiver)
compiler.shared_lib_extension = shlib_suffix
def get_python_inc(plat_specific=0, prefix=None): def get_python_inc(plat_specific=0, prefix=None):
@ -167,389 +246,3 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None):
raise DistutilsPlatformError( raise DistutilsPlatformError(
"I don't know where Python installs its library " "I don't know where Python installs its library "
"on platform '%s'" % os.name) "on platform '%s'" % os.name)
def customize_compiler(compiler):
"""Do any platform-specific customization of a CCompiler instance.
Mainly needed on Unix, so we can plug in the information that
varies across Unices and is stored in Python's Makefile.
"""
if compiler.compiler_type == "unix":
if sys.platform == "darwin":
# Perform first-time customization of compiler-related
# config vars on OS X now that we know we need a compiler.
# This is primarily to support Pythons from binary
# installers. The kind and paths to build tools on
# the user system may vary significantly from the system
# that Python itself was built on. Also the user OS
# version and build tools may not support the same set
# of CPU architectures for universal builds.
global _config_vars
# Use get_config_var() to ensure _config_vars is initialized.
if not get_config_var('CUSTOMIZED_OSX_COMPILER'):
import _osx_support
_osx_support.customize_compiler(_config_vars)
_config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True'
(cc, cxx, cflags, ccshared, ldshared, shlib_suffix, ar, ar_flags) = \
get_config_vars('CC', 'CXX', 'CFLAGS',
'CCSHARED', 'LDSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS')
if 'CC' in os.environ:
newcc = os.environ['CC']
if (sys.platform == 'darwin'
and 'LDSHARED' not in os.environ
and ldshared.startswith(cc)):
# On OS X, if CC is overridden, use that as the default
# command for LDSHARED as well
ldshared = newcc + ldshared[len(cc):]
cc = newcc
if 'CXX' in os.environ:
cxx = os.environ['CXX']
if 'LDSHARED' in os.environ:
ldshared = os.environ['LDSHARED']
if 'CPP' in os.environ:
cpp = os.environ['CPP']
else:
cpp = cc + " -E" # not always
if 'LDFLAGS' in os.environ:
ldshared = ldshared + ' ' + os.environ['LDFLAGS']
if 'CFLAGS' in os.environ:
cflags = cflags + ' ' + os.environ['CFLAGS']
ldshared = ldshared + ' ' + os.environ['CFLAGS']
if 'CPPFLAGS' in os.environ:
cpp = cpp + ' ' + os.environ['CPPFLAGS']
cflags = cflags + ' ' + os.environ['CPPFLAGS']
ldshared = ldshared + ' ' + os.environ['CPPFLAGS']
if 'AR' in os.environ:
ar = os.environ['AR']
if 'ARFLAGS' in os.environ:
archiver = ar + ' ' + os.environ['ARFLAGS']
else:
archiver = ar + ' ' + ar_flags
cc_cmd = cc + ' ' + cflags
compiler.set_executables(
preprocessor=cpp,
compiler=cc_cmd,
compiler_so=cc_cmd + ' ' + ccshared,
compiler_cxx=cxx,
linker_so=ldshared,
linker_exe=cc,
archiver=archiver)
compiler.shared_lib_extension = shlib_suffix
def get_config_h_filename():
"""Return full pathname of installed pyconfig.h file."""
if python_build:
if os.name == "nt":
inc_dir = os.path.join(_sys_home or project_base, "PC")
else:
inc_dir = _sys_home or project_base
else:
inc_dir = get_python_inc(plat_specific=1)
return os.path.join(inc_dir, 'pyconfig.h')
def get_makefile_filename():
"""Return full pathname of installed Makefile from the Python build."""
if python_build:
return os.path.join(_sys_home or project_base, "Makefile")
lib_dir = get_python_lib(plat_specific=0, standard_lib=1)
config_file = 'config-{}{}'.format(get_python_version(), build_flags)
if hasattr(sys.implementation, '_multiarch'):
config_file += '-%s' % sys.implementation._multiarch
return os.path.join(lib_dir, config_file, 'Makefile')
def parse_config_h(fp, g=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 g is None:
g = {}
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: v = int(v)
except ValueError: pass
g[n] = v
else:
m = undef_rx.match(line)
if m:
g[m.group(1)] = 0
return g
# Regexes needed for parsing Makefile (and similar syntaxes,
# like old-style Setup files).
_variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
def parse_makefile(fn, g=None):
"""Parse a Makefile-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.
"""
from distutils.text_file import TextFile
fp = TextFile(fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape")
if g is None:
g = {}
done = {}
notdone = {}
while True:
line = fp.readline()
if line is None: # eof
break
m = _variable_rx.match(line)
if m:
n, v = m.group(1, 2)
v = v.strip()
# `$$' is a literal `$' in make
tmpv = v.replace('$$', '')
if "$" in tmpv:
notdone[n] = v
else:
try:
v = int(v)
except ValueError:
# insert literal `$'
done[n] = v.replace('$$', '$')
else:
done[n] = v
# Variables with a 'PY_' prefix in the makefile. These need to
# be made available without that prefix through sysconfig.
# Special care is needed to ensure that variable expansion works, even
# if the expansion uses the name without a prefix.
renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')
# do variable interpolation here
while notdone:
for name in list(notdone):
value = notdone[name]
m = _findvar1_rx.search(value) or _findvar2_rx.search(value)
if m:
n = m.group(1)
found = True
if n in done:
item = str(done[n])
elif n in notdone:
# get it on a subsequent round
found = False
elif n in os.environ:
# do it like make: fall back to environment
item = os.environ[n]
elif n in renamed_variables:
if name.startswith('PY_') and name[3:] in renamed_variables:
item = ""
elif 'PY_' + n in notdone:
found = False
else:
item = str(done['PY_' + n])
else:
done[n] = item = ""
if found:
after = value[m.end():]
value = value[:m.start()] + item + after
if "$" in after:
notdone[name] = value
else:
try: value = int(value)
except ValueError:
done[name] = value.strip()
else:
done[name] = value
del notdone[name]
if name.startswith('PY_') \
and name[3:] in renamed_variables:
name = name[3:]
if name not in done:
done[name] = value
else:
# bogus variable reference; just drop it since we can't deal
del notdone[name]
fp.close()
# strip spurious spaces
for k, v in done.items():
if isinstance(v, str):
done[k] = v.strip()
# save the results in the global dictionary
g.update(done)
return g
def expand_makefile_vars(s, vars):
"""Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in
'string' according to 'vars' (a dictionary mapping variable names to
values). Variables not present in 'vars' are silently expanded to the
empty string. The variable values in 'vars' should not contain further
variable expansions; if 'vars' is the output of 'parse_makefile()',
you're fine. Returns a variable-expanded version of 's'.
"""
# This algorithm does multiple expansion, so if vars['foo'] contains
# "${bar}", it will expand ${foo} to ${bar}, and then expand
# ${bar}... and so forth. This is fine as long as 'vars' comes from
# 'parse_makefile()', which takes care of such expansions eagerly,
# according to make's variable expansion semantics.
while True:
m = _findvar1_rx.search(s) or _findvar2_rx.search(s)
if m:
(beg, end) = m.span()
s = s[0:beg] + vars.get(m.group(1)) + s[end:]
else:
break
return s
_config_vars = None
def _init_posix():
"""Initialize the module as appropriate for POSIX systems."""
# _sysconfigdata is generated at build time, see the sysconfig module
name = os.environ.get('_PYTHON_SYSCONFIGDATA_NAME',
'_sysconfigdata_{abi}_{platform}_{multiarch}'.format(
abi=sys.abiflags,
platform=sys.platform,
multiarch=getattr(sys.implementation, '_multiarch', ''),
))
_temp = __import__(name, globals(), locals(), ['build_time_vars'], 0)
build_time_vars = _temp.build_time_vars
global _config_vars
_config_vars = {}
_config_vars.update(build_time_vars)
def _init_nt():
"""Initialize the module as appropriate for NT"""
g = {}
# set basic install directories
g['LIBDEST'] = get_python_lib(plat_specific=0, standard_lib=1)
g['BINLIBDEST'] = get_python_lib(plat_specific=1, standard_lib=1)
# XXX hmmm.. a normal install puts include files here
g['INCLUDEPY'] = get_python_inc(plat_specific=0)
g['EXT_SUFFIX'] = _imp.extension_suffixes()[0]
g['EXE'] = ".exe"
g['VERSION'] = get_python_version().replace(".", "")
g['BINDIR'] = os.path.dirname(os.path.abspath(sys.executable))
global _config_vars
_config_vars = g
def get_config_vars(*args):
"""With no arguments, return a dictionary of all configuration
variables relevant for the current platform. Generally this includes
everything needed to build extensions and install both pure modules and
extensions. On Unix, this means every variable defined in Python's
installed Makefile; on Windows it's a much smaller set.
With arguments, return a list of values that result from looking up
each argument in the configuration variable dictionary.
"""
global _config_vars
if _config_vars is None:
func = globals().get("_init_" + os.name)
if func:
func()
else:
_config_vars = {}
# Normalized versions of prefix and exec_prefix are handy to have;
# in fact, these are the standard versions used most places in the
# Distutils.
_config_vars['prefix'] = PREFIX
_config_vars['exec_prefix'] = EXEC_PREFIX
# For backward compatibility, see issue19555
SO = _config_vars.get('EXT_SUFFIX')
if SO is not None:
_config_vars['SO'] = SO
# Always convert srcdir to an absolute path
srcdir = _config_vars.get('srcdir', project_base)
if os.name == 'posix':
if python_build:
# If srcdir is a relative path (typically '.' or '..')
# then it should be interpreted relative to the directory
# containing Makefile.
base = os.path.dirname(get_makefile_filename())
srcdir = os.path.join(base, srcdir)
else:
# srcdir is not meaningful since the installation is
# spread about the filesystem. We choose the
# directory containing the Makefile since we know it
# exists.
srcdir = os.path.dirname(get_makefile_filename())
_config_vars['srcdir'] = os.path.abspath(os.path.normpath(srcdir))
# Convert srcdir into an absolute path if it appears necessary.
# Normally it is relative to the build directory. However, during
# testing, for example, we might be running a non-installed python
# from a different directory.
if python_build and os.name == "posix":
base = project_base
if (not os.path.isabs(_config_vars['srcdir']) and
base != os.getcwd()):
# srcdir is relative and we are not in the same directory
# as the executable. Assume executable is in the build
# directory and make srcdir absolute.
srcdir = os.path.join(base, _config_vars['srcdir'])
_config_vars['srcdir'] = os.path.normpath(srcdir)
# OS X platforms require special customization to handle
# multi-architecture, multi-os-version installers
if sys.platform == 'darwin':
import _osx_support
_osx_support.customize_config_vars(_config_vars)
if args:
vals = []
for name in args:
vals.append(_config_vars.get(name))
return vals
else:
return _config_vars
def get_config_var(name):
"""Return the value of a single variable using the dictionary
returned by 'get_config_vars()'. Equivalent to
get_config_vars().get(name)
"""
if name == 'SO':
import warnings
warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2)
return get_config_vars().get(name)

View file

@ -2,6 +2,7 @@
import unittest import unittest
import os import os
import sys import sys
import sysconfig
from test.support import run_unittest, missing_compiler_executable from test.support import run_unittest, missing_compiler_executable
@ -13,6 +14,15 @@ class BuildCLibTestCase(support.TempdirManager,
support.LoggingSilencer, support.LoggingSilencer,
unittest.TestCase): unittest.TestCase):
def setUp(self):
super().setUp()
self._backup_CONFIG_VARS = dict(sysconfig._CONFIG_VARS)
def tearDown(self):
super().tearDown()
sysconfig._CONFIG_VARS.clear()
sysconfig._CONFIG_VARS.update(self._backup_CONFIG_VARS)
def test_check_library_dist(self): def test_check_library_dist(self):
pkg_dir, dist = self.create_dist() pkg_dir, dist = self.create_dist()
cmd = build_clib(dist) cmd = build_clib(dist)

View file

@ -35,6 +35,7 @@ class BuildExtTestCase(TempdirManager,
site.USER_BASE = self.mkdtemp() site.USER_BASE = self.mkdtemp()
from distutils.command import build_ext from distutils.command import build_ext
build_ext.USER_BASE = site.USER_BASE build_ext.USER_BASE = site.USER_BASE
self.old_config_vars = dict(sysconfig._config_vars)
# bpo-30132: On Windows, a .pdb file may be created in the current # bpo-30132: On Windows, a .pdb file may be created in the current
# working directory. Create a temporary working directory to cleanup # working directory. Create a temporary working directory to cleanup
@ -48,6 +49,8 @@ class BuildExtTestCase(TempdirManager,
site.USER_BASE = self.old_user_base site.USER_BASE = self.old_user_base
from distutils.command import build_ext from distutils.command import build_ext
build_ext.USER_BASE = self.old_user_base build_ext.USER_BASE = self.old_user_base
sysconfig._config_vars.clear()
sysconfig._config_vars.update(self.old_config_vars)
super(BuildExtTestCase, self).tearDown() super(BuildExtTestCase, self).tearDown()
def build_ext(self, *args, **kwargs): def build_ext(self, *args, **kwargs):

View file

@ -2,6 +2,7 @@
import unittest import unittest
import os import os
import sys import sys
import sysconfig
from test.support import run_unittest, missing_compiler_executable from test.support import run_unittest, missing_compiler_executable
from distutils.command.config import dump_file, config from distutils.command.config import dump_file, config
@ -21,9 +22,12 @@ class ConfigTestCase(support.LoggingSilencer,
self._logs = [] self._logs = []
self.old_log = log.info self.old_log = log.info
log.info = self._info log.info = self._info
self.old_config_vars = dict(sysconfig._CONFIG_VARS)
def tearDown(self): def tearDown(self):
log.info = self.old_log log.info = self.old_log
sysconfig._CONFIG_VARS.clear()
sysconfig._CONFIG_VARS.update(self.old_config_vars)
super(ConfigTestCase, self).tearDown() super(ConfigTestCase, self).tearDown()
def test_dump_file(self): def test_dump_file(self):

View file

@ -29,6 +29,15 @@ class InstallTestCase(support.TempdirManager,
support.LoggingSilencer, support.LoggingSilencer,
unittest.TestCase): unittest.TestCase):
def setUp(self):
super().setUp()
self._backup_config_vars = dict(sysconfig._config_vars)
def tearDown(self):
super().tearDown()
sysconfig._config_vars.clear()
sysconfig._config_vars.update(self._backup_config_vars)
def test_home_installation_scheme(self): def test_home_installation_scheme(self):
# This ensure two things: # This ensure two things:
# - that --home generates the desired set of directory names # - that --home generates the desired set of directory names

View file

@ -12,6 +12,7 @@ class UnixCCompilerTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self._backup_platform = sys.platform self._backup_platform = sys.platform
self._backup_get_config_var = sysconfig.get_config_var self._backup_get_config_var = sysconfig.get_config_var
self._backup_config_vars = dict(sysconfig._config_vars)
class CompilerWrapper(UnixCCompiler): class CompilerWrapper(UnixCCompiler):
def rpath_foo(self): def rpath_foo(self):
return self.runtime_library_dir_option('/foo') return self.runtime_library_dir_option('/foo')
@ -20,6 +21,8 @@ class UnixCCompilerTestCase(unittest.TestCase):
def tearDown(self): def tearDown(self):
sys.platform = self._backup_platform sys.platform = self._backup_platform
sysconfig.get_config_var = self._backup_get_config_var sysconfig.get_config_var = self._backup_get_config_var
sysconfig._config_vars.clear()
sysconfig._config_vars.update(self._backup_config_vars)
@unittest.skipIf(sys.platform == 'win32', "can't test on Windows") @unittest.skipIf(sys.platform == 'win32', "can't test on Windows")
def test_runtime_libdir_option(self): def test_runtime_libdir_option(self):

View file

@ -54,7 +54,8 @@ class UtilTestCase(support.EnvironGuard, unittest.TestCase):
os.uname = self.uname os.uname = self.uname
else: else:
del os.uname del os.uname
sysconfig._config_vars = copy(self._config_vars) sysconfig._config_vars.clear()
sysconfig._config_vars.update(self._config_vars)
super(UtilTestCase, self).tearDown() super(UtilTestCase, self).tearDown()
def _set_uname(self, uname): def _set_uname(self, uname):

View file

@ -90,7 +90,8 @@ sys.path = {additional_paths or []} + sys.path
sys.argv[1:] = {args} sys.argv[1:] = {args}
runpy.run_module("pip", run_name="__main__", alter_sys=True) runpy.run_module("pip", run_name="__main__", alter_sys=True)
""" """
return subprocess.run([sys.executable, "-c", code], check=True).returncode return subprocess.run([sys.executable, '-W', 'ignore::DeprecationWarning',
"-c", code], check=True).returncode
def version(): def version():

View file

@ -130,6 +130,12 @@ _BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix)
_CONFIG_VARS = None _CONFIG_VARS = None
_USER_BASE = None _USER_BASE = None
# Regexes needed for parsing Makefile (and similar syntaxes,
# like old-style Setup files).
_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_]*)}"
def _safe_realpath(path): def _safe_realpath(path):
try: try:
@ -215,19 +221,14 @@ def _get_default_scheme():
def _parse_makefile(filename, vars=None): def _parse_makefile(filename, vars=None, keep_unresolved=True):
"""Parse a Makefile-style file. """Parse a Makefile-style file.
A dictionary containing name/value pairs is returned. If an A dictionary containing name/value pairs is returned. If an
optional dictionary is passed in as the second argument, it is optional dictionary is passed in as the second argument, it is
used instead of a new dictionary. used instead of a new dictionary.
""" """
# Regexes needed for parsing Makefile (and similar syntaxes,
# like old-style Setup files).
import re import re
_variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
if vars is None: if vars is None:
vars = {} vars = {}
@ -241,7 +242,7 @@ def _parse_makefile(filename, vars=None):
for line in lines: for line in lines:
if line.startswith('#') or line.strip() == '': if line.startswith('#') or line.strip() == '':
continue continue
m = _variable_rx.match(line) m = re.match(_variable_rx, line)
if m: if m:
n, v = m.group(1, 2) n, v = m.group(1, 2)
v = v.strip() v = v.strip()
@ -274,8 +275,8 @@ def _parse_makefile(filename, vars=None):
while len(variables) > 0: while len(variables) > 0:
for name in tuple(variables): for name in tuple(variables):
value = notdone[name] value = notdone[name]
m1 = _findvar1_rx.search(value) m1 = re.search(_findvar1_rx, value)
m2 = _findvar2_rx.search(value) m2 = re.search(_findvar2_rx, value)
if m1 and m2: if m1 and m2:
m = m1 if m1.start() < m2.start() else m2 m = m1 if m1.start() < m2.start() else m2
else: else:
@ -330,9 +331,12 @@ def _parse_makefile(filename, vars=None):
done[name] = value done[name] = value
else: else:
# Adds unresolved variables to the done dict.
# This is disabled when called from distutils.sysconfig
if keep_unresolved:
done[name] = value
# bogus variable reference (e.g. "prefix=$/opt/python"); # bogus variable reference (e.g. "prefix=$/opt/python");
# just drop it since we can't deal # just drop it since we can't deal
done[name] = value
variables.remove(name) variables.remove(name)
# strip spurious spaces # strip spurious spaces
@ -712,6 +716,32 @@ def get_python_version():
return _PY_VERSION_SHORT return _PY_VERSION_SHORT
def expand_makefile_vars(s, vars):
"""Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in
'string' according to 'vars' (a dictionary mapping variable names to
values). Variables not present in 'vars' are silently expanded to the
empty string. The variable values in 'vars' should not contain further
variable expansions; if 'vars' is the output of 'parse_makefile()',
you're fine. Returns a variable-expanded version of 's'.
"""
import re
# This algorithm does multiple expansion, so if vars['foo'] contains
# "${bar}", it will expand ${foo} to ${bar}, and then expand
# ${bar}... and so forth. This is fine as long as 'vars' comes from
# 'parse_makefile()', which takes care of such expansions eagerly,
# according to make's variable expansion semantics.
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
def _print_dict(title, data): def _print_dict(title, data):
for index, (key, value) in enumerate(sorted(data.items())): for index, (key, value) in enumerate(sorted(data.items())):
if index == 0: if index == 0:

View file

@ -1667,6 +1667,7 @@ def missing_compiler_executable(cmd_names=[]):
missing. missing.
""" """
# TODO (PEP 632): alternate check without using distutils
from distutils import ccompiler, sysconfig, spawn, errors from distutils import ccompiler, sysconfig, spawn, errors
compiler = ccompiler.new_compiler() compiler = ccompiler.new_compiler()
sysconfig.customize_compiler(compiler) sysconfig.customize_compiler(compiler)

View file

@ -70,6 +70,7 @@ unittest.main()
class TestCParser(TempdirManager, unittest.TestCase): class TestCParser(TempdirManager, unittest.TestCase):
def setUp(self): def setUp(self):
self._backup_config_vars = dict(sysconfig._CONFIG_VARS)
cmd = support.missing_compiler_executable() cmd = support.missing_compiler_executable()
if cmd is not None: if cmd is not None:
self.skipTest("The %r command is not found" % cmd) self.skipTest("The %r command is not found" % cmd)
@ -81,6 +82,8 @@ class TestCParser(TempdirManager, unittest.TestCase):
def tearDown(self): def tearDown(self):
super(TestCParser, self).tearDown() super(TestCParser, self).tearDown()
sysconfig._CONFIG_VARS.clear()
sysconfig._CONFIG_VARS.update(self._backup_config_vars)
def build_extension(self, grammar_source): def build_extension(self, grammar_source):
grammar = parse_string(grammar_source, GrammarParser) grammar = parse_string(grammar_source, GrammarParser)

View file

@ -0,0 +1 @@
:mod:`distutils.sysconfig` has been merged to :mod:`sysconfig`.