| 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 |