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