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