mirror of https://github.com/python/cpython
GH-103480: make sysconfig a package (GH-110785)
This commit is contained in:
parent
d2a536b170
commit
4a53a397c3
|
@ -180,12 +180,6 @@ _CONFIG_VARS = None
|
|||
_CONFIG_VARS_INITIALIZED = False
|
||||
_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):
|
||||
try:
|
||||
|
@ -317,134 +311,6 @@ def get_default_scheme():
|
|||
return get_preferred_scheme('prefix')
|
||||
|
||||
|
||||
def _parse_makefile(filename, vars=None, keep_unresolved=True):
|
||||
"""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.
|
||||
"""
|
||||
import re
|
||||
|
||||
if vars is None:
|
||||
vars = {}
|
||||
done = {}
|
||||
notdone = {}
|
||||
|
||||
with open(filename, encoding=sys.getfilesystemencoding(),
|
||||
errors="surrogateescape") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('#') or line.strip() == '':
|
||||
continue
|
||||
m = re.match(_variable_rx, 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:
|
||||
if n in _ALWAYS_STR:
|
||||
raise ValueError
|
||||
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
# insert literal `$'
|
||||
done[n] = v.replace('$$', '$')
|
||||
else:
|
||||
done[n] = v
|
||||
|
||||
# do variable interpolation here
|
||||
variables = list(notdone.keys())
|
||||
|
||||
# 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')
|
||||
|
||||
while len(variables) > 0:
|
||||
for name in tuple(variables):
|
||||
value = notdone[name]
|
||||
m1 = re.search(_findvar1_rx, value)
|
||||
m2 = re.search(_findvar2_rx, value)
|
||||
if m1 and m2:
|
||||
m = m1 if m1.start() < m2.start() else m2
|
||||
else:
|
||||
m = m1 if m1 else m2
|
||||
if m is not None:
|
||||
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:
|
||||
if name in _ALWAYS_STR:
|
||||
raise ValueError
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
done[name] = value.strip()
|
||||
else:
|
||||
done[name] = value
|
||||
variables.remove(name)
|
||||
|
||||
if name.startswith('PY_') \
|
||||
and name[3:] in renamed_variables:
|
||||
|
||||
name = name[3:]
|
||||
if name not in done:
|
||||
done[name] = value
|
||||
|
||||
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");
|
||||
# just drop it since we can't deal
|
||||
variables.remove(name)
|
||||
|
||||
# strip spurious spaces
|
||||
for k, v in done.items():
|
||||
if isinstance(v, str):
|
||||
done[k] = v.strip()
|
||||
|
||||
# save the results in the global dictionary
|
||||
vars.update(done)
|
||||
return vars
|
||||
|
||||
|
||||
def get_makefile_filename():
|
||||
"""Return the path of the Makefile."""
|
||||
if _PYTHON_BUILD:
|
||||
|
@ -465,74 +331,6 @@ def _get_sysconfigdata_name():
|
|||
f'_sysconfigdata_{sys.abiflags}_{sys.platform}_{multiarch}',
|
||||
)
|
||||
|
||||
def _print_config_dict(d, stream):
|
||||
print ("{", file=stream)
|
||||
for k, v in sorted(d.items()):
|
||||
print(f" {k!r}: {v!r},", file=stream)
|
||||
print ("}", file=stream)
|
||||
|
||||
def _generate_posix_vars():
|
||||
"""Generate the Python module containing build-time variables."""
|
||||
vars = {}
|
||||
# load the installed Makefile:
|
||||
makefile = get_makefile_filename()
|
||||
try:
|
||||
_parse_makefile(makefile, vars)
|
||||
except OSError as e:
|
||||
msg = f"invalid Python installation: unable to open {makefile}"
|
||||
if hasattr(e, "strerror"):
|
||||
msg = f"{msg} ({e.strerror})"
|
||||
raise OSError(msg)
|
||||
# load the installed pyconfig.h:
|
||||
config_h = get_config_h_filename()
|
||||
try:
|
||||
with open(config_h, encoding="utf-8") as f:
|
||||
parse_config_h(f, vars)
|
||||
except OSError as e:
|
||||
msg = f"invalid Python installation: unable to open {config_h}"
|
||||
if hasattr(e, "strerror"):
|
||||
msg = f"{msg} ({e.strerror})"
|
||||
raise OSError(msg)
|
||||
# On AIX, there are wrong paths to the linker scripts in the Makefile
|
||||
# -- these paths are relative to the Python source, but when installed
|
||||
# the scripts are in another directory.
|
||||
if _PYTHON_BUILD:
|
||||
vars['BLDSHARED'] = vars['LDSHARED']
|
||||
|
||||
# There's a chicken-and-egg situation on OS X with regards to the
|
||||
# _sysconfigdata module after the changes introduced by #15298:
|
||||
# get_config_vars() is called by get_platform() as part of the
|
||||
# `make pybuilddir.txt` target -- which is a precursor to the
|
||||
# _sysconfigdata.py module being constructed. Unfortunately,
|
||||
# get_config_vars() eventually calls _init_posix(), which attempts
|
||||
# to import _sysconfigdata, which we won't have built yet. In order
|
||||
# for _init_posix() to work, if we're on Darwin, just mock up the
|
||||
# _sysconfigdata module manually and populate it with the build vars.
|
||||
# This is more than sufficient for ensuring the subsequent call to
|
||||
# get_platform() succeeds.
|
||||
name = _get_sysconfigdata_name()
|
||||
if 'darwin' in sys.platform:
|
||||
import types
|
||||
module = types.ModuleType(name)
|
||||
module.build_time_vars = vars
|
||||
sys.modules[name] = module
|
||||
|
||||
pybuilddir = f'build/lib.{get_platform()}-{_PY_VERSION_SHORT}'
|
||||
if hasattr(sys, "gettotalrefcount"):
|
||||
pybuilddir += '-pydebug'
|
||||
os.makedirs(pybuilddir, exist_ok=True)
|
||||
destfile = os.path.join(pybuilddir, name + '.py')
|
||||
|
||||
with open(destfile, 'w', encoding='utf8') as f:
|
||||
f.write('# system configuration generated and used by'
|
||||
' the sysconfig module\n')
|
||||
f.write('build_time_vars = ')
|
||||
_print_config_dict(vars, stream=f)
|
||||
|
||||
# Create file used for sys.path fixup -- see Modules/getpath.c
|
||||
with open('pybuilddir.txt', 'w', encoding='utf8') as f:
|
||||
f.write(pybuilddir)
|
||||
|
||||
def _init_posix(vars):
|
||||
"""Initialize the module as appropriate for POSIX systems."""
|
||||
# _sysconfigdata is generated at build time, see _generate_posix_vars()
|
||||
|
@ -857,28 +655,3 @@ def expand_makefile_vars(s, vars):
|
|||
else:
|
||||
break
|
||||
return s
|
||||
|
||||
|
||||
def _print_dict(title, data):
|
||||
for index, (key, value) in enumerate(sorted(data.items())):
|
||||
if index == 0:
|
||||
print(f'{title}: ')
|
||||
print(f'\t{key} = "{value}"')
|
||||
|
||||
|
||||
def _main():
|
||||
"""Display all information sysconfig detains."""
|
||||
if '--generate-posix-vars' in sys.argv:
|
||||
_generate_posix_vars()
|
||||
return
|
||||
print(f'Platform: "{get_platform()}"')
|
||||
print(f'Python version: "{get_python_version()}"')
|
||||
print(f'Current installation scheme: "{get_default_scheme()}"')
|
||||
print()
|
||||
_print_dict('Paths', get_paths())
|
||||
print()
|
||||
_print_dict('Variables', get_config_vars())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_main()
|
|
@ -0,0 +1,245 @@
|
|||
import os
|
||||
import sys
|
||||
from sysconfig import (
|
||||
_ALWAYS_STR,
|
||||
_PYTHON_BUILD,
|
||||
_get_sysconfigdata_name,
|
||||
get_config_h_filename,
|
||||
get_config_vars,
|
||||
get_default_scheme,
|
||||
get_makefile_filename,
|
||||
get_paths,
|
||||
get_platform,
|
||||
get_python_version,
|
||||
parse_config_h,
|
||||
)
|
||||
|
||||
|
||||
# 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 _parse_makefile(filename, vars=None, keep_unresolved=True):
|
||||
"""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.
|
||||
"""
|
||||
import re
|
||||
|
||||
if vars is None:
|
||||
vars = {}
|
||||
done = {}
|
||||
notdone = {}
|
||||
|
||||
with open(filename, encoding=sys.getfilesystemencoding(),
|
||||
errors="surrogateescape") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('#') or line.strip() == '':
|
||||
continue
|
||||
m = re.match(_variable_rx, 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:
|
||||
if n in _ALWAYS_STR:
|
||||
raise ValueError
|
||||
|
||||
v = int(v)
|
||||
except ValueError:
|
||||
# insert literal `$'
|
||||
done[n] = v.replace('$$', '$')
|
||||
else:
|
||||
done[n] = v
|
||||
|
||||
# do variable interpolation here
|
||||
variables = list(notdone.keys())
|
||||
|
||||
# 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')
|
||||
|
||||
while len(variables) > 0:
|
||||
for name in tuple(variables):
|
||||
value = notdone[name]
|
||||
m1 = re.search(_findvar1_rx, value)
|
||||
m2 = re.search(_findvar2_rx, value)
|
||||
if m1 and m2:
|
||||
m = m1 if m1.start() < m2.start() else m2
|
||||
else:
|
||||
m = m1 if m1 else m2
|
||||
if m is not None:
|
||||
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:
|
||||
if name in _ALWAYS_STR:
|
||||
raise ValueError
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
done[name] = value.strip()
|
||||
else:
|
||||
done[name] = value
|
||||
variables.remove(name)
|
||||
|
||||
if name.startswith('PY_') \
|
||||
and name[3:] in renamed_variables:
|
||||
|
||||
name = name[3:]
|
||||
if name not in done:
|
||||
done[name] = value
|
||||
|
||||
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");
|
||||
# just drop it since we can't deal
|
||||
variables.remove(name)
|
||||
|
||||
# strip spurious spaces
|
||||
for k, v in done.items():
|
||||
if isinstance(v, str):
|
||||
done[k] = v.strip()
|
||||
|
||||
# save the results in the global dictionary
|
||||
vars.update(done)
|
||||
return vars
|
||||
|
||||
|
||||
def _print_config_dict(d, stream):
|
||||
print ("{", file=stream)
|
||||
for k, v in sorted(d.items()):
|
||||
print(f" {k!r}: {v!r},", file=stream)
|
||||
print ("}", file=stream)
|
||||
|
||||
|
||||
def _generate_posix_vars():
|
||||
"""Generate the Python module containing build-time variables."""
|
||||
vars = {}
|
||||
# load the installed Makefile:
|
||||
makefile = get_makefile_filename()
|
||||
try:
|
||||
_parse_makefile(makefile, vars)
|
||||
except OSError as e:
|
||||
msg = f"invalid Python installation: unable to open {makefile}"
|
||||
if hasattr(e, "strerror"):
|
||||
msg = f"{msg} ({e.strerror})"
|
||||
raise OSError(msg)
|
||||
# load the installed pyconfig.h:
|
||||
config_h = get_config_h_filename()
|
||||
try:
|
||||
with open(config_h, encoding="utf-8") as f:
|
||||
parse_config_h(f, vars)
|
||||
except OSError as e:
|
||||
msg = f"invalid Python installation: unable to open {config_h}"
|
||||
if hasattr(e, "strerror"):
|
||||
msg = f"{msg} ({e.strerror})"
|
||||
raise OSError(msg)
|
||||
# On AIX, there are wrong paths to the linker scripts in the Makefile
|
||||
# -- these paths are relative to the Python source, but when installed
|
||||
# the scripts are in another directory.
|
||||
if _PYTHON_BUILD:
|
||||
vars['BLDSHARED'] = vars['LDSHARED']
|
||||
|
||||
# There's a chicken-and-egg situation on OS X with regards to the
|
||||
# _sysconfigdata module after the changes introduced by #15298:
|
||||
# get_config_vars() is called by get_platform() as part of the
|
||||
# `make pybuilddir.txt` target -- which is a precursor to the
|
||||
# _sysconfigdata.py module being constructed. Unfortunately,
|
||||
# get_config_vars() eventually calls _init_posix(), which attempts
|
||||
# to import _sysconfigdata, which we won't have built yet. In order
|
||||
# for _init_posix() to work, if we're on Darwin, just mock up the
|
||||
# _sysconfigdata module manually and populate it with the build vars.
|
||||
# This is more than sufficient for ensuring the subsequent call to
|
||||
# get_platform() succeeds.
|
||||
name = _get_sysconfigdata_name()
|
||||
if 'darwin' in sys.platform:
|
||||
import types
|
||||
module = types.ModuleType(name)
|
||||
module.build_time_vars = vars
|
||||
sys.modules[name] = module
|
||||
|
||||
pybuilddir = f'build/lib.{get_platform()}-{get_python_version()}'
|
||||
if hasattr(sys, "gettotalrefcount"):
|
||||
pybuilddir += '-pydebug'
|
||||
os.makedirs(pybuilddir, exist_ok=True)
|
||||
destfile = os.path.join(pybuilddir, name + '.py')
|
||||
|
||||
with open(destfile, 'w', encoding='utf8') as f:
|
||||
f.write('# system configuration generated and used by'
|
||||
' the sysconfig module\n')
|
||||
f.write('build_time_vars = ')
|
||||
_print_config_dict(vars, stream=f)
|
||||
|
||||
# Create file used for sys.path fixup -- see Modules/getpath.c
|
||||
with open('pybuilddir.txt', 'w', encoding='utf8') as f:
|
||||
f.write(pybuilddir)
|
||||
|
||||
|
||||
def _print_dict(title, data):
|
||||
for index, (key, value) in enumerate(sorted(data.items())):
|
||||
if index == 0:
|
||||
print(f'{title}: ')
|
||||
print(f'\t{key} = "{value}"')
|
||||
|
||||
|
||||
def _main():
|
||||
"""Display all information sysconfig detains."""
|
||||
if '--generate-posix-vars' in sys.argv:
|
||||
_generate_posix_vars()
|
||||
return
|
||||
print(f'Platform: "{get_platform()}"')
|
||||
print(f'Python version: "{get_python_version()}"')
|
||||
print(f'Current installation scheme: "{get_default_scheme()}"')
|
||||
print()
|
||||
_print_dict('Paths', get_paths())
|
||||
print()
|
||||
_print_dict('Variables', get_config_vars())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_main()
|
|
@ -16,7 +16,8 @@ import sysconfig
|
|||
from sysconfig import (get_paths, get_platform, get_config_vars,
|
||||
get_path, get_path_names, _INSTALL_SCHEMES,
|
||||
get_default_scheme, get_scheme_names, get_config_var,
|
||||
_expand_vars, _get_preferred_schemes, _main)
|
||||
_expand_vars, _get_preferred_schemes)
|
||||
from sysconfig.__main__ import _main, _parse_makefile
|
||||
import _imp
|
||||
import _osx_support
|
||||
import _sysconfig
|
||||
|
@ -539,7 +540,7 @@ class MakefileTests(unittest.TestCase):
|
|||
print("var5=dollar$$5", file=makefile)
|
||||
print("var6=${var3}/lib/python3.5/config-$(VAR2)$(var5)"
|
||||
"-x86_64-linux-gnu", file=makefile)
|
||||
vars = sysconfig._parse_makefile(TESTFN)
|
||||
vars = _parse_makefile(TESTFN)
|
||||
self.assertEqual(vars, {
|
||||
'var1': 'ab42',
|
||||
'VAR2': 'b42',
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
The :mod:`sysconfig` module is now a package, instead of a single-file
|
||||
module.
|
Loading…
Reference in New Issue