1"""Provide access to Python's configuration information.  The specific 
    2configuration variables available depend heavily on the platform and 
    3configuration.  The values may be retrieved using 
    4get_config_var(name), and the list of variables is available via 
    5get_config_vars().keys().  Additional convenience functions are also 
    6available. 
    7 
    8Written by:   Fred L. Drake, Jr. 
    9Email:        <fdrake@acm.org> 
    10""" 
    11 
    12import functools 
    13import os 
    14import pathlib 
    15import re 
    16import sys 
    17import sysconfig 
    18 
    19from ._functools import pass_none 
    20from .compat import py39 
    21from .errors import DistutilsPlatformError 
    22from .util import is_mingw 
    23 
    24IS_PYPY = '__pypy__' in sys.builtin_module_names 
    25 
    26# These are needed in a couple of spots, so just compute them once. 
    27PREFIX = os.path.normpath(sys.prefix) 
    28EXEC_PREFIX = os.path.normpath(sys.exec_prefix) 
    29BASE_PREFIX = os.path.normpath(sys.base_prefix) 
    30BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix) 
    31 
    32# Path to the base directory of the project. On Windows the binary may 
    33# live in project/PCbuild/win32 or project/PCbuild/amd64. 
    34# set for cross builds 
    35if "_PYTHON_PROJECT_BASE" in os.environ: 
    36    project_base = os.path.abspath(os.environ["_PYTHON_PROJECT_BASE"]) 
    37else: 
    38    if sys.executable: 
    39        project_base = os.path.dirname(os.path.abspath(sys.executable)) 
    40    else: 
    41        # sys.executable can be empty if argv[0] has been changed and Python is 
    42        # unable to retrieve the real program name 
    43        project_base = os.getcwd() 
    44 
    45 
    46def _is_python_source_dir(d): 
    47    """ 
    48    Return True if the target directory appears to point to an 
    49    un-installed Python. 
    50    """ 
    51    modules = pathlib.Path(d).joinpath('Modules') 
    52    return any(modules.joinpath(fn).is_file() for fn in ('Setup', 'Setup.local')) 
    53 
    54 
    55_sys_home = getattr(sys, '_home', None) 
    56 
    57 
    58def _is_parent(dir_a, dir_b): 
    59    """ 
    60    Return True if a is a parent of b. 
    61    """ 
    62    return os.path.normcase(dir_a).startswith(os.path.normcase(dir_b)) 
    63 
    64 
    65if os.name == 'nt': 
    66 
    67    @pass_none 
    68    def _fix_pcbuild(d): 
    69        # In a venv, sys._home will be inside BASE_PREFIX rather than PREFIX. 
    70        prefixes = PREFIX, BASE_PREFIX 
    71        matched = ( 
    72            prefix 
    73            for prefix in prefixes 
    74            if _is_parent(d, os.path.join(prefix, "PCbuild")) 
    75        ) 
    76        return next(matched, d) 
    77 
    78    project_base = _fix_pcbuild(project_base) 
    79    _sys_home = _fix_pcbuild(_sys_home) 
    80 
    81 
    82def _python_build(): 
    83    if _sys_home: 
    84        return _is_python_source_dir(_sys_home) 
    85    return _is_python_source_dir(project_base) 
    86 
    87 
    88python_build = _python_build() 
    89 
    90 
    91# Calculate the build qualifier flags if they are defined.  Adding the flags 
    92# to the include and lib directories only makes sense for an installation, not 
    93# an in-source build. 
    94build_flags = '' 
    95try: 
    96    if not python_build: 
    97        build_flags = sys.abiflags 
    98except AttributeError: 
    99    # It's not a configure-based build, so the sys module doesn't have 
    100    # this attribute, which is fine. 
    101    pass 
    102 
    103 
    104def get_python_version(): 
    105    """Return a string containing the major and minor Python version, 
    106    leaving off the patchlevel.  Sample return values could be '1.5' 
    107    or '2.2'. 
    108    """ 
    109    return '%d.%d' % sys.version_info[:2] 
    110 
    111 
    112def get_python_inc(plat_specific=False, prefix=None): 
    113    """Return the directory containing installed Python header files. 
    114 
    115    If 'plat_specific' is false (the default), this is the path to the 
    116    non-platform-specific header files, i.e. Python.h and so on; 
    117    otherwise, this is the path to platform-specific header files 
    118    (namely pyconfig.h). 
    119 
    120    If 'prefix' is supplied, use it instead of sys.base_prefix or 
    121    sys.base_exec_prefix -- i.e., ignore 'plat_specific'. 
    122    """ 
    123    default_prefix = BASE_EXEC_PREFIX if plat_specific else BASE_PREFIX 
    124    resolved_prefix = prefix if prefix is not None else default_prefix 
    125    # MinGW imitates posix like layout, but os.name != posix 
    126    os_name = "posix" if is_mingw() else os.name 
    127    try: 
    128        getter = globals()[f'_get_python_inc_{os_name}'] 
    129    except KeyError: 
    130        raise DistutilsPlatformError( 
    131            "I don't know where Python installs its C header files " 
    132            f"on platform '{os.name}'" 
    133        ) 
    134    return getter(resolved_prefix, prefix, plat_specific) 
    135 
    136 
    137@pass_none 
    138def _extant(path): 
    139    """ 
    140    Replace path with None if it doesn't exist. 
    141    """ 
    142    return path if os.path.exists(path) else None 
    143 
    144 
    145def _get_python_inc_posix(prefix, spec_prefix, plat_specific): 
    146    if IS_PYPY and sys.version_info < (3, 8): 
    147        return os.path.join(prefix, 'include') 
    148    return ( 
    149        _get_python_inc_posix_python(plat_specific) 
    150        or _extant(_get_python_inc_from_config(plat_specific, spec_prefix)) 
    151        or _get_python_inc_posix_prefix(prefix) 
    152    ) 
    153 
    154 
    155def _get_python_inc_posix_python(plat_specific): 
    156    """ 
    157    Assume the executable is in the build directory. The 
    158    pyconfig.h file should be in the same directory. Since 
    159    the build directory may not be the source directory, 
    160    use "srcdir" from the makefile to find the "Include" 
    161    directory. 
    162    """ 
    163    if not python_build: 
    164        return 
    165    if plat_specific: 
    166        return _sys_home or project_base 
    167    incdir = os.path.join(get_config_var('srcdir'), 'Include') 
    168    return os.path.normpath(incdir) 
    169 
    170 
    171def _get_python_inc_from_config(plat_specific, spec_prefix): 
    172    """ 
    173    If no prefix was explicitly specified, provide the include 
    174    directory from the config vars. Useful when 
    175    cross-compiling, since the config vars may come from 
    176    the host 
    177    platform Python installation, while the current Python 
    178    executable is from the build platform installation. 
    179 
    180    >>> monkeypatch = getfixture('monkeypatch') 
    181    >>> gpifc = _get_python_inc_from_config 
    182    >>> monkeypatch.setitem(gpifc.__globals__, 'get_config_var', str.lower) 
    183    >>> gpifc(False, '/usr/bin/') 
    184    >>> gpifc(False, '') 
    185    >>> gpifc(False, None) 
    186    'includepy' 
    187    >>> gpifc(True, None) 
    188    'confincludepy' 
    189    """ 
    190    if spec_prefix is None: 
    191        return get_config_var('CONF' * plat_specific + 'INCLUDEPY') 
    192 
    193 
    194def _get_python_inc_posix_prefix(prefix): 
    195    implementation = 'pypy' if IS_PYPY else 'python' 
    196    python_dir = implementation + get_python_version() + build_flags 
    197    return os.path.join(prefix, "include", python_dir) 
    198 
    199 
    200def _get_python_inc_nt(prefix, spec_prefix, plat_specific): 
    201    if python_build: 
    202        # Include both include dirs to ensure we can find pyconfig.h 
    203        return ( 
    204            os.path.join(prefix, "include") 
    205            + os.path.pathsep 
    206            + os.path.dirname(sysconfig.get_config_h_filename()) 
    207        ) 
    208    return os.path.join(prefix, "include") 
    209 
    210 
    211# allow this behavior to be monkey-patched. Ref pypa/distutils#2. 
    212def _posix_lib(standard_lib, libpython, early_prefix, prefix): 
    213    if standard_lib: 
    214        return libpython 
    215    else: 
    216        return os.path.join(libpython, "site-packages") 
    217 
    218 
    219def get_python_lib(plat_specific=False, standard_lib=False, prefix=None): 
    220    """Return the directory containing the Python library (standard or 
    221    site additions). 
    222 
    223    If 'plat_specific' is true, return the directory containing 
    224    platform-specific modules, i.e. any module from a non-pure-Python 
    225    module distribution; otherwise, return the platform-shared library 
    226    directory.  If 'standard_lib' is true, return the directory 
    227    containing standard Python library modules; otherwise, return the 
    228    directory for site-specific modules. 
    229 
    230    If 'prefix' is supplied, use it instead of sys.base_prefix or 
    231    sys.base_exec_prefix -- i.e., ignore 'plat_specific'. 
    232    """ 
    233 
    234    if IS_PYPY and sys.version_info < (3, 8): 
    235        # PyPy-specific schema 
    236        if prefix is None: 
    237            prefix = PREFIX 
    238        if standard_lib: 
    239            return os.path.join(prefix, "lib-python", sys.version[0]) 
    240        return os.path.join(prefix, 'site-packages') 
    241 
    242    early_prefix = prefix 
    243 
    244    if prefix is None: 
    245        if standard_lib: 
    246            prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX 
    247        else: 
    248            prefix = plat_specific and EXEC_PREFIX or PREFIX 
    249 
    250    if os.name == "posix" or is_mingw(): 
    251        if plat_specific or standard_lib: 
    252            # Platform-specific modules (any module from a non-pure-Python 
    253            # module distribution) or standard Python library modules. 
    254            libdir = getattr(sys, "platlibdir", "lib") 
    255        else: 
    256            # Pure Python 
    257            libdir = "lib" 
    258        implementation = 'pypy' if IS_PYPY else 'python' 
    259        libpython = os.path.join(prefix, libdir, implementation + get_python_version()) 
    260        return _posix_lib(standard_lib, libpython, early_prefix, prefix) 
    261    elif os.name == "nt": 
    262        if standard_lib: 
    263            return os.path.join(prefix, "Lib") 
    264        else: 
    265            return os.path.join(prefix, "Lib", "site-packages") 
    266    else: 
    267        raise DistutilsPlatformError( 
    268            f"I don't know where Python installs its library on platform '{os.name}'" 
    269        ) 
    270 
    271 
    272@functools.lru_cache 
    273def _customize_macos(): 
    274    """ 
    275    Perform first-time customization of compiler-related 
    276    config vars on macOS. Use after a compiler is known 
    277    to be needed. This customization exists primarily to support Pythons 
    278    from binary installers. The kind and paths to build tools on 
    279    the user system may vary significantly from the system 
    280    that Python itself was built on.  Also the user OS 
    281    version and build tools may not support the same set 
    282    of CPU architectures for universal builds. 
    283    """ 
    284 
    285    sys.platform == "darwin" and __import__('_osx_support').customize_compiler( 
    286        get_config_vars() 
    287    ) 
    288 
    289 
    290def customize_compiler(compiler):  # noqa: C901 
    291    """Do any platform-specific customization of a CCompiler instance. 
    292 
    293    Mainly needed on Unix, so we can plug in the information that 
    294    varies across Unices and is stored in Python's Makefile. 
    295    """ 
    296    if compiler.compiler_type in ["unix", "cygwin"] or ( 
    297        compiler.compiler_type == "mingw32" and is_mingw() 
    298    ): 
    299        _customize_macos() 
    300 
    301        ( 
    302            cc, 
    303            cxx, 
    304            cflags, 
    305            ccshared, 
    306            ldshared, 
    307            shlib_suffix, 
    308            ar, 
    309            ar_flags, 
    310        ) = get_config_vars( 
    311            'CC', 
    312            'CXX', 
    313            'CFLAGS', 
    314            'CCSHARED', 
    315            'LDSHARED', 
    316            'SHLIB_SUFFIX', 
    317            'AR', 
    318            'ARFLAGS', 
    319        ) 
    320 
    321        if 'CC' in os.environ: 
    322            newcc = os.environ['CC'] 
    323            if 'LDSHARED' not in os.environ and ldshared.startswith(cc): 
    324                # If CC is overridden, use that as the default 
    325                #       command for LDSHARED as well 
    326                ldshared = newcc + ldshared[len(cc) :] 
    327            cc = newcc 
    328        if 'CXX' in os.environ: 
    329            cxx = os.environ['CXX'] 
    330        if 'LDSHARED' in os.environ: 
    331            ldshared = os.environ['LDSHARED'] 
    332        if 'CPP' in os.environ: 
    333            cpp = os.environ['CPP'] 
    334        else: 
    335            cpp = cc + " -E"  # not always 
    336        if 'LDFLAGS' in os.environ: 
    337            ldshared = ldshared + ' ' + os.environ['LDFLAGS'] 
    338        if 'CFLAGS' in os.environ: 
    339            cflags = cflags + ' ' + os.environ['CFLAGS'] 
    340            ldshared = ldshared + ' ' + os.environ['CFLAGS'] 
    341        if 'CPPFLAGS' in os.environ: 
    342            cpp = cpp + ' ' + os.environ['CPPFLAGS'] 
    343            cflags = cflags + ' ' + os.environ['CPPFLAGS'] 
    344            ldshared = ldshared + ' ' + os.environ['CPPFLAGS'] 
    345        if 'AR' in os.environ: 
    346            ar = os.environ['AR'] 
    347        if 'ARFLAGS' in os.environ: 
    348            archiver = ar + ' ' + os.environ['ARFLAGS'] 
    349        else: 
    350            archiver = ar + ' ' + ar_flags 
    351 
    352        cc_cmd = cc + ' ' + cflags 
    353        compiler.set_executables( 
    354            preprocessor=cpp, 
    355            compiler=cc_cmd, 
    356            compiler_so=cc_cmd + ' ' + ccshared, 
    357            compiler_cxx=cxx, 
    358            linker_so=ldshared, 
    359            linker_exe=cc, 
    360            archiver=archiver, 
    361        ) 
    362 
    363        if 'RANLIB' in os.environ and compiler.executables.get('ranlib', None): 
    364            compiler.set_executables(ranlib=os.environ['RANLIB']) 
    365 
    366        compiler.shared_lib_extension = shlib_suffix 
    367 
    368 
    369def get_config_h_filename(): 
    370    """Return full pathname of installed pyconfig.h file.""" 
    371    return sysconfig.get_config_h_filename() 
    372 
    373 
    374def get_makefile_filename(): 
    375    """Return full pathname of installed Makefile from the Python build.""" 
    376    return sysconfig.get_makefile_filename() 
    377 
    378 
    379def parse_config_h(fp, g=None): 
    380    """Parse a config.h-style file. 
    381 
    382    A dictionary containing name/value pairs is returned.  If an 
    383    optional dictionary is passed in as the second argument, it is 
    384    used instead of a new dictionary. 
    385    """ 
    386    return sysconfig.parse_config_h(fp, vars=g) 
    387 
    388 
    389# Regexes needed for parsing Makefile (and similar syntaxes, 
    390# like old-style Setup files). 
    391_variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)") 
    392_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)") 
    393_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}") 
    394 
    395 
    396def parse_makefile(fn, g=None):  # noqa: C901 
    397    """Parse a Makefile-style file. 
    398 
    399    A dictionary containing name/value pairs is returned.  If an 
    400    optional dictionary is passed in as the second argument, it is 
    401    used instead of a new dictionary. 
    402    """ 
    403    from distutils.text_file import TextFile 
    404 
    405    fp = TextFile( 
    406        fn, 
    407        strip_comments=True, 
    408        skip_blanks=True, 
    409        join_lines=True, 
    410        errors="surrogateescape", 
    411    ) 
    412 
    413    if g is None: 
    414        g = {} 
    415    done = {} 
    416    notdone = {} 
    417 
    418    while True: 
    419        line = fp.readline() 
    420        if line is None:  # eof 
    421            break 
    422        m = _variable_rx.match(line) 
    423        if m: 
    424            n, v = m.group(1, 2) 
    425            v = v.strip() 
    426            # `$$' is a literal `$' in make 
    427            tmpv = v.replace('$$', '') 
    428 
    429            if "$" in tmpv: 
    430                notdone[n] = v 
    431            else: 
    432                try: 
    433                    v = int(v) 
    434                except ValueError: 
    435                    # insert literal `$' 
    436                    done[n] = v.replace('$$', '$') 
    437                else: 
    438                    done[n] = v 
    439 
    440    # Variables with a 'PY_' prefix in the makefile. These need to 
    441    # be made available without that prefix through sysconfig. 
    442    # Special care is needed to ensure that variable expansion works, even 
    443    # if the expansion uses the name without a prefix. 
    444    renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS') 
    445 
    446    # do variable interpolation here 
    447    while notdone: 
    448        for name in list(notdone): 
    449            value = notdone[name] 
    450            m = _findvar1_rx.search(value) or _findvar2_rx.search(value) 
    451            if m: 
    452                n = m.group(1) 
    453                found = True 
    454                if n in done: 
    455                    item = str(done[n]) 
    456                elif n in notdone: 
    457                    # get it on a subsequent round 
    458                    found = False 
    459                elif n in os.environ: 
    460                    # do it like make: fall back to environment 
    461                    item = os.environ[n] 
    462 
    463                elif n in renamed_variables: 
    464                    if name.startswith('PY_') and name[3:] in renamed_variables: 
    465                        item = "" 
    466 
    467                    elif 'PY_' + n in notdone: 
    468                        found = False 
    469 
    470                    else: 
    471                        item = str(done['PY_' + n]) 
    472                else: 
    473                    done[n] = item = "" 
    474                if found: 
    475                    after = value[m.end() :] 
    476                    value = value[: m.start()] + item + after 
    477                    if "$" in after: 
    478                        notdone[name] = value 
    479                    else: 
    480                        try: 
    481                            value = int(value) 
    482                        except ValueError: 
    483                            done[name] = value.strip() 
    484                        else: 
    485                            done[name] = value 
    486                        del notdone[name] 
    487 
    488                        if name.startswith('PY_') and name[3:] in renamed_variables: 
    489                            name = name[3:] 
    490                            if name not in done: 
    491                                done[name] = value 
    492            else: 
    493                # bogus variable reference; just drop it since we can't deal 
    494                del notdone[name] 
    495 
    496    fp.close() 
    497 
    498    # strip spurious spaces 
    499    for k, v in done.items(): 
    500        if isinstance(v, str): 
    501            done[k] = v.strip() 
    502 
    503    # save the results in the global dictionary 
    504    g.update(done) 
    505    return g 
    506 
    507 
    508def expand_makefile_vars(s, vars): 
    509    """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in 
    510    'string' according to 'vars' (a dictionary mapping variable names to 
    511    values).  Variables not present in 'vars' are silently expanded to the 
    512    empty string.  The variable values in 'vars' should not contain further 
    513    variable expansions; if 'vars' is the output of 'parse_makefile()', 
    514    you're fine.  Returns a variable-expanded version of 's'. 
    515    """ 
    516 
    517    # This algorithm does multiple expansion, so if vars['foo'] contains 
    518    # "${bar}", it will expand ${foo} to ${bar}, and then expand 
    519    # ${bar}... and so forth.  This is fine as long as 'vars' comes from 
    520    # 'parse_makefile()', which takes care of such expansions eagerly, 
    521    # according to make's variable expansion semantics. 
    522 
    523    while True: 
    524        m = _findvar1_rx.search(s) or _findvar2_rx.search(s) 
    525        if m: 
    526            (beg, end) = m.span() 
    527            s = s[0:beg] + vars.get(m.group(1)) + s[end:] 
    528        else: 
    529            break 
    530    return s 
    531 
    532 
    533_config_vars = None 
    534 
    535 
    536def get_config_vars(*args): 
    537    """With no arguments, return a dictionary of all configuration 
    538    variables relevant for the current platform.  Generally this includes 
    539    everything needed to build extensions and install both pure modules and 
    540    extensions.  On Unix, this means every variable defined in Python's 
    541    installed Makefile; on Windows it's a much smaller set. 
    542 
    543    With arguments, return a list of values that result from looking up 
    544    each argument in the configuration variable dictionary. 
    545    """ 
    546    global _config_vars 
    547    if _config_vars is None: 
    548        _config_vars = sysconfig.get_config_vars().copy() 
    549        py39.add_ext_suffix(_config_vars) 
    550 
    551    return [_config_vars.get(name) for name in args] if args else _config_vars 
    552 
    553 
    554def get_config_var(name): 
    555    """Return the value of a single variable using the dictionary 
    556    returned by 'get_config_vars()'.  Equivalent to 
    557    get_config_vars().get(name) 
    558    """ 
    559    if name == 'SO': 
    560        import warnings 
    561 
    562        warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2) 
    563    return get_config_vars().get(name)