1"""Access to Python's configuration information."""
2
3import os
4import sys
5from os.path import pardir, realpath
6
7__all__ = [
8    'get_config_h_filename',
9    'get_config_var',
10    'get_config_vars',
11    'get_makefile_filename',
12    'get_path',
13    'get_path_names',
14    'get_paths',
15    'get_platform',
16    'get_python_version',
17    'get_scheme_names',
18    'parse_config_h',
19]
20
21# Keys for get_config_var() that are never converted to Python integers.
22_ALWAYS_STR = {
23    'MACOSX_DEPLOYMENT_TARGET',
24}
25
26_INSTALL_SCHEMES = {
27    'posix_prefix': {
28        'stdlib': '{installed_base}/{platlibdir}/python{py_version_short}',
29        'platstdlib': '{platbase}/{platlibdir}/python{py_version_short}',
30        'purelib': '{base}/lib/python{py_version_short}/site-packages',
31        'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages',
32        'include':
33            '{installed_base}/include/python{py_version_short}{abiflags}',
34        'platinclude':
35            '{installed_platbase}/include/python{py_version_short}{abiflags}',
36        'scripts': '{base}/bin',
37        'data': '{base}',
38        },
39    'posix_home': {
40        'stdlib': '{installed_base}/lib/python',
41        'platstdlib': '{base}/lib/python',
42        'purelib': '{base}/lib/python',
43        'platlib': '{base}/lib/python',
44        'include': '{installed_base}/include/python',
45        'platinclude': '{installed_base}/include/python',
46        'scripts': '{base}/bin',
47        'data': '{base}',
48        },
49    'nt': {
50        'stdlib': '{installed_base}/Lib',
51        'platstdlib': '{base}/Lib',
52        'purelib': '{base}/Lib/site-packages',
53        'platlib': '{base}/Lib/site-packages',
54        'include': '{installed_base}/Include',
55        'platinclude': '{installed_base}/Include',
56        'scripts': '{base}/Scripts',
57        'data': '{base}',
58        },
59    # Downstream distributors can overwrite the default install scheme.
60    # This is done to support downstream modifications where distributors change
61    # the installation layout (eg. different site-packages directory).
62    # So, distributors will change the default scheme to one that correctly
63    # represents their layout.
64    # This presents an issue for projects/people that need to bootstrap virtual
65    # environments, like virtualenv. As distributors might now be customizing
66    # the default install scheme, there is no guarantee that the information
67    # returned by sysconfig.get_default_scheme/get_paths is correct for
68    # a virtual environment, the only guarantee we have is that it is correct
69    # for the *current* environment. When bootstrapping a virtual environment,
70    # we need to know its layout, so that we can place the files in the
71    # correct locations.
72    # The "*_venv" install scheme is a scheme to bootstrap virtual environments,
73    # essentially identical to the default posix_prefix/nt schemes.
74    # Downstream distributors who patch posix_prefix/nt scheme are encouraged to
75    # leave the following schemes unchanged
76    'posix_venv': {
77        'stdlib': '{installed_base}/{platlibdir}/python{py_version_short}',
78        'platstdlib': '{platbase}/{platlibdir}/python{py_version_short}',
79        'purelib': '{base}/lib/python{py_version_short}/site-packages',
80        'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages',
81        'include':
82            '{installed_base}/include/python{py_version_short}{abiflags}',
83        'platinclude':
84            '{installed_platbase}/include/python{py_version_short}{abiflags}',
85        'scripts': '{base}/bin',
86        'data': '{base}',
87        },
88    'nt_venv': {
89        'stdlib': '{installed_base}/Lib',
90        'platstdlib': '{base}/Lib',
91        'purelib': '{base}/Lib/site-packages',
92        'platlib': '{base}/Lib/site-packages',
93        'include': '{installed_base}/Include',
94        'platinclude': '{installed_base}/Include',
95        'scripts': '{base}/Scripts',
96        'data': '{base}',
97        },
98    }
99
100# For the OS-native venv scheme, we essentially provide an alias:
101if os.name == 'nt':
102    _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['nt_venv']
103else:
104    _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv']
105
106
107# NOTE: site.py has copy of this function.
108# Sync it when modify this function.
109def _getuserbase():
110    env_base = os.environ.get("PYTHONUSERBASE", None)
111    if env_base:
112        return env_base
113
114    # Emscripten, VxWorks, and WASI have no home directories
115    if sys.platform in {"emscripten", "vxworks", "wasi"}:
116        return None
117
118    def joinuser(*args):
119        return os.path.expanduser(os.path.join(*args))
120
121    if os.name == "nt":
122        base = os.environ.get("APPDATA") or "~"
123        return joinuser(base, "Python")
124
125    if sys.platform == "darwin" and sys._framework:
126        return joinuser("~", "Library", sys._framework,
127                        f"{sys.version_info[0]}.{sys.version_info[1]}")
128
129    return joinuser("~", ".local")
130
131_HAS_USER_BASE = (_getuserbase() is not None)
132
133if _HAS_USER_BASE:
134    _INSTALL_SCHEMES |= {
135        # NOTE: When modifying "purelib" scheme, update site._get_path() too.
136        'nt_user': {
137            'stdlib': '{userbase}/Python{py_version_nodot_plat}',
138            'platstdlib': '{userbase}/Python{py_version_nodot_plat}',
139            'purelib': '{userbase}/Python{py_version_nodot_plat}/site-packages',
140            'platlib': '{userbase}/Python{py_version_nodot_plat}/site-packages',
141            'include': '{userbase}/Python{py_version_nodot_plat}/Include',
142            'scripts': '{userbase}/Python{py_version_nodot_plat}/Scripts',
143            'data': '{userbase}',
144            },
145        'posix_user': {
146            'stdlib': '{userbase}/{platlibdir}/python{py_version_short}',
147            'platstdlib': '{userbase}/{platlibdir}/python{py_version_short}',
148            'purelib': '{userbase}/lib/python{py_version_short}/site-packages',
149            'platlib': '{userbase}/lib/python{py_version_short}/site-packages',
150            'include': '{userbase}/include/python{py_version_short}',
151            'scripts': '{userbase}/bin',
152            'data': '{userbase}',
153            },
154        'osx_framework_user': {
155            'stdlib': '{userbase}/lib/python',
156            'platstdlib': '{userbase}/lib/python',
157            'purelib': '{userbase}/lib/python/site-packages',
158            'platlib': '{userbase}/lib/python/site-packages',
159            'include': '{userbase}/include/python{py_version_short}',
160            'scripts': '{userbase}/bin',
161            'data': '{userbase}',
162            },
163    }
164
165_SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include',
166                'scripts', 'data')
167
168_PY_VERSION = sys.version.split()[0]
169_PY_VERSION_SHORT = f'{sys.version_info[0]}.{sys.version_info[1]}'
170_PY_VERSION_SHORT_NO_DOT = f'{sys.version_info[0]}{sys.version_info[1]}'
171_PREFIX = os.path.normpath(sys.prefix)
172_BASE_PREFIX = os.path.normpath(sys.base_prefix)
173_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
174_BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix)
175_CONFIG_VARS = None
176_USER_BASE = None
177
178# Regexes needed for parsing Makefile (and similar syntaxes,
179# like old-style Setup files).
180_variable_rx = r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)"
181_findvar1_rx = r"\$\(([A-Za-z][A-Za-z0-9_]*)\)"
182_findvar2_rx = r"\${([A-Za-z][A-Za-z0-9_]*)}"
183
184
185def _safe_realpath(path):
186    try:
187        return realpath(path)
188    except OSError:
189        return path
190
191if sys.executable:
192    _PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
193else:
194    # sys.executable can be empty if argv[0] has been changed and Python is
195    # unable to retrieve the real program name
196    _PROJECT_BASE = _safe_realpath(os.getcwd())
197
198# In a virtual environment, `sys._home` gives us the target directory
199# `_PROJECT_BASE` for the executable that created it when the virtual
200# python is an actual executable ('venv --copies' or Windows).
201_sys_home = getattr(sys, '_home', None)
202if _sys_home:
203    _PROJECT_BASE = _sys_home
204
205if os.name == 'nt':
206    # In a source build, the executable is in a subdirectory of the root
207    # that we want (<root>\PCbuild\<platname>).
208    # `_BASE_PREFIX` is used as the base installation is where the source
209    # will be.  The realpath is needed to prevent mount point confusion
210    # that can occur with just string comparisons.
211    if _safe_realpath(_PROJECT_BASE).startswith(
212            _safe_realpath(f'{_BASE_PREFIX}\\PCbuild')):
213        _PROJECT_BASE = _BASE_PREFIX
214
215# set for cross builds
216if "_PYTHON_PROJECT_BASE" in os.environ:
217    _PROJECT_BASE = _safe_realpath(os.environ["_PYTHON_PROJECT_BASE"])
218
219def is_python_build(check_home=None):
220    if check_home is not None:
221        import warnings
222        warnings.warn("check_home argument is deprecated and ignored.",
223                      DeprecationWarning, stacklevel=2)
224    for fn in ("Setup", "Setup.local"):
225        if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
226            return True
227    return False
228
229_PYTHON_BUILD = is_python_build()
230
231if _PYTHON_BUILD:
232    for scheme in ('posix_prefix', 'posix_home'):
233        # On POSIX-y platforms, Python will:
234        # - Build from .h files in 'headers' (which is only added to the
235        #   scheme when building CPython)
236        # - Install .h files to 'include'
237        scheme = _INSTALL_SCHEMES[scheme]
238        scheme['headers'] = scheme['include']
239        scheme['include'] = '{srcdir}/Include'
240        scheme['platinclude'] = '{projectbase}/.'
241    del scheme
242
243
244def _subst_vars(s, local_vars):
245    try:
246        return s.format(**local_vars)
247    except KeyError as var:
248        try:
249            return s.format(**os.environ)
250        except KeyError:
251            raise AttributeError(f'{var}') from None
252
253def _extend_dict(target_dict, other_dict):
254    target_keys = target_dict.keys()
255    for key, value in other_dict.items():
256        if key in target_keys:
257            continue
258        target_dict[key] = value
259
260
261def _expand_vars(scheme, vars):
262    res = {}
263    if vars is None:
264        vars = {}
265    _extend_dict(vars, get_config_vars())
266    if os.name == 'nt':
267        # On Windows we want to substitute 'lib' for schemes rather
268        # than the native value (without modifying vars, in case it
269        # was passed in)
270        vars = vars | {'platlibdir': 'lib'}
271
272    for key, value in _INSTALL_SCHEMES[scheme].items():
273        if os.name in ('posix', 'nt'):
274            value = os.path.expanduser(value)
275        res[key] = os.path.normpath(_subst_vars(value, vars))
276    return res
277
278
279def _get_preferred_schemes():
280    if os.name == 'nt':
281        return {
282            'prefix': 'nt',
283            'home': 'posix_home',
284            'user': 'nt_user',
285        }
286    if sys.platform == 'darwin' and sys._framework:
287        return {
288            'prefix': 'posix_prefix',
289            'home': 'posix_home',
290            'user': 'osx_framework_user',
291        }
292    return {
293        'prefix': 'posix_prefix',
294        'home': 'posix_home',
295        'user': 'posix_user',
296    }
297
298
299def get_preferred_scheme(key):
300    if key == 'prefix' and sys.prefix != sys.base_prefix:
301        return 'venv'
302    scheme = _get_preferred_schemes()[key]
303    if scheme not in _INSTALL_SCHEMES:
304        raise ValueError(
305            f"{key!r} returned {scheme!r}, which is not a valid scheme "
306            f"on this platform"
307        )
308    return scheme
309
310
311def get_default_scheme():
312    return get_preferred_scheme('prefix')
313
314
315def _parse_makefile(filename, vars=None, keep_unresolved=True):
316    """Parse a Makefile-style file.
317
318    A dictionary containing name/value pairs is returned.  If an
319    optional dictionary is passed in as the second argument, it is
320    used instead of a new dictionary.
321    """
322    import re
323
324    if vars is None:
325        vars = {}
326    done = {}
327    notdone = {}
328
329    with open(filename, encoding=sys.getfilesystemencoding(),
330              errors="surrogateescape") as f:
331        lines = f.readlines()
332
333    for line in lines:
334        if line.startswith('#') or line.strip() == '':
335            continue
336        m = re.match(_variable_rx, line)
337        if m:
338            n, v = m.group(1, 2)
339            v = v.strip()
340            # `$$' is a literal `$' in make
341            tmpv = v.replace('$$', '')
342
343            if "$" in tmpv:
344                notdone[n] = v
345            else:
346                try:
347                    if n in _ALWAYS_STR:
348                        raise ValueError
349
350                    v = int(v)
351                except ValueError:
352                    # insert literal `$'
353                    done[n] = v.replace('$$', '$')
354                else:
355                    done[n] = v
356
357    # do variable interpolation here
358    variables = list(notdone.keys())
359
360    # Variables with a 'PY_' prefix in the makefile. These need to
361    # be made available without that prefix through sysconfig.
362    # Special care is needed to ensure that variable expansion works, even
363    # if the expansion uses the name without a prefix.
364    renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')
365
366    while len(variables) > 0:
367        for name in tuple(variables):
368            value = notdone[name]
369            m1 = re.search(_findvar1_rx, value)
370            m2 = re.search(_findvar2_rx, value)
371            if m1 and m2:
372                m = m1 if m1.start() < m2.start() else m2
373            else:
374                m = m1 if m1 else m2
375            if m is not None:
376                n = m.group(1)
377                found = True
378                if n in done:
379                    item = str(done[n])
380                elif n in notdone:
381                    # get it on a subsequent round
382                    found = False
383                elif n in os.environ:
384                    # do it like make: fall back to environment
385                    item = os.environ[n]
386
387                elif n in renamed_variables:
388                    if (name.startswith('PY_') and
389                        name[3:] in renamed_variables):
390                        item = ""
391
392                    elif 'PY_' + n in notdone:
393                        found = False
394
395                    else:
396                        item = str(done['PY_' + n])
397
398                else:
399                    done[n] = item = ""
400
401                if found:
402                    after = value[m.end():]
403                    value = value[:m.start()] + item + after
404                    if "$" in after:
405                        notdone[name] = value
406                    else:
407                        try:
408                            if name in _ALWAYS_STR:
409                                raise ValueError
410                            value = int(value)
411                        except ValueError:
412                            done[name] = value.strip()
413                        else:
414                            done[name] = value
415                        variables.remove(name)
416
417                        if name.startswith('PY_') \
418                        and name[3:] in renamed_variables:
419
420                            name = name[3:]
421                            if name not in done:
422                                done[name] = value
423
424            else:
425                # Adds unresolved variables to the done dict.
426                # This is disabled when called from distutils.sysconfig
427                if keep_unresolved:
428                    done[name] = value
429                # bogus variable reference (e.g. "prefix=$/opt/python");
430                # just drop it since we can't deal
431                variables.remove(name)
432
433    # strip spurious spaces
434    for k, v in done.items():
435        if isinstance(v, str):
436            done[k] = v.strip()
437
438    # save the results in the global dictionary
439    vars.update(done)
440    return vars
441
442
443def get_makefile_filename():
444    """Return the path of the Makefile."""
445    if _PYTHON_BUILD:
446        return os.path.join(_PROJECT_BASE, "Makefile")
447    if hasattr(sys, 'abiflags'):
448        config_dir_name = f'config-{_PY_VERSION_SHORT}{sys.abiflags}'
449    else:
450        config_dir_name = 'config'
451    if hasattr(sys.implementation, '_multiarch'):
452        config_dir_name += f'-{sys.implementation._multiarch}'
453    return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
454
455
456def _get_sysconfigdata_name():
457    multiarch = getattr(sys.implementation, '_multiarch', '')
458    return os.environ.get(
459        '_PYTHON_SYSCONFIGDATA_NAME',
460        f'_sysconfigdata_{sys.abiflags}_{sys.platform}_{multiarch}',
461    )
462
463
464def _generate_posix_vars():
465    """Generate the Python module containing build-time variables."""
466    import pprint
467    vars = {}
468    # load the installed Makefile:
469    makefile = get_makefile_filename()
470    try:
471        _parse_makefile(makefile, vars)
472    except OSError as e:
473        msg = f"invalid Python installation: unable to open {makefile}"
474        if hasattr(e, "strerror"):
475            msg = f"{msg} ({e.strerror})"
476        raise OSError(msg)
477    # load the installed pyconfig.h:
478    config_h = get_config_h_filename()
479    try:
480        with open(config_h, encoding="utf-8") as f:
481            parse_config_h(f, vars)
482    except OSError as e:
483        msg = f"invalid Python installation: unable to open {config_h}"
484        if hasattr(e, "strerror"):
485            msg = f"{msg} ({e.strerror})"
486        raise OSError(msg)
487    # On AIX, there are wrong paths to the linker scripts in the Makefile
488    # -- these paths are relative to the Python source, but when installed
489    # the scripts are in another directory.
490    if _PYTHON_BUILD:
491        vars['BLDSHARED'] = vars['LDSHARED']
492
493    # There's a chicken-and-egg situation on OS X with regards to the
494    # _sysconfigdata module after the changes introduced by #15298:
495    # get_config_vars() is called by get_platform() as part of the
496    # `make pybuilddir.txt` target -- which is a precursor to the
497    # _sysconfigdata.py module being constructed.  Unfortunately,
498    # get_config_vars() eventually calls _init_posix(), which attempts
499    # to import _sysconfigdata, which we won't have built yet.  In order
500    # for _init_posix() to work, if we're on Darwin, just mock up the
501    # _sysconfigdata module manually and populate it with the build vars.
502    # This is more than sufficient for ensuring the subsequent call to
503    # get_platform() succeeds.
504    name = _get_sysconfigdata_name()
505    if 'darwin' in sys.platform:
506        import types
507        module = types.ModuleType(name)
508        module.build_time_vars = vars
509        sys.modules[name] = module
510
511    pybuilddir = f'build/lib.{get_platform()}-{_PY_VERSION_SHORT}'
512    if hasattr(sys, "gettotalrefcount"):
513        pybuilddir += '-pydebug'
514    os.makedirs(pybuilddir, exist_ok=True)
515    destfile = os.path.join(pybuilddir, name + '.py')
516
517    with open(destfile, 'w', encoding='utf8') as f:
518        f.write('# system configuration generated and used by'
519                ' the sysconfig module\n')
520        f.write('build_time_vars = ')
521        pprint.pprint(vars, stream=f)
522
523    # Create file used for sys.path fixup -- see Modules/getpath.c
524    with open('pybuilddir.txt', 'w', encoding='utf8') as f:
525        f.write(pybuilddir)
526
527def _init_posix(vars):
528    """Initialize the module as appropriate for POSIX systems."""
529    # _sysconfigdata is generated at build time, see _generate_posix_vars()
530    name = _get_sysconfigdata_name()
531    _temp = __import__(name, globals(), locals(), ['build_time_vars'], 0)
532    build_time_vars = _temp.build_time_vars
533    vars.update(build_time_vars)
534
535def _init_non_posix(vars):
536    """Initialize the module as appropriate for NT"""
537    # set basic install directories
538    import _imp
539    vars['LIBDEST'] = get_path('stdlib')
540    vars['BINLIBDEST'] = get_path('platstdlib')
541    vars['INCLUDEPY'] = get_path('include')
542    vars['EXT_SUFFIX'] = _imp.extension_suffixes()[0]
543    vars['EXE'] = '.exe'
544    vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
545    vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
546    vars['TZPATH'] = ''
547
548#
549# public APIs
550#
551
552
553def parse_config_h(fp, vars=None):
554    """Parse a config.h-style file.
555
556    A dictionary containing name/value pairs is returned.  If an
557    optional dictionary is passed in as the second argument, it is
558    used instead of a new dictionary.
559    """
560    if vars is None:
561        vars = {}
562    import re
563    define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
564    undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
565
566    while True:
567        line = fp.readline()
568        if not line:
569            break
570        m = define_rx.match(line)
571        if m:
572            n, v = m.group(1, 2)
573            try:
574                if n in _ALWAYS_STR:
575                    raise ValueError
576                v = int(v)
577            except ValueError:
578                pass
579            vars[n] = v
580        else:
581            m = undef_rx.match(line)
582            if m:
583                vars[m.group(1)] = 0
584    return vars
585
586
587def get_config_h_filename():
588    """Return the path of pyconfig.h."""
589    if _PYTHON_BUILD:
590        if os.name == "nt":
591            inc_dir = os.path.join(_PROJECT_BASE, "PC")
592        else:
593            inc_dir = _PROJECT_BASE
594    else:
595        inc_dir = get_path('platinclude')
596    return os.path.join(inc_dir, 'pyconfig.h')
597
598
599def get_scheme_names():
600    """Return a tuple containing the schemes names."""
601    return tuple(sorted(_INSTALL_SCHEMES))
602
603
604def get_path_names():
605    """Return a tuple containing the paths names."""
606    return _SCHEME_KEYS
607
608
609def get_paths(scheme=get_default_scheme(), vars=None, expand=True):
610    """Return a mapping containing an install scheme.
611
612    ``scheme`` is the install scheme name. If not provided, it will
613    return the default scheme for the current platform.
614    """
615    if expand:
616        return _expand_vars(scheme, vars)
617    else:
618        return _INSTALL_SCHEMES[scheme]
619
620
621def get_path(name, scheme=get_default_scheme(), vars=None, expand=True):
622    """Return a path corresponding to the scheme.
623
624    ``scheme`` is the install scheme name.
625    """
626    return get_paths(scheme, vars, expand)[name]
627
628
629def get_config_vars(*args):
630    """With no arguments, return a dictionary of all configuration
631    variables relevant for the current platform.
632
633    On Unix, this means every variable defined in Python's installed Makefile;
634    On Windows it's a much smaller set.
635
636    With arguments, return a list of values that result from looking up
637    each argument in the configuration variable dictionary.
638    """
639    global _CONFIG_VARS
640    if _CONFIG_VARS is None:
641        _CONFIG_VARS = {}
642        # Normalized versions of prefix and exec_prefix are handy to have;
643        # in fact, these are the standard versions used most places in the
644        # Distutils.
645        _CONFIG_VARS['prefix'] = _PREFIX
646        _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
647        _CONFIG_VARS['py_version'] = _PY_VERSION
648        _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
649        _CONFIG_VARS['py_version_nodot'] = _PY_VERSION_SHORT_NO_DOT
650        _CONFIG_VARS['installed_base'] = _BASE_PREFIX
651        _CONFIG_VARS['base'] = _PREFIX
652        _CONFIG_VARS['installed_platbase'] = _BASE_EXEC_PREFIX
653        _CONFIG_VARS['platbase'] = _EXEC_PREFIX
654        _CONFIG_VARS['projectbase'] = _PROJECT_BASE
655        _CONFIG_VARS['platlibdir'] = sys.platlibdir
656        try:
657            _CONFIG_VARS['abiflags'] = sys.abiflags
658        except AttributeError:
659            # sys.abiflags may not be defined on all platforms.
660            _CONFIG_VARS['abiflags'] = ''
661        try:
662            _CONFIG_VARS['py_version_nodot_plat'] = sys.winver.replace('.', '')
663        except AttributeError:
664            _CONFIG_VARS['py_version_nodot_plat'] = ''
665
666        if os.name == 'nt':
667            _init_non_posix(_CONFIG_VARS)
668            _CONFIG_VARS['VPATH'] = sys._vpath
669        if os.name == 'posix':
670            _init_posix(_CONFIG_VARS)
671        if _HAS_USER_BASE:
672            # Setting 'userbase' is done below the call to the
673            # init function to enable using 'get_config_var' in
674            # the init-function.
675            _CONFIG_VARS['userbase'] = _getuserbase()
676
677        # Always convert srcdir to an absolute path
678        srcdir = _CONFIG_VARS.get('srcdir', _PROJECT_BASE)
679        if os.name == 'posix':
680            if _PYTHON_BUILD:
681                # If srcdir is a relative path (typically '.' or '..')
682                # then it should be interpreted relative to the directory
683                # containing Makefile.
684                base = os.path.dirname(get_makefile_filename())
685                srcdir = os.path.join(base, srcdir)
686            else:
687                # srcdir is not meaningful since the installation is
688                # spread about the filesystem.  We choose the
689                # directory containing the Makefile since we know it
690                # exists.
691                srcdir = os.path.dirname(get_makefile_filename())
692        _CONFIG_VARS['srcdir'] = _safe_realpath(srcdir)
693
694        # OS X platforms require special customization to handle
695        # multi-architecture, multi-os-version installers
696        if sys.platform == 'darwin':
697            import _osx_support
698            _osx_support.customize_config_vars(_CONFIG_VARS)
699
700    if args:
701        vals = []
702        for name in args:
703            vals.append(_CONFIG_VARS.get(name))
704        return vals
705    else:
706        return _CONFIG_VARS
707
708
709def get_config_var(name):
710    """Return the value of a single variable using the dictionary returned by
711    'get_config_vars()'.
712
713    Equivalent to get_config_vars().get(name)
714    """
715    return get_config_vars().get(name)
716
717
718def get_platform():
719    """Return a string that identifies the current platform.
720
721    This is used mainly to distinguish platform-specific build directories and
722    platform-specific built distributions.  Typically includes the OS name and
723    version and the architecture (as supplied by 'os.uname()'), although the
724    exact information included depends on the OS; on Linux, the kernel version
725    isn't particularly important.
726
727    Examples of returned values:
728       linux-i586
729       linux-alpha (?)
730       solaris-2.6-sun4u
731
732    Windows will return one of:
733       win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
734       win32 (all others - specifically, sys.platform is returned)
735
736    For other non-POSIX platforms, currently just returns 'sys.platform'.
737
738    """
739    if os.name == 'nt':
740        if 'amd64' in sys.version.lower():
741            return 'win-amd64'
742        if '(arm)' in sys.version.lower():
743            return 'win-arm32'
744        if '(arm64)' in sys.version.lower():
745            return 'win-arm64'
746        return sys.platform
747
748    if os.name != "posix" or not hasattr(os, 'uname'):
749        # XXX what about the architecture? NT is Intel or Alpha
750        return sys.platform
751
752    # Set for cross builds explicitly
753    if "_PYTHON_HOST_PLATFORM" in os.environ:
754        return os.environ["_PYTHON_HOST_PLATFORM"]
755
756    # Try to distinguish various flavours of Unix
757    osname, host, release, version, machine = os.uname()
758
759    # Convert the OS name to lowercase, remove '/' characters, and translate
760    # spaces (for "Power Macintosh")
761    osname = osname.lower().replace('/', '')
762    machine = machine.replace(' ', '_')
763    machine = machine.replace('/', '-')
764
765    if osname[:5] == "linux":
766        # At least on Linux/Intel, 'machine' is the processor --
767        # i386, etc.
768        # XXX what about Alpha, SPARC, etc?
769        return  f"{osname}-{machine}"
770    elif osname[:5] == "sunos":
771        if release[0] >= "5":           # SunOS 5 == Solaris 2
772            osname = "solaris"
773            release = f"{int(release[0]) - 3}.{release[2:]}"
774            # We can't use "platform.architecture()[0]" because a
775            # bootstrap problem. We use a dict to get an error
776            # if some suspicious happens.
777            bitness = {2147483647:"32bit", 9223372036854775807:"64bit"}
778            machine += f".{bitness[sys.maxsize]}"
779        # fall through to standard osname-release-machine representation
780    elif osname[:3] == "aix":
781        from _aix_support import aix_platform
782        return aix_platform()
783    elif osname[:6] == "cygwin":
784        osname = "cygwin"
785        import re
786        rel_re = re.compile(r'[\d.]+')
787        m = rel_re.match(release)
788        if m:
789            release = m.group()
790    elif osname[:6] == "darwin":
791        import _osx_support
792        osname, release, machine = _osx_support.get_platform_osx(
793                                            get_config_vars(),
794                                            osname, release, machine)
795
796    return f"{osname}-{release}-{machine}"
797
798
799def get_python_version():
800    return _PY_VERSION_SHORT
801
802
803def expand_makefile_vars(s, vars):
804    """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in
805    'string' according to 'vars' (a dictionary mapping variable names to
806    values).  Variables not present in 'vars' are silently expanded to the
807    empty string.  The variable values in 'vars' should not contain further
808    variable expansions; if 'vars' is the output of 'parse_makefile()',
809    you're fine.  Returns a variable-expanded version of 's'.
810    """
811    import re
812
813    # This algorithm does multiple expansion, so if vars['foo'] contains
814    # "${bar}", it will expand ${foo} to ${bar}, and then expand
815    # ${bar}... and so forth.  This is fine as long as 'vars' comes from
816    # 'parse_makefile()', which takes care of such expansions eagerly,
817    # according to make's variable expansion semantics.
818
819    while True:
820        m = re.search(_findvar1_rx, s) or re.search(_findvar2_rx, s)
821        if m:
822            (beg, end) = m.span()
823            s = s[0:beg] + vars.get(m.group(1)) + s[end:]
824        else:
825            break
826    return s
827
828
829def _print_dict(title, data):
830    for index, (key, value) in enumerate(sorted(data.items())):
831        if index == 0:
832            print(f'{title}: ')
833        print(f'\t{key} = "{value}"')
834
835
836def _main():
837    """Display all information sysconfig detains."""
838    if '--generate-posix-vars' in sys.argv:
839        _generate_posix_vars()
840        return
841    print(f'Platform: "{get_platform()}"')
842    print(f'Python version: "{get_python_version()}"')
843    print(f'Current installation scheme: "{get_default_scheme()}"')
844    print()
845    _print_dict('Paths', get_paths())
846    print()
847    _print_dict('Variables', get_config_vars())
848
849
850if __name__ == '__main__':
851    _main()
852