• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Access to Python's configuration information."""
2
3import os
4import sys
5import threading
6from os.path import realpath
7
8__all__ = [
9    'get_config_h_filename',
10    'get_config_var',
11    'get_config_vars',
12    'get_makefile_filename',
13    'get_path',
14    'get_path_names',
15    'get_paths',
16    'get_platform',
17    'get_python_version',
18    'get_scheme_names',
19    'parse_config_h',
20]
21
22# Keys for get_config_var() that are never converted to Python integers.
23_ALWAYS_STR = {
24    'IPHONEOS_DEPLOYMENT_TARGET',
25    'MACOSX_DEPLOYMENT_TARGET',
26}
27
28_INSTALL_SCHEMES = {
29    'posix_prefix': {
30        'stdlib': '{installed_base}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}',
31        'platstdlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}',
32        'purelib': '{base}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages',
33        'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}/site-packages',
34        'include':
35            '{installed_base}/include/{implementation_lower}{py_version_short}{abiflags}',
36        'platinclude':
37            '{installed_platbase}/include/{implementation_lower}{py_version_short}{abiflags}',
38        'scripts': '{base}/bin',
39        'data': '{base}',
40        },
41    'posix_home': {
42        'stdlib': '{installed_base}/lib/{implementation_lower}',
43        'platstdlib': '{base}/lib/{implementation_lower}',
44        'purelib': '{base}/lib/{implementation_lower}',
45        'platlib': '{base}/lib/{implementation_lower}',
46        'include': '{installed_base}/include/{implementation_lower}',
47        'platinclude': '{installed_base}/include/{implementation_lower}',
48        'scripts': '{base}/bin',
49        'data': '{base}',
50        },
51    'nt': {
52        'stdlib': '{installed_base}/Lib',
53        'platstdlib': '{base}/Lib',
54        'purelib': '{base}/Lib/site-packages',
55        'platlib': '{base}/Lib/site-packages',
56        'include': '{installed_base}/Include',
57        'platinclude': '{installed_base}/Include',
58        'scripts': '{base}/Scripts',
59        'data': '{base}',
60        },
61
62    # Downstream distributors can overwrite the default install scheme.
63    # This is done to support downstream modifications where distributors change
64    # the installation layout (eg. different site-packages directory).
65    # So, distributors will change the default scheme to one that correctly
66    # represents their layout.
67    # This presents an issue for projects/people that need to bootstrap virtual
68    # environments, like virtualenv. As distributors might now be customizing
69    # the default install scheme, there is no guarantee that the information
70    # returned by sysconfig.get_default_scheme/get_paths is correct for
71    # a virtual environment, the only guarantee we have is that it is correct
72    # for the *current* environment. When bootstrapping a virtual environment,
73    # we need to know its layout, so that we can place the files in the
74    # correct locations.
75    # The "*_venv" install scheme is a scheme to bootstrap virtual environments,
76    # essentially identical to the default posix_prefix/nt schemes.
77    # Downstream distributors who patch posix_prefix/nt scheme are encouraged to
78    # leave the following schemes unchanged
79    'posix_venv': {
80        'stdlib': '{installed_base}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}',
81        'platstdlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}',
82        'purelib': '{base}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages',
83        'platlib': '{platbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}/site-packages',
84        'include':
85            '{installed_base}/include/{implementation_lower}{py_version_short}{abiflags}',
86        'platinclude':
87            '{installed_platbase}/include/{implementation_lower}{py_version_short}{abiflags}',
88        'scripts': '{base}/bin',
89        'data': '{base}',
90        },
91    'nt_venv': {
92        'stdlib': '{installed_base}/Lib',
93        'platstdlib': '{base}/Lib',
94        'purelib': '{base}/Lib/site-packages',
95        'platlib': '{base}/Lib/site-packages',
96        'include': '{installed_base}/Include',
97        'platinclude': '{installed_base}/Include',
98        'scripts': '{base}/Scripts',
99        'data': '{base}',
100        },
101    }
102
103# For the OS-native venv scheme, we essentially provide an alias:
104if os.name == 'nt':
105    _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['nt_venv']
106else:
107    _INSTALL_SCHEMES['venv'] = _INSTALL_SCHEMES['posix_venv']
108
109def _get_implementation():
110    return 'Python'
111
112# NOTE: site.py has copy of this function.
113# Sync it when modify this function.
114def _getuserbase():
115    env_base = os.environ.get("PYTHONUSERBASE", None)
116    if env_base:
117        return env_base
118
119    # Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories
120    if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
121        return None
122
123    def joinuser(*args):
124        return os.path.expanduser(os.path.join(*args))
125
126    if os.name == "nt":
127        base = os.environ.get("APPDATA") or "~"
128        return joinuser(base,  _get_implementation())
129
130    if sys.platform == "darwin" and sys._framework:
131        return joinuser("~", "Library", sys._framework,
132                        f"{sys.version_info[0]}.{sys.version_info[1]}")
133
134    return joinuser("~", ".local")
135
136_HAS_USER_BASE = (_getuserbase() is not None)
137
138if _HAS_USER_BASE:
139    _INSTALL_SCHEMES |= {
140        # NOTE: When modifying "purelib" scheme, update site._get_path() too.
141        'nt_user': {
142            'stdlib': '{userbase}/{implementation}{py_version_nodot_plat}',
143            'platstdlib': '{userbase}/{implementation}{py_version_nodot_plat}',
144            'purelib': '{userbase}/{implementation}{py_version_nodot_plat}/site-packages',
145            'platlib': '{userbase}/{implementation}{py_version_nodot_plat}/site-packages',
146            'include': '{userbase}/{implementation}{py_version_nodot_plat}/Include',
147            'scripts': '{userbase}/{implementation}{py_version_nodot_plat}/Scripts',
148            'data': '{userbase}',
149            },
150        'posix_user': {
151            'stdlib': '{userbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}',
152            'platstdlib': '{userbase}/{platlibdir}/{implementation_lower}{py_version_short}{abi_thread}',
153            'purelib': '{userbase}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages',
154            'platlib': '{userbase}/lib/{implementation_lower}{py_version_short}{abi_thread}/site-packages',
155            'include': '{userbase}/include/{implementation_lower}{py_version_short}{abi_thread}',
156            'scripts': '{userbase}/bin',
157            'data': '{userbase}',
158            },
159        'osx_framework_user': {
160            'stdlib': '{userbase}/lib/{implementation_lower}',
161            'platstdlib': '{userbase}/lib/{implementation_lower}',
162            'purelib': '{userbase}/lib/{implementation_lower}/site-packages',
163            'platlib': '{userbase}/lib/{implementation_lower}/site-packages',
164            'include': '{userbase}/include/{implementation_lower}{py_version_short}',
165            'scripts': '{userbase}/bin',
166            'data': '{userbase}',
167            },
168    }
169
170_SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include',
171                'scripts', 'data')
172
173_PY_VERSION = sys.version.split()[0]
174_PY_VERSION_SHORT = f'{sys.version_info[0]}.{sys.version_info[1]}'
175_PY_VERSION_SHORT_NO_DOT = f'{sys.version_info[0]}{sys.version_info[1]}'
176_BASE_PREFIX = os.path.normpath(sys.base_prefix)
177_BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix)
178# Mutex guarding initialization of _CONFIG_VARS.
179_CONFIG_VARS_LOCK = threading.RLock()
180_CONFIG_VARS = None
181# True iff _CONFIG_VARS has been fully initialized.
182_CONFIG_VARS_INITIALIZED = False
183_USER_BASE = None
184
185
186def _safe_realpath(path):
187    try:
188        return realpath(path)
189    except OSError:
190        return path
191
192if sys.executable:
193    _PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
194else:
195    # sys.executable can be empty if argv[0] has been changed and Python is
196    # unable to retrieve the real program name
197    _PROJECT_BASE = _safe_realpath(os.getcwd())
198
199# In a virtual environment, `sys._home` gives us the target directory
200# `_PROJECT_BASE` for the executable that created it when the virtual
201# python is an actual executable ('venv --copies' or Windows).
202_sys_home = getattr(sys, '_home', None)
203if _sys_home:
204    _PROJECT_BASE = _sys_home
205
206if os.name == 'nt':
207    # In a source build, the executable is in a subdirectory of the root
208    # that we want (<root>\PCbuild\<platname>).
209    # `_BASE_PREFIX` is used as the base installation is where the source
210    # will be.  The realpath is needed to prevent mount point confusion
211    # that can occur with just string comparisons.
212    if _safe_realpath(_PROJECT_BASE).startswith(
213            _safe_realpath(f'{_BASE_PREFIX}\\PCbuild')):
214        _PROJECT_BASE = _BASE_PREFIX
215
216# set for cross builds
217if "_PYTHON_PROJECT_BASE" in os.environ:
218    _PROJECT_BASE = _safe_realpath(os.environ["_PYTHON_PROJECT_BASE"])
219
220def is_python_build(check_home=None):
221    if check_home is not None:
222        import warnings
223        warnings.warn("check_home argument is deprecated and ignored.",
224                      DeprecationWarning, stacklevel=2)
225    for fn in ("Setup", "Setup.local"):
226        if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
227            return True
228    return False
229
230_PYTHON_BUILD = is_python_build()
231
232if _PYTHON_BUILD:
233    for scheme in ('posix_prefix', 'posix_home'):
234        # On POSIX-y platforms, Python will:
235        # - Build from .h files in 'headers' (which is only added to the
236        #   scheme when building CPython)
237        # - Install .h files to 'include'
238        scheme = _INSTALL_SCHEMES[scheme]
239        scheme['headers'] = scheme['include']
240        scheme['include'] = '{srcdir}/Include'
241        scheme['platinclude'] = '{projectbase}/.'
242    del scheme
243
244
245def _subst_vars(s, local_vars):
246    try:
247        return s.format(**local_vars)
248    except KeyError as var:
249        try:
250            return s.format(**os.environ)
251        except KeyError:
252            raise AttributeError(f'{var}') from None
253
254def _extend_dict(target_dict, other_dict):
255    target_keys = target_dict.keys()
256    for key, value in other_dict.items():
257        if key in target_keys:
258            continue
259        target_dict[key] = value
260
261
262def _expand_vars(scheme, vars):
263    res = {}
264    if vars is None:
265        vars = {}
266    _extend_dict(vars, get_config_vars())
267    if os.name == 'nt':
268        # On Windows we want to substitute 'lib' for schemes rather
269        # than the native value (without modifying vars, in case it
270        # was passed in)
271        vars = vars | {'platlibdir': 'lib'}
272
273    for key, value in _INSTALL_SCHEMES[scheme].items():
274        if os.name in ('posix', 'nt'):
275            value = os.path.expanduser(value)
276        res[key] = os.path.normpath(_subst_vars(value, vars))
277    return res
278
279
280def _get_preferred_schemes():
281    if os.name == 'nt':
282        return {
283            'prefix': 'nt',
284            'home': 'posix_home',
285            'user': 'nt_user',
286        }
287    if sys.platform == 'darwin' and sys._framework:
288        return {
289            'prefix': 'posix_prefix',
290            'home': 'posix_home',
291            'user': 'osx_framework_user',
292        }
293
294    return {
295        'prefix': 'posix_prefix',
296        'home': 'posix_home',
297        'user': 'posix_user',
298    }
299
300
301def get_preferred_scheme(key):
302    if key == 'prefix' and sys.prefix != sys.base_prefix:
303        return 'venv'
304    scheme = _get_preferred_schemes()[key]
305    if scheme not in _INSTALL_SCHEMES:
306        raise ValueError(
307            f"{key!r} returned {scheme!r}, which is not a valid scheme "
308            f"on this platform"
309        )
310    return scheme
311
312
313def get_default_scheme():
314    return get_preferred_scheme('prefix')
315
316
317def get_makefile_filename():
318    """Return the path of the Makefile."""
319    if _PYTHON_BUILD:
320        return os.path.join(_PROJECT_BASE, "Makefile")
321    if hasattr(sys, 'abiflags'):
322        config_dir_name = f'config-{_PY_VERSION_SHORT}{sys.abiflags}'
323    else:
324        config_dir_name = 'config'
325    if hasattr(sys.implementation, '_multiarch'):
326        config_dir_name += f'-{sys.implementation._multiarch}'
327    return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
328
329
330def _get_sysconfigdata_name():
331    multiarch = getattr(sys.implementation, '_multiarch', '')
332    return os.environ.get(
333        '_PYTHON_SYSCONFIGDATA_NAME',
334        f'_sysconfigdata_{sys.abiflags}_{sys.platform}_{multiarch}',
335    )
336
337def _init_posix(vars):
338    """Initialize the module as appropriate for POSIX systems."""
339    # _sysconfigdata is generated at build time, see _generate_posix_vars()
340    name = _get_sysconfigdata_name()
341
342    # For cross builds, the path to the target's sysconfigdata must be specified
343    # so it can be imported. It cannot be in PYTHONPATH, as foreign modules in
344    # sys.path can cause crashes when loaded by the host interpreter.
345    # Rely on truthiness as a valueless env variable is still an empty string.
346    # See OS X note in _generate_posix_vars re _sysconfigdata.
347    if (path := os.environ.get('_PYTHON_SYSCONFIGDATA_PATH')):
348        from importlib.machinery import FileFinder, SourceFileLoader, SOURCE_SUFFIXES
349        from importlib.util import module_from_spec
350        spec = FileFinder(path, (SourceFileLoader, SOURCE_SUFFIXES)).find_spec(name)
351        _temp = module_from_spec(spec)
352        spec.loader.exec_module(_temp)
353    else:
354        _temp = __import__(name, globals(), locals(), ['build_time_vars'], 0)
355    build_time_vars = _temp.build_time_vars
356    vars.update(build_time_vars)
357
358def _init_non_posix(vars):
359    """Initialize the module as appropriate for NT"""
360    # set basic install directories
361    import _winapi
362    import _sysconfig
363    vars['LIBDEST'] = get_path('stdlib')
364    vars['BINLIBDEST'] = get_path('platstdlib')
365    vars['INCLUDEPY'] = get_path('include')
366
367    # Add EXT_SUFFIX, SOABI, and Py_GIL_DISABLED
368    vars.update(_sysconfig.config_vars())
369
370    vars['LIBDIR'] = _safe_realpath(os.path.join(get_config_var('installed_base'), 'libs'))
371    if hasattr(sys, 'dllhandle'):
372        dllhandle = _winapi.GetModuleFileName(sys.dllhandle)
373        vars['LIBRARY'] = os.path.basename(_safe_realpath(dllhandle))
374        vars['LDLIBRARY'] = vars['LIBRARY']
375    vars['EXE'] = '.exe'
376    vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
377    vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
378    vars['TZPATH'] = ''
379
380#
381# public APIs
382#
383
384
385def parse_config_h(fp, vars=None):
386    """Parse a config.h-style file.
387
388    A dictionary containing name/value pairs is returned.  If an
389    optional dictionary is passed in as the second argument, it is
390    used instead of a new dictionary.
391    """
392    if vars is None:
393        vars = {}
394    import re
395    define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
396    undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
397
398    while True:
399        line = fp.readline()
400        if not line:
401            break
402        m = define_rx.match(line)
403        if m:
404            n, v = m.group(1, 2)
405            try:
406                if n in _ALWAYS_STR:
407                    raise ValueError
408                v = int(v)
409            except ValueError:
410                pass
411            vars[n] = v
412        else:
413            m = undef_rx.match(line)
414            if m:
415                vars[m.group(1)] = 0
416    return vars
417
418
419def get_config_h_filename():
420    """Return the path of pyconfig.h."""
421    if _PYTHON_BUILD:
422        if os.name == "nt":
423            inc_dir = os.path.dirname(sys._base_executable)
424        else:
425            inc_dir = _PROJECT_BASE
426    else:
427        inc_dir = get_path('platinclude')
428    return os.path.join(inc_dir, 'pyconfig.h')
429
430
431def get_scheme_names():
432    """Return a tuple containing the schemes names."""
433    return tuple(sorted(_INSTALL_SCHEMES))
434
435
436def get_path_names():
437    """Return a tuple containing the paths names."""
438    return _SCHEME_KEYS
439
440
441def get_paths(scheme=get_default_scheme(), vars=None, expand=True):
442    """Return a mapping containing an install scheme.
443
444    ``scheme`` is the install scheme name. If not provided, it will
445    return the default scheme for the current platform.
446    """
447    if expand:
448        return _expand_vars(scheme, vars)
449    else:
450        return _INSTALL_SCHEMES[scheme]
451
452
453def get_path(name, scheme=get_default_scheme(), vars=None, expand=True):
454    """Return a path corresponding to the scheme.
455
456    ``scheme`` is the install scheme name.
457    """
458    return get_paths(scheme, vars, expand)[name]
459
460
461def _init_config_vars():
462    global _CONFIG_VARS
463    _CONFIG_VARS = {}
464    # Normalized versions of prefix and exec_prefix are handy to have;
465    # in fact, these are the standard versions used most places in the
466    # Distutils.
467    _PREFIX = os.path.normpath(sys.prefix)
468    _EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
469    _CONFIG_VARS['prefix'] = _PREFIX  # FIXME: This gets overwriten by _init_posix.
470    _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX  # FIXME: This gets overwriten by _init_posix.
471    _CONFIG_VARS['py_version'] = _PY_VERSION
472    _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
473    _CONFIG_VARS['py_version_nodot'] = _PY_VERSION_SHORT_NO_DOT
474    _CONFIG_VARS['installed_base'] = _BASE_PREFIX
475    _CONFIG_VARS['base'] = _PREFIX
476    _CONFIG_VARS['installed_platbase'] = _BASE_EXEC_PREFIX
477    _CONFIG_VARS['platbase'] = _EXEC_PREFIX
478    _CONFIG_VARS['projectbase'] = _PROJECT_BASE
479    _CONFIG_VARS['platlibdir'] = sys.platlibdir
480    _CONFIG_VARS['implementation'] = _get_implementation()
481    _CONFIG_VARS['implementation_lower'] = _get_implementation().lower()
482    try:
483        _CONFIG_VARS['abiflags'] = sys.abiflags
484    except AttributeError:
485        # sys.abiflags may not be defined on all platforms.
486        _CONFIG_VARS['abiflags'] = ''
487    try:
488        _CONFIG_VARS['py_version_nodot_plat'] = sys.winver.replace('.', '')
489    except AttributeError:
490        _CONFIG_VARS['py_version_nodot_plat'] = ''
491
492    if os.name == 'nt':
493        _init_non_posix(_CONFIG_VARS)
494        _CONFIG_VARS['VPATH'] = sys._vpath
495    if os.name == 'posix':
496        _init_posix(_CONFIG_VARS)
497    if _HAS_USER_BASE:
498        # Setting 'userbase' is done below the call to the
499        # init function to enable using 'get_config_var' in
500        # the init-function.
501        _CONFIG_VARS['userbase'] = _getuserbase()
502
503    # e.g., 't' for free-threaded or '' for default build
504    _CONFIG_VARS['abi_thread'] = 't' if _CONFIG_VARS.get('Py_GIL_DISABLED') else ''
505
506    # Always convert srcdir to an absolute path
507    srcdir = _CONFIG_VARS.get('srcdir', _PROJECT_BASE)
508    if os.name == 'posix':
509        if _PYTHON_BUILD:
510            # If srcdir is a relative path (typically '.' or '..')
511            # then it should be interpreted relative to the directory
512            # containing Makefile.
513            base = os.path.dirname(get_makefile_filename())
514            srcdir = os.path.join(base, srcdir)
515        else:
516            # srcdir is not meaningful since the installation is
517            # spread about the filesystem.  We choose the
518            # directory containing the Makefile since we know it
519            # exists.
520            srcdir = os.path.dirname(get_makefile_filename())
521    _CONFIG_VARS['srcdir'] = _safe_realpath(srcdir)
522
523    # OS X platforms require special customization to handle
524    # multi-architecture, multi-os-version installers
525    if sys.platform == 'darwin':
526        import _osx_support
527        _osx_support.customize_config_vars(_CONFIG_VARS)
528
529    global _CONFIG_VARS_INITIALIZED
530    _CONFIG_VARS_INITIALIZED = True
531
532
533def get_config_vars(*args):
534    """With no arguments, return a dictionary of all configuration
535    variables relevant for the current platform.
536
537    On Unix, this means every variable defined in Python's installed Makefile;
538    On Windows it's a much smaller set.
539
540    With arguments, return a list of values that result from looking up
541    each argument in the configuration variable dictionary.
542    """
543    global _CONFIG_VARS_INITIALIZED
544
545    # Avoid claiming the lock once initialization is complete.
546    if not _CONFIG_VARS_INITIALIZED:
547        with _CONFIG_VARS_LOCK:
548            # Test again with the lock held to avoid races. Note that
549            # we test _CONFIG_VARS here, not _CONFIG_VARS_INITIALIZED,
550            # to ensure that recursive calls to get_config_vars()
551            # don't re-enter init_config_vars().
552            if _CONFIG_VARS is None:
553                _init_config_vars()
554    else:
555        # If the site module initialization happened after _CONFIG_VARS was
556        # initialized, a virtual environment might have been activated, resulting in
557        # variables like sys.prefix changing their value, so we need to re-init the
558        # config vars (see GH-126789).
559        if _CONFIG_VARS['base'] != os.path.normpath(sys.prefix):
560            with _CONFIG_VARS_LOCK:
561                _CONFIG_VARS_INITIALIZED = False
562                _init_config_vars()
563
564    if args:
565        vals = []
566        for name in args:
567            vals.append(_CONFIG_VARS.get(name))
568        return vals
569    else:
570        return _CONFIG_VARS
571
572
573def get_config_var(name):
574    """Return the value of a single variable using the dictionary returned by
575    'get_config_vars()'.
576
577    Equivalent to get_config_vars().get(name)
578    """
579    return get_config_vars().get(name)
580
581
582def get_platform():
583    """Return a string that identifies the current platform.
584
585    This is used mainly to distinguish platform-specific build directories and
586    platform-specific built distributions.  Typically includes the OS name and
587    version and the architecture (as supplied by 'os.uname()'), although the
588    exact information included depends on the OS; on Linux, the kernel version
589    isn't particularly important.
590
591    Examples of returned values:
592       linux-i586
593       linux-alpha (?)
594       solaris-2.6-sun4u
595
596    Windows will return one of:
597       win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
598       win32 (all others - specifically, sys.platform is returned)
599
600    For other non-POSIX platforms, currently just returns 'sys.platform'.
601
602    """
603    if os.name == 'nt':
604        if 'amd64' in sys.version.lower():
605            return 'win-amd64'
606        if '(arm)' in sys.version.lower():
607            return 'win-arm32'
608        if '(arm64)' in sys.version.lower():
609            return 'win-arm64'
610        return sys.platform
611
612    if os.name != "posix" or not hasattr(os, 'uname'):
613        # XXX what about the architecture? NT is Intel or Alpha
614        return sys.platform
615
616    # Set for cross builds explicitly
617    if "_PYTHON_HOST_PLATFORM" in os.environ:
618        return os.environ["_PYTHON_HOST_PLATFORM"]
619
620    # Try to distinguish various flavours of Unix
621    osname, host, release, version, machine = os.uname()
622
623    # Convert the OS name to lowercase, remove '/' characters, and translate
624    # spaces (for "Power Macintosh")
625    osname = osname.lower().replace('/', '')
626    machine = machine.replace(' ', '_')
627    machine = machine.replace('/', '-')
628
629    if osname[:5] == "linux":
630        if sys.platform == "android":
631            osname = "android"
632            release = get_config_var("ANDROID_API_LEVEL")
633
634            # Wheel tags use the ABI names from Android's own tools.
635            machine = {
636                "x86_64": "x86_64",
637                "i686": "x86",
638                "aarch64": "arm64_v8a",
639                "armv7l": "armeabi_v7a",
640            }[machine]
641        else:
642            # At least on Linux/Intel, 'machine' is the processor --
643            # i386, etc.
644            # XXX what about Alpha, SPARC, etc?
645            return  f"{osname}-{machine}"
646    elif osname[:5] == "sunos":
647        if release[0] >= "5":           # SunOS 5 == Solaris 2
648            osname = "solaris"
649            release = f"{int(release[0]) - 3}.{release[2:]}"
650            # We can't use "platform.architecture()[0]" because a
651            # bootstrap problem. We use a dict to get an error
652            # if some suspicious happens.
653            bitness = {2147483647:"32bit", 9223372036854775807:"64bit"}
654            machine += f".{bitness[sys.maxsize]}"
655        # fall through to standard osname-release-machine representation
656    elif osname[:3] == "aix":
657        from _aix_support import aix_platform
658        return aix_platform()
659    elif osname[:6] == "cygwin":
660        osname = "cygwin"
661        import re
662        rel_re = re.compile(r'[\d.]+')
663        m = rel_re.match(release)
664        if m:
665            release = m.group()
666    elif osname[:6] == "darwin":
667        if sys.platform == "ios":
668            release = get_config_vars().get("IPHONEOS_DEPLOYMENT_TARGET", "13.0")
669            osname = sys.platform
670            machine = sys.implementation._multiarch
671        else:
672            import _osx_support
673            osname, release, machine = _osx_support.get_platform_osx(
674                                                get_config_vars(),
675                                                osname, release, machine)
676
677    return f"{osname}-{release}-{machine}"
678
679
680def get_python_version():
681    return _PY_VERSION_SHORT
682
683
684def _get_python_version_abi():
685    return _PY_VERSION_SHORT + get_config_var("abi_thread")
686
687
688def expand_makefile_vars(s, vars):
689    """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in
690    'string' according to 'vars' (a dictionary mapping variable names to
691    values).  Variables not present in 'vars' are silently expanded to the
692    empty string.  The variable values in 'vars' should not contain further
693    variable expansions; if 'vars' is the output of 'parse_makefile()',
694    you're fine.  Returns a variable-expanded version of 's'.
695    """
696    import re
697
698    # This algorithm does multiple expansion, so if vars['foo'] contains
699    # "${bar}", it will expand ${foo} to ${bar}, and then expand
700    # ${bar}... and so forth.  This is fine as long as 'vars' comes from
701    # 'parse_makefile()', which takes care of such expansions eagerly,
702    # according to make's variable expansion semantics.
703
704    while True:
705        m = re.search(_findvar1_rx, s) or re.search(_findvar2_rx, s)
706        if m:
707            (beg, end) = m.span()
708            s = s[0:beg] + vars.get(m.group(1)) + s[end:]
709        else:
710            break
711    return s
712