Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
sysconfig.py599 linesDownload Raw Back to _distutils
1"""Provide access to Python's configuration information.  The specific2configuration variables available depend heavily on the platform and3configuration.  The values may be retrieved using4get_config_var(name), and the list of variables is available via5get_config_vars().keys().  Additional convenience functions are also6available.7 8Written by:   Fred L. Drake, Jr.9Email:        <fdrake@acm.org>10"""11 12from __future__ import annotations13 14import functools15import os16import pathlib17import re18import sys19import sysconfig20from typing import TYPE_CHECKING, Literal, overload21 22from jaraco.functools import pass_none23 24from .ccompiler import CCompiler25from .compat import py3926from .errors import DistutilsPlatformError27from .util import is_mingw28 29if TYPE_CHECKING:30    from typing_extensions import deprecated31else:32 33    def deprecated(message):34        return lambda fn: fn35 36 37IS_PYPY = '__pypy__' in sys.builtin_module_names38 39# These are needed in a couple of spots, so just compute them once.40PREFIX = os.path.normpath(sys.prefix)41EXEC_PREFIX = os.path.normpath(sys.exec_prefix)42BASE_PREFIX = os.path.normpath(sys.base_prefix)43BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix)44 45# Path to the base directory of the project. On Windows the binary may46# live in project/PCbuild/win32 or project/PCbuild/amd64.47# set for cross builds48if "_PYTHON_PROJECT_BASE" in os.environ:49    project_base = os.path.abspath(os.environ["_PYTHON_PROJECT_BASE"])50else:51    if sys.executable:52        project_base = os.path.dirname(os.path.abspath(sys.executable))53    else:54        # sys.executable can be empty if argv[0] has been changed and Python is55        # unable to retrieve the real program name56        project_base = os.getcwd()57 58 59def _is_python_source_dir(d):60    """61    Return True if the target directory appears to point to an62    un-installed Python.63    """64    modules = pathlib.Path(d).joinpath('Modules')65    return any(modules.joinpath(fn).is_file() for fn in ('Setup', 'Setup.local'))66 67 68_sys_home = getattr(sys, '_home', None)69 70 71def _is_parent(dir_a, dir_b):72    """73    Return True if a is a parent of b.74    """75    return os.path.normcase(dir_a).startswith(os.path.normcase(dir_b))76 77 78if os.name == 'nt':79 80    @pass_none81    def _fix_pcbuild(d):82        # In a venv, sys._home will be inside BASE_PREFIX rather than PREFIX.83        prefixes = PREFIX, BASE_PREFIX84        matched = (85            prefix86            for prefix in prefixes87            if _is_parent(d, os.path.join(prefix, "PCbuild"))88        )89        return next(matched, d)90 91    project_base = _fix_pcbuild(project_base)92    _sys_home = _fix_pcbuild(_sys_home)93 94 95def _python_build():96    if _sys_home:97        return _is_python_source_dir(_sys_home)98    return _is_python_source_dir(project_base)99 100 101python_build = _python_build()102 103 104# Calculate the build qualifier flags if they are defined.  Adding the flags105# to the include and lib directories only makes sense for an installation, not106# an in-source build.107build_flags = ''108try:109    if not python_build:110        build_flags = sys.abiflags111except AttributeError:112    # It's not a configure-based build, so the sys module doesn't have113    # this attribute, which is fine.114    pass115 116 117def get_python_version():118    """Return a string containing the major and minor Python version,119    leaving off the patchlevel.  Sample return values could be '1.5'120    or '2.2'.121    """122    return f'{sys.version_info.major}.{sys.version_info.minor}'123 124 125def get_python_inc(plat_specific: bool = False, prefix: str | None = None) -> str:126    """Return the directory containing installed Python header files.127 128    If 'plat_specific' is false (the default), this is the path to the129    non-platform-specific header files, i.e. Python.h and so on;130    otherwise, this is the path to platform-specific header files131    (namely pyconfig.h).132 133    If 'prefix' is supplied, use it instead of sys.base_prefix or134    sys.base_exec_prefix -- i.e., ignore 'plat_specific'.135    """136    default_prefix = BASE_EXEC_PREFIX if plat_specific else BASE_PREFIX137    resolved_prefix = prefix if prefix is not None else default_prefix138    # MinGW imitates posix like layout, but os.name != posix139    os_name = "posix" if is_mingw() else os.name140    try:141        getter = globals()[f'_get_python_inc_{os_name}']142    except KeyError:143        raise DistutilsPlatformError(144            "I don't know where Python installs its C header files "145            f"on platform '{os.name}'"146        )147    return getter(resolved_prefix, prefix, plat_specific)148 149 150@pass_none151def _extant(path):152    """153    Replace path with None if it doesn't exist.154    """155    return path if os.path.exists(path) else None156 157 158def _get_python_inc_posix(prefix, spec_prefix, plat_specific):159    return (160        _get_python_inc_posix_python(plat_specific)161        or _extant(_get_python_inc_from_config(plat_specific, spec_prefix))162        or _get_python_inc_posix_prefix(prefix)163    )164 165 166def _get_python_inc_posix_python(plat_specific):167    """168    Assume the executable is in the build directory. The169    pyconfig.h file should be in the same directory. Since170    the build directory may not be the source directory,171    use "srcdir" from the makefile to find the "Include"172    directory.173    """174    if not python_build:175        return176    if plat_specific:177        return _sys_home or project_base178    incdir = os.path.join(get_config_var('srcdir'), 'Include')179    return os.path.normpath(incdir)180 181 182def _get_python_inc_from_config(plat_specific, spec_prefix):183    """184    If no prefix was explicitly specified, provide the include185    directory from the config vars. Useful when186    cross-compiling, since the config vars may come from187    the host188    platform Python installation, while the current Python189    executable is from the build platform installation.190 191    >>> monkeypatch = getfixture('monkeypatch')192    >>> gpifc = _get_python_inc_from_config193    >>> monkeypatch.setitem(gpifc.__globals__, 'get_config_var', str.lower)194    >>> gpifc(False, '/usr/bin/')195    >>> gpifc(False, '')196    >>> gpifc(False, None)197    'includepy'198    >>> gpifc(True, None)199    'confincludepy'200    """201    if spec_prefix is None:202        return get_config_var('CONF' * plat_specific + 'INCLUDEPY')203 204 205def _get_python_inc_posix_prefix(prefix):206    implementation = 'pypy' if IS_PYPY else 'python'207    python_dir = implementation + get_python_version() + build_flags208    return os.path.join(prefix, "include", python_dir)209 210 211def _get_python_inc_nt(prefix, spec_prefix, plat_specific):212    if python_build:213        # Include both include dirs to ensure we can find pyconfig.h214        return (215            os.path.join(prefix, "include")216            + os.path.pathsep217            + os.path.dirname(sysconfig.get_config_h_filename())218        )219    return os.path.join(prefix, "include")220 221 222# allow this behavior to be monkey-patched. Ref pypa/distutils#2.223def _posix_lib(standard_lib, libpython, early_prefix, prefix):224    if standard_lib:225        return libpython226    else:227        return os.path.join(libpython, "site-packages")228 229 230def get_python_lib(231    plat_specific: bool = False, standard_lib: bool = False, prefix: str | None = None232) -> str:233    """Return the directory containing the Python library (standard or234    site additions).235 236    If 'plat_specific' is true, return the directory containing237    platform-specific modules, i.e. any module from a non-pure-Python238    module distribution; otherwise, return the platform-shared library239    directory.  If 'standard_lib' is true, return the directory240    containing standard Python library modules; otherwise, return the241    directory for site-specific modules.242 243    If 'prefix' is supplied, use it instead of sys.base_prefix or244    sys.base_exec_prefix -- i.e., ignore 'plat_specific'.245    """246 247    early_prefix = prefix248 249    if prefix is None:250        if standard_lib:251            prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX252        else:253            prefix = plat_specific and EXEC_PREFIX or PREFIX254 255    if os.name == "posix" or is_mingw():256        if plat_specific or standard_lib:257            # Platform-specific modules (any module from a non-pure-Python258            # module distribution) or standard Python library modules.259            libdir = getattr(sys, "platlibdir", "lib")260        else:261            # Pure Python262            libdir = "lib"263        implementation = 'pypy' if IS_PYPY else 'python'264        libpython = os.path.join(prefix, libdir, implementation + get_python_version())265        return _posix_lib(standard_lib, libpython, early_prefix, prefix)266    elif os.name == "nt":267        if standard_lib:268            return os.path.join(prefix, "Lib")269        else:270            return os.path.join(prefix, "Lib", "site-packages")271    else:272        raise DistutilsPlatformError(273            f"I don't know where Python installs its library on platform '{os.name}'"274        )275 276 277@functools.lru_cache278def _customize_macos():279    """280    Perform first-time customization of compiler-related281    config vars on macOS. Use after a compiler is known282    to be needed. This customization exists primarily to support Pythons283    from binary installers. The kind and paths to build tools on284    the user system may vary significantly from the system285    that Python itself was built on.  Also the user OS286    version and build tools may not support the same set287    of CPU architectures for universal builds.288    """289 290    sys.platform == "darwin" and __import__('_osx_support').customize_compiler(291        get_config_vars()292    )293 294 295def customize_compiler(compiler: CCompiler) -> None:296    """Do any platform-specific customization of a CCompiler instance.297 298    Mainly needed on Unix, so we can plug in the information that299    varies across Unices and is stored in Python's Makefile.300    """301    if compiler.compiler_type in ["unix", "cygwin"] or (302        compiler.compiler_type == "mingw32" and is_mingw()303    ):304        _customize_macos()305 306        (307            cc,308            cxx,309            cflags,310            ccshared,311            ldshared,312            ldcxxshared,313            shlib_suffix,314            ar,315            ar_flags,316        ) = get_config_vars(317            'CC',318            'CXX',319            'CFLAGS',320            'CCSHARED',321            'LDSHARED',322            'LDCXXSHARED',323            'SHLIB_SUFFIX',324            'AR',325            'ARFLAGS',326        )327 328        cxxflags = cflags329 330        if 'CC' in os.environ:331            newcc = os.environ['CC']332            if 'LDSHARED' not in os.environ and ldshared.startswith(cc):333                # If CC is overridden, use that as the default334                #       command for LDSHARED as well335                ldshared = newcc + ldshared[len(cc) :]336            cc = newcc337        cxx = os.environ.get('CXX', cxx)338        ldshared = os.environ.get('LDSHARED', ldshared)339        ldcxxshared = os.environ.get('LDCXXSHARED', ldcxxshared)340        cpp = os.environ.get(341            'CPP',342            cc + " -E",  # not always343        )344 345        ldshared = _add_flags(ldshared, 'LD')346        ldcxxshared = _add_flags(ldcxxshared, 'LD')347        cflags = os.environ.get('CFLAGS', cflags)348        ldshared = _add_flags(ldshared, 'C')349        cxxflags = os.environ.get('CXXFLAGS', cxxflags)350        ldcxxshared = _add_flags(ldcxxshared, 'CXX')351        cpp = _add_flags(cpp, 'CPP')352        cflags = _add_flags(cflags, 'CPP')353        cxxflags = _add_flags(cxxflags, 'CPP')354        ldshared = _add_flags(ldshared, 'CPP')355        ldcxxshared = _add_flags(ldcxxshared, 'CPP')356 357        ar = os.environ.get('AR', ar)358 359        archiver = ar + ' ' + os.environ.get('ARFLAGS', ar_flags)360        cc_cmd = cc + ' ' + cflags361        cxx_cmd = cxx + ' ' + cxxflags362 363        compiler.set_executables(364            preprocessor=cpp,365            compiler=cc_cmd,366            compiler_so=cc_cmd + ' ' + ccshared,367            compiler_cxx=cxx_cmd,368            compiler_so_cxx=cxx_cmd + ' ' + ccshared,369            linker_so=ldshared,370            linker_so_cxx=ldcxxshared,371            linker_exe=cc,372            linker_exe_cxx=cxx,373            archiver=archiver,374        )375 376        if 'RANLIB' in os.environ and compiler.executables.get('ranlib', None):377            compiler.set_executables(ranlib=os.environ['RANLIB'])378 379        compiler.shared_lib_extension = shlib_suffix380 381 382def get_config_h_filename() -> str:383    """Return full pathname of installed pyconfig.h file."""384    return sysconfig.get_config_h_filename()385 386 387def get_makefile_filename() -> str:388    """Return full pathname of installed Makefile from the Python build."""389    return sysconfig.get_makefile_filename()390 391 392def parse_config_h(fp, g=None):393    """Parse a config.h-style file.394 395    A dictionary containing name/value pairs is returned.  If an396    optional dictionary is passed in as the second argument, it is397    used instead of a new dictionary.398    """399    return sysconfig.parse_config_h(fp, vars=g)400 401 402# Regexes needed for parsing Makefile (and similar syntaxes,403# like old-style Setup files).404_variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")405_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")406_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")407 408 409def parse_makefile(fn, g=None):  # noqa: C901410    """Parse a Makefile-style file.411 412    A dictionary containing name/value pairs is returned.  If an413    optional dictionary is passed in as the second argument, it is414    used instead of a new dictionary.415    """416    from distutils.text_file import TextFile417 418    fp = TextFile(419        fn,420        strip_comments=True,421        skip_blanks=True,422        join_lines=True,423        errors="surrogateescape",424    )425 426    if g is None:427        g = {}428    done = {}429    notdone = {}430 431    while True:432        line = fp.readline()433        if line is None:  # eof434            break435        m = _variable_rx.match(line)436        if m:437            n, v = m.group(1, 2)438            v = v.strip()439            # `$$' is a literal `$' in make440            tmpv = v.replace('$$', '')441 442            if "$" in tmpv:443                notdone[n] = v444            else:445                try:446                    v = int(v)447                except ValueError:448                    # insert literal `$'449                    done[n] = v.replace('$$', '$')450                else:451                    done[n] = v452 453    # Variables with a 'PY_' prefix in the makefile. These need to454    # be made available without that prefix through sysconfig.455    # Special care is needed to ensure that variable expansion works, even456    # if the expansion uses the name without a prefix.457    renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')458 459    # do variable interpolation here460    while notdone:461        for name in list(notdone):462            value = notdone[name]463            m = _findvar1_rx.search(value) or _findvar2_rx.search(value)464            if m:465                n = m.group(1)466                found = True467                if n in done:468                    item = str(done[n])469                elif n in notdone:470                    # get it on a subsequent round471                    found = False472                elif n in os.environ:473                    # do it like make: fall back to environment474                    item = os.environ[n]475 476                elif n in renamed_variables:477                    if name.startswith('PY_') and name[3:] in renamed_variables:478                        item = ""479 480                    elif 'PY_' + n in notdone:481                        found = False482 483                    else:484                        item = str(done['PY_' + n])485                else:486                    done[n] = item = ""487                if found:488                    after = value[m.end() :]489                    value = value[: m.start()] + item + after490                    if "$" in after:491                        notdone[name] = value492                    else:493                        try:494                            value = int(value)495                        except ValueError:496                            done[name] = value.strip()497                        else:498                            done[name] = value499                        del notdone[name]500 501                        if name.startswith('PY_') and name[3:] in renamed_variables:502                            name = name[3:]503                            if name not in done:504                                done[name] = value505            else:506                # bogus variable reference; just drop it since we can't deal507                del notdone[name]508 509    fp.close()510 511    # strip spurious spaces512    for k, v in done.items():513        if isinstance(v, str):514            done[k] = v.strip()515 516    # save the results in the global dictionary517    g.update(done)518    return g519 520 521def expand_makefile_vars(s, vars):522    """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in523    'string' according to 'vars' (a dictionary mapping variable names to524    values).  Variables not present in 'vars' are silently expanded to the525    empty string.  The variable values in 'vars' should not contain further526    variable expansions; if 'vars' is the output of 'parse_makefile()',527    you're fine.  Returns a variable-expanded version of 's'.528    """529 530    # This algorithm does multiple expansion, so if vars['foo'] contains531    # "${bar}", it will expand ${foo} to ${bar}, and then expand532    # ${bar}... and so forth.  This is fine as long as 'vars' comes from533    # 'parse_makefile()', which takes care of such expansions eagerly,534    # according to make's variable expansion semantics.535 536    while True:537        m = _findvar1_rx.search(s) or _findvar2_rx.search(s)538        if m:539            (beg, end) = m.span()540            s = s[0:beg] + vars.get(m.group(1)) + s[end:]541        else:542            break543    return s544 545 546_config_vars = None547 548 549@overload550def get_config_vars() -> dict[str, str | int]: ...551@overload552def get_config_vars(arg: str, /, *args: str) -> list[str | int]: ...553def get_config_vars(*args: str) -> list[str | int] | dict[str, str | int]:554    """With no arguments, return a dictionary of all configuration555    variables relevant for the current platform.  Generally this includes556    everything needed to build extensions and install both pure modules and557    extensions.  On Unix, this means every variable defined in Python's558    installed Makefile; on Windows it's a much smaller set.559 560    With arguments, return a list of values that result from looking up561    each argument in the configuration variable dictionary.562    """563    global _config_vars564    if _config_vars is None:565        _config_vars = sysconfig.get_config_vars().copy()566        py39.add_ext_suffix(_config_vars)567 568    return [_config_vars.get(name) for name in args] if args else _config_vars569 570 571@overload572@deprecated(573    "SO is deprecated, use EXT_SUFFIX. Support will be removed when this module is synchronized with stdlib Python 3.11"574)575def get_config_var(name: Literal["SO"]) -> int | str | None: ...576@overload577def get_config_var(name: str) -> int | str | None: ...578def get_config_var(name: str) -> int | str | None:579    """Return the value of a single variable using the dictionary580    returned by 'get_config_vars()'.  Equivalent to581    get_config_vars().get(name)582    """583    if name == 'SO':584        import warnings585 586        warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2)587    return get_config_vars().get(name)588 589 590@pass_none591def _add_flags(value: str, type: str) -> str:592    """593    Add any flags from the environment for the given type.594 595    type is the prefix to FLAGS in the environment key (e.g. "C" for "CFLAGS").596    """597    flags = os.environ.get(f'{type}FLAGS')598    return f'{value} {flags}' if flags else value599 
codekingpro/portable-devtools · Team Ai