mirror of
https://github.com/python/cpython.git
synced 2025-11-02 06:31:29 +00:00
* Fix potential infinite recursion. * Fix a bug when reference can cross boundaries of substitutions, e.g. a=$( b=$(a)a) * Fix potential quadratic complexity. * Fix KeyError for undefined CFLAGS, LDFLAGS, or CPPFLAGS. * Fix infinite recursion when keep_unresolved=False. * Unify behavior with keep_unresolved=False for bogus $ occurred before and after variable references.
250 lines
7.9 KiB
Python
250 lines
7.9 KiB
Python
import json
|
|
import os
|
|
import sys
|
|
import types
|
|
from sysconfig import (
|
|
_ALWAYS_STR,
|
|
_PYTHON_BUILD,
|
|
_get_sysconfigdata_name,
|
|
get_config_h_filename,
|
|
get_config_var,
|
|
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*(.*)"
|
|
_findvar_rx = (r"\$(\([A-Za-z][A-Za-z0-9_]*\)"
|
|
r"|\{[A-Za-z][A-Za-z0-9_]*\}"
|
|
r"|\$?)")
|
|
|
|
|
|
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)
|
|
notdone[n] = v.strip()
|
|
|
|
# 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')
|
|
|
|
def resolve_var(name):
|
|
def repl(m):
|
|
n = m[1]
|
|
if n == '$':
|
|
return '$'
|
|
elif n == '':
|
|
# bogus variable reference (e.g. "prefix=$/opt/python")
|
|
if keep_unresolved:
|
|
return m[0]
|
|
raise ValueError
|
|
elif n[0] == '(' and n[-1] == ')':
|
|
n = n[1:-1]
|
|
elif n[0] == '{' and n[-1] == '}':
|
|
n = n[1:-1]
|
|
|
|
if n in done:
|
|
return str(done[n])
|
|
elif n in notdone:
|
|
return str(resolve_var(n))
|
|
elif n in os.environ:
|
|
# do it like make: fall back to environment
|
|
return os.environ[n]
|
|
elif n in renamed_variables:
|
|
if name.startswith('PY_') and name[3:] in renamed_variables:
|
|
return ""
|
|
n = 'PY_' + n
|
|
if n in notdone:
|
|
return str(resolve_var(n))
|
|
else:
|
|
assert n not in done
|
|
return ""
|
|
else:
|
|
done[n] = ""
|
|
return ""
|
|
|
|
assert name not in done
|
|
done[name] = ""
|
|
try:
|
|
value = re.sub(_findvar_rx, repl, notdone[name])
|
|
except ValueError:
|
|
del done[name]
|
|
return ""
|
|
value = value.strip()
|
|
if name not in _ALWAYS_STR:
|
|
try:
|
|
value = int(value)
|
|
except ValueError:
|
|
pass
|
|
done[name] = value
|
|
if name.startswith('PY_') and name[3:] in renamed_variables:
|
|
name = name[3:]
|
|
if name not in done:
|
|
done[name] = value
|
|
return value
|
|
|
|
for n in notdone:
|
|
if n not in done:
|
|
resolve_var(n)
|
|
|
|
# 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 _get_pybuilddir():
|
|
pybuilddir = f'build/lib.{get_platform()}-{get_python_version()}'
|
|
if get_config_var('Py_DEBUG') == '1':
|
|
pybuilddir += '-pydebug'
|
|
return pybuilddir
|
|
|
|
|
|
def _get_json_data_name():
|
|
name = _get_sysconfigdata_name()
|
|
assert name.startswith('_sysconfigdata')
|
|
return name.replace('_sysconfigdata', '_sysconfig_vars') + '.json'
|
|
|
|
|
|
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']
|
|
|
|
name = _get_sysconfigdata_name()
|
|
|
|
# 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.
|
|
# GH-127178: Since we started generating a .json file, we also need this to
|
|
# be able to run sysconfig.get_config_vars().
|
|
module = types.ModuleType(name)
|
|
module.build_time_vars = vars
|
|
sys.modules[name] = module
|
|
|
|
pybuilddir = _get_pybuilddir()
|
|
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)
|
|
|
|
print(f'Written {destfile}')
|
|
|
|
install_vars = get_config_vars()
|
|
# Fix config vars to match the values after install (of the default environment)
|
|
install_vars['projectbase'] = install_vars['BINDIR']
|
|
install_vars['srcdir'] = install_vars['LIBPL']
|
|
# Write a JSON file with the output of sysconfig.get_config_vars
|
|
jsonfile = os.path.join(pybuilddir, _get_json_data_name())
|
|
with open(jsonfile, 'w') as f:
|
|
json.dump(install_vars, f, indent=2)
|
|
|
|
print(f'Written {jsonfile}')
|
|
|
|
# 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__':
|
|
try:
|
|
_main()
|
|
except BrokenPipeError:
|
|
pass
|