1import os 2import sys 3from sysconfig import ( 4 _ALWAYS_STR, 5 _PYTHON_BUILD, 6 _get_sysconfigdata_name, 7 get_config_h_filename, 8 get_config_vars, 9 get_default_scheme, 10 get_makefile_filename, 11 get_paths, 12 get_platform, 13 get_python_version, 14 parse_config_h, 15) 16 17 18# Regexes needed for parsing Makefile (and similar syntaxes, 19# like old-style Setup files). 20_variable_rx = r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)" 21_findvar1_rx = r"\$\(([A-Za-z][A-Za-z0-9_]*)\)" 22_findvar2_rx = r"\${([A-Za-z][A-Za-z0-9_]*)}" 23 24 25def _parse_makefile(filename, vars=None, keep_unresolved=True): 26 """Parse a Makefile-style file. 27 28 A dictionary containing name/value pairs is returned. If an 29 optional dictionary is passed in as the second argument, it is 30 used instead of a new dictionary. 31 """ 32 import re 33 34 if vars is None: 35 vars = {} 36 done = {} 37 notdone = {} 38 39 with open(filename, encoding=sys.getfilesystemencoding(), 40 errors="surrogateescape") as f: 41 lines = f.readlines() 42 43 for line in lines: 44 if line.startswith('#') or line.strip() == '': 45 continue 46 m = re.match(_variable_rx, line) 47 if m: 48 n, v = m.group(1, 2) 49 v = v.strip() 50 # `$$' is a literal `$' in make 51 tmpv = v.replace('$$', '') 52 53 if "$" in tmpv: 54 notdone[n] = v 55 else: 56 try: 57 if n in _ALWAYS_STR: 58 raise ValueError 59 60 v = int(v) 61 except ValueError: 62 # insert literal `$' 63 done[n] = v.replace('$$', '$') 64 else: 65 done[n] = v 66 67 # do variable interpolation here 68 variables = list(notdone.keys()) 69 70 # Variables with a 'PY_' prefix in the makefile. These need to 71 # be made available without that prefix through sysconfig. 72 # Special care is needed to ensure that variable expansion works, even 73 # if the expansion uses the name without a prefix. 74 renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS') 75 76 while len(variables) > 0: 77 for name in tuple(variables): 78 value = notdone[name] 79 m1 = re.search(_findvar1_rx, value) 80 m2 = re.search(_findvar2_rx, value) 81 if m1 and m2: 82 m = m1 if m1.start() < m2.start() else m2 83 else: 84 m = m1 if m1 else m2 85 if m is not None: 86 n = m.group(1) 87 found = True 88 if n in done: 89 item = str(done[n]) 90 elif n in notdone: 91 # get it on a subsequent round 92 found = False 93 elif n in os.environ: 94 # do it like make: fall back to environment 95 item = os.environ[n] 96 97 elif n in renamed_variables: 98 if (name.startswith('PY_') and 99 name[3:] in renamed_variables): 100 item = "" 101 102 elif 'PY_' + n in notdone: 103 found = False 104 105 else: 106 item = str(done['PY_' + n]) 107 108 else: 109 done[n] = item = "" 110 111 if found: 112 after = value[m.end():] 113 value = value[:m.start()] + item + after 114 if "$" in after: 115 notdone[name] = value 116 else: 117 try: 118 if name in _ALWAYS_STR: 119 raise ValueError 120 value = int(value) 121 except ValueError: 122 done[name] = value.strip() 123 else: 124 done[name] = value 125 variables.remove(name) 126 127 if name.startswith('PY_') \ 128 and name[3:] in renamed_variables: 129 130 name = name[3:] 131 if name not in done: 132 done[name] = value 133 134 else: 135 # Adds unresolved variables to the done dict. 136 # This is disabled when called from distutils.sysconfig 137 if keep_unresolved: 138 done[name] = value 139 # bogus variable reference (e.g. "prefix=$/opt/python"); 140 # just drop it since we can't deal 141 variables.remove(name) 142 143 # strip spurious spaces 144 for k, v in done.items(): 145 if isinstance(v, str): 146 done[k] = v.strip() 147 148 # save the results in the global dictionary 149 vars.update(done) 150 return vars 151 152 153def _print_config_dict(d, stream): 154 print ("{", file=stream) 155 for k, v in sorted(d.items()): 156 print(f" {k!r}: {v!r},", file=stream) 157 print ("}", file=stream) 158 159 160def _generate_posix_vars(): 161 """Generate the Python module containing build-time variables.""" 162 vars = {} 163 # load the installed Makefile: 164 makefile = get_makefile_filename() 165 try: 166 _parse_makefile(makefile, vars) 167 except OSError as e: 168 msg = f"invalid Python installation: unable to open {makefile}" 169 if hasattr(e, "strerror"): 170 msg = f"{msg} ({e.strerror})" 171 raise OSError(msg) 172 # load the installed pyconfig.h: 173 config_h = get_config_h_filename() 174 try: 175 with open(config_h, encoding="utf-8") as f: 176 parse_config_h(f, vars) 177 except OSError as e: 178 msg = f"invalid Python installation: unable to open {config_h}" 179 if hasattr(e, "strerror"): 180 msg = f"{msg} ({e.strerror})" 181 raise OSError(msg) 182 # On AIX, there are wrong paths to the linker scripts in the Makefile 183 # -- these paths are relative to the Python source, but when installed 184 # the scripts are in another directory. 185 if _PYTHON_BUILD: 186 vars['BLDSHARED'] = vars['LDSHARED'] 187 188 # There's a chicken-and-egg situation on OS X with regards to the 189 # _sysconfigdata module after the changes introduced by #15298: 190 # get_config_vars() is called by get_platform() as part of the 191 # `make pybuilddir.txt` target -- which is a precursor to the 192 # _sysconfigdata.py module being constructed. Unfortunately, 193 # get_config_vars() eventually calls _init_posix(), which attempts 194 # to import _sysconfigdata, which we won't have built yet. In order 195 # for _init_posix() to work, if we're on Darwin, just mock up the 196 # _sysconfigdata module manually and populate it with the build vars. 197 # This is more than sufficient for ensuring the subsequent call to 198 # get_platform() succeeds. 199 name = _get_sysconfigdata_name() 200 if 'darwin' in sys.platform: 201 import types 202 module = types.ModuleType(name) 203 module.build_time_vars = vars 204 sys.modules[name] = module 205 206 pybuilddir = f'build/lib.{get_platform()}-{get_python_version()}' 207 if hasattr(sys, "gettotalrefcount"): 208 pybuilddir += '-pydebug' 209 os.makedirs(pybuilddir, exist_ok=True) 210 destfile = os.path.join(pybuilddir, name + '.py') 211 212 with open(destfile, 'w', encoding='utf8') as f: 213 f.write('# system configuration generated and used by' 214 ' the sysconfig module\n') 215 f.write('build_time_vars = ') 216 _print_config_dict(vars, stream=f) 217 218 # Create file used for sys.path fixup -- see Modules/getpath.c 219 with open('pybuilddir.txt', 'w', encoding='utf8') as f: 220 f.write(pybuilddir) 221 222 223def _print_dict(title, data): 224 for index, (key, value) in enumerate(sorted(data.items())): 225 if index == 0: 226 print(f'{title}: ') 227 print(f'\t{key} = "{value}"') 228 229 230def _main(): 231 """Display all information sysconfig detains.""" 232 if '--generate-posix-vars' in sys.argv: 233 _generate_posix_vars() 234 return 235 print(f'Platform: "{get_platform()}"') 236 print(f'Python version: "{get_python_version()}"') 237 print(f'Current installation scheme: "{get_default_scheme()}"') 238 print() 239 _print_dict('Paths', get_paths()) 240 print() 241 _print_dict('Variables', get_config_vars()) 242 243 244if __name__ == '__main__': 245 try: 246 _main() 247 except BrokenPipeError: 248 pass 249