codekingpro/portable-devtools
114k
1import json2import os3import sys4import types5from sysconfig import (6 _ALWAYS_STR,7 _PYTHON_BUILD,8 _get_sysconfigdata_name,9 get_config_h_filename,10 get_config_var,11 get_config_vars,12 get_default_scheme,13 get_makefile_filename,14 get_paths,15 get_platform,16 get_python_version,17 parse_config_h,18)19 20 21# Regexes needed for parsing Makefile (and similar syntaxes,22# like old-style Setup files).23_variable_rx = r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)"24_findvar1_rx = r"\$\(([A-Za-z][A-Za-z0-9_]*)\)"25_findvar2_rx = r"\${([A-Za-z][A-Za-z0-9_]*)}"26 27 28def _parse_makefile(filename, vars=None, keep_unresolved=True):29 """Parse a Makefile-style file.30 31 A dictionary containing name/value pairs is returned. If an32 optional dictionary is passed in as the second argument, it is33 used instead of a new dictionary.34 """35 import re36 37 if vars is None:38 vars = {}39 done = {}40 notdone = {}41 42 with open(filename, encoding=sys.getfilesystemencoding(),43 errors="surrogateescape") as f:44 lines = f.readlines()45 46 for line in lines:47 if line.startswith('#') or line.strip() == '':48 continue49 m = re.match(_variable_rx, line)50 if m:51 n, v = m.group(1, 2)52 v = v.strip()53 # `$$' is a literal `$' in make54 tmpv = v.replace('$$', '')55 56 if "$" in tmpv:57 notdone[n] = v58 else:59 try:60 if n in _ALWAYS_STR:61 raise ValueError62 63 v = int(v)64 except ValueError:65 # insert literal `$'66 done[n] = v.replace('$$', '$')67 else:68 done[n] = v69 70 # do variable interpolation here71 variables = list(notdone.keys())72 73 # Variables with a 'PY_' prefix in the makefile. These need to74 # be made available without that prefix through sysconfig.75 # Special care is needed to ensure that variable expansion works, even76 # if the expansion uses the name without a prefix.77 renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')78 79 while len(variables) > 0:80 for name in tuple(variables):81 value = notdone[name]82 m1 = re.search(_findvar1_rx, value)83 m2 = re.search(_findvar2_rx, value)84 if m1 and m2:85 m = m1 if m1.start() < m2.start() else m286 else:87 m = m1 if m1 else m288 if m is not None:89 n = m.group(1)90 found = True91 if n in done:92 item = str(done[n])93 elif n in notdone:94 # get it on a subsequent round95 found = False96 elif n in os.environ:97 # do it like make: fall back to environment98 item = os.environ[n]99 100 elif n in renamed_variables:101 if (name.startswith('PY_') and102 name[3:] in renamed_variables):103 item = ""104 105 elif 'PY_' + n in notdone:106 found = False107 108 else:109 item = str(done['PY_' + n])110 111 else:112 done[n] = item = ""113 114 if found:115 after = value[m.end():]116 value = value[:m.start()] + item + after117 if "$" in after:118 notdone[name] = value119 else:120 try:121 if name in _ALWAYS_STR:122 raise ValueError123 value = int(value)124 except ValueError:125 done[name] = value.strip()126 else:127 done[name] = value128 variables.remove(name)129 130 if name.startswith('PY_') \131 and name[3:] in renamed_variables:132 133 name = name[3:]134 if name not in done:135 done[name] = value136 137 else:138 # Adds unresolved variables to the done dict.139 # This is disabled when called from distutils.sysconfig140 if keep_unresolved:141 done[name] = value142 # bogus variable reference (e.g. "prefix=$/opt/python");143 # just drop it since we can't deal144 variables.remove(name)145 146 # strip spurious spaces147 for k, v in done.items():148 if isinstance(v, str):149 done[k] = v.strip()150 151 # save the results in the global dictionary152 vars.update(done)153 return vars154 155 156def _print_config_dict(d, stream):157 print ("{", file=stream)158 for k, v in sorted(d.items()):159 print(f" {k!r}: {v!r},", file=stream)160 print ("}", file=stream)161 162 163def _get_pybuilddir():164 pybuilddir = f'build/lib.{get_platform()}-{get_python_version()}'165 if get_config_var('Py_DEBUG') == '1':166 pybuilddir += '-pydebug'167 return pybuilddir168 169 170def _get_json_data_name():171 name = _get_sysconfigdata_name()172 assert name.startswith('_sysconfigdata')173 return name.replace('_sysconfigdata', '_sysconfig_vars') + '.json'174 175 176def _generate_posix_vars():177 """Generate the Python module containing build-time variables."""178 vars = {}179 # load the installed Makefile:180 makefile = get_makefile_filename()181 try:182 _parse_makefile(makefile, vars)183 except OSError as e:184 msg = f"invalid Python installation: unable to open {makefile}"185 if hasattr(e, "strerror"):186 msg = f"{msg} ({e.strerror})"187 raise OSError(msg)188 # load the installed pyconfig.h:189 config_h = get_config_h_filename()190 try:191 with open(config_h, encoding="utf-8") as f:192 parse_config_h(f, vars)193 except OSError as e:194 msg = f"invalid Python installation: unable to open {config_h}"195 if hasattr(e, "strerror"):196 msg = f"{msg} ({e.strerror})"197 raise OSError(msg)198 # On AIX, there are wrong paths to the linker scripts in the Makefile199 # -- these paths are relative to the Python source, but when installed200 # the scripts are in another directory.201 if _PYTHON_BUILD:202 vars['BLDSHARED'] = vars['LDSHARED']203 204 name = _get_sysconfigdata_name()205 206 # There's a chicken-and-egg situation on OS X with regards to the207 # _sysconfigdata module after the changes introduced by #15298:208 # get_config_vars() is called by get_platform() as part of the209 # `make pybuilddir.txt` target -- which is a precursor to the210 # _sysconfigdata.py module being constructed. Unfortunately,211 # get_config_vars() eventually calls _init_posix(), which attempts212 # to import _sysconfigdata, which we won't have built yet. In order213 # for _init_posix() to work, if we're on Darwin, just mock up the214 # _sysconfigdata module manually and populate it with the build vars.215 # This is more than sufficient for ensuring the subsequent call to216 # get_platform() succeeds.217 # GH-127178: Since we started generating a .json file, we also need this to218 # be able to run sysconfig.get_config_vars().219 module = types.ModuleType(name)220 module.build_time_vars = vars221 sys.modules[name] = module222 223 pybuilddir = _get_pybuilddir()224 os.makedirs(pybuilddir, exist_ok=True)225 destfile = os.path.join(pybuilddir, name + '.py')226 227 with open(destfile, 'w', encoding='utf8') as f:228 f.write('# system configuration generated and used by'229 ' the sysconfig module\n')230 f.write('build_time_vars = ')231 _print_config_dict(vars, stream=f)232 233 print(f'Written {destfile}')234 235 install_vars = get_config_vars()236 # Fix config vars to match the values after install (of the default environment)237 install_vars['projectbase'] = install_vars['BINDIR']238 install_vars['srcdir'] = install_vars['LIBPL']239 # Write a JSON file with the output of sysconfig.get_config_vars240 jsonfile = os.path.join(pybuilddir, _get_json_data_name())241 with open(jsonfile, 'w') as f:242 json.dump(install_vars, f, indent=2)243 244 print(f'Written {jsonfile}')245 246 # Create file used for sys.path fixup -- see Modules/getpath.c247 with open('pybuilddir.txt', 'w', encoding='utf8') as f:248 f.write(pybuilddir)249 250 251def _print_dict(title, data):252 for index, (key, value) in enumerate(sorted(data.items())):253 if index == 0:254 print(f'{title}: ')255 print(f'\t{key} = "{value}"')256 257 258def _main():259 """Display all information sysconfig detains."""260 if '--generate-posix-vars' in sys.argv:261 _generate_posix_vars()262 return263 print(f'Platform: "{get_platform()}"')264 print(f'Python version: "{get_python_version()}"')265 print(f'Current installation scheme: "{get_default_scheme()}"')266 print()267 _print_dict('Paths', get_paths())268 print()269 _print_dict('Variables', get_config_vars())270 271 272if __name__ == '__main__':273 try:274 _main()275 except BrokenPipeError:276 pass277 