• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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