Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
install.py806 linesDownload Raw Back to command
1"""distutils.command.install2 3Implements the Distutils 'install' command."""4 5from __future__ import annotations6 7import collections8import contextlib9import itertools10import os11import sys12import sysconfig13from distutils._log import log14from site import USER_BASE, USER_SITE15from typing import ClassVar16 17from ..core import Command18from ..debug import DEBUG19from ..errors import DistutilsOptionError, DistutilsPlatformError20from ..file_util import write_file21from ..sysconfig import get_config_vars22from ..util import change_root, convert_path, get_platform, subst_vars23from . import _framework_compat as fw24 25HAS_USER_SITE = True26 27WINDOWS_SCHEME = {28    'purelib': '{base}/Lib/site-packages',29    'platlib': '{base}/Lib/site-packages',30    'headers': '{base}/Include/{dist_name}',31    'scripts': '{base}/Scripts',32    'data': '{base}',33}34 35INSTALL_SCHEMES = {36    'posix_prefix': {37        'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages',38        'platlib': '{platbase}/{platlibdir}/{implementation_lower}'39        '{py_version_short}/site-packages',40        'headers': '{base}/include/{implementation_lower}'41        '{py_version_short}{abiflags}/{dist_name}',42        'scripts': '{base}/bin',43        'data': '{base}',44    },45    'posix_home': {46        'purelib': '{base}/lib/{implementation_lower}',47        'platlib': '{base}/{platlibdir}/{implementation_lower}',48        'headers': '{base}/include/{implementation_lower}/{dist_name}',49        'scripts': '{base}/bin',50        'data': '{base}',51    },52    'nt': WINDOWS_SCHEME,53    'pypy': {54        'purelib': '{base}/site-packages',55        'platlib': '{base}/site-packages',56        'headers': '{base}/include/{dist_name}',57        'scripts': '{base}/bin',58        'data': '{base}',59    },60    'pypy_nt': {61        'purelib': '{base}/site-packages',62        'platlib': '{base}/site-packages',63        'headers': '{base}/include/{dist_name}',64        'scripts': '{base}/Scripts',65        'data': '{base}',66    },67}68 69# user site schemes70if HAS_USER_SITE:71    INSTALL_SCHEMES['nt_user'] = {72        'purelib': '{usersite}',73        'platlib': '{usersite}',74        'headers': '{userbase}/{implementation}{py_version_nodot_plat}'75        '/Include/{dist_name}',76        'scripts': '{userbase}/{implementation}{py_version_nodot_plat}/Scripts',77        'data': '{userbase}',78    }79 80    INSTALL_SCHEMES['posix_user'] = {81        'purelib': '{usersite}',82        'platlib': '{usersite}',83        'headers': '{userbase}/include/{implementation_lower}'84        '{py_version_short}{abiflags}/{dist_name}',85        'scripts': '{userbase}/bin',86        'data': '{userbase}',87    }88 89 90INSTALL_SCHEMES.update(fw.schemes)91 92 93# The keys to an installation scheme; if any new types of files are to be94# installed, be sure to add an entry to every installation scheme above,95# and to SCHEME_KEYS here.96SCHEME_KEYS = ('purelib', 'platlib', 'headers', 'scripts', 'data')97 98 99def _load_sysconfig_schemes():100    with contextlib.suppress(AttributeError):101        return {102            scheme: sysconfig.get_paths(scheme, expand=False)103            for scheme in sysconfig.get_scheme_names()104        }105 106 107def _load_schemes():108    """109    Extend default schemes with schemes from sysconfig.110    """111 112    sysconfig_schemes = _load_sysconfig_schemes() or {}113 114    return {115        scheme: {116            **INSTALL_SCHEMES.get(scheme, {}),117            **sysconfig_schemes.get(scheme, {}),118        }119        for scheme in set(itertools.chain(INSTALL_SCHEMES, sysconfig_schemes))120    }121 122 123def _get_implementation():124    if hasattr(sys, 'pypy_version_info'):125        return 'PyPy'126    else:127        return 'Python'128 129 130def _select_scheme(ob, name):131    scheme = _inject_headers(name, _load_scheme(_resolve_scheme(name)))132    vars(ob).update(_remove_set(ob, _scheme_attrs(scheme)))133 134 135def _remove_set(ob, attrs):136    """137    Include only attrs that are None in ob.138    """139    return {key: value for key, value in attrs.items() if getattr(ob, key) is None}140 141 142def _resolve_scheme(name):143    os_name, sep, key = name.partition('_')144    try:145        resolved = sysconfig.get_preferred_scheme(key)146    except Exception:147        resolved = fw.scheme(name)148    return resolved149 150 151def _load_scheme(name):152    return _load_schemes()[name]153 154 155def _inject_headers(name, scheme):156    """157    Given a scheme name and the resolved scheme,158    if the scheme does not include headers, resolve159    the fallback scheme for the name and use headers160    from it. pypa/distutils#88161    """162    # Bypass the preferred scheme, which may not163    # have defined headers.164    fallback = _load_scheme(name)165    scheme.setdefault('headers', fallback['headers'])166    return scheme167 168 169def _scheme_attrs(scheme):170    """Resolve install directories by applying the install schemes."""171    return {f'install_{key}': scheme[key] for key in SCHEME_KEYS}172 173 174class install(Command):175    description = "install everything from build directory"176 177    user_options = [178        # Select installation scheme and set base director(y|ies)179        ('prefix=', None, "installation prefix"),180        ('exec-prefix=', None, "(Unix only) prefix for platform-specific files"),181        ('home=', None, "(Unix only) home directory to install under"),182        # Or, just set the base director(y|ies)183        (184            'install-base=',185            None,186            "base installation directory (instead of --prefix or --home)",187        ),188        (189            'install-platbase=',190            None,191            "base installation directory for platform-specific files (instead of --exec-prefix or --home)",192        ),193        ('root=', None, "install everything relative to this alternate root directory"),194        # Or, explicitly set the installation scheme195        (196            'install-purelib=',197            None,198            "installation directory for pure Python module distributions",199        ),200        (201            'install-platlib=',202            None,203            "installation directory for non-pure module distributions",204        ),205        (206            'install-lib=',207            None,208            "installation directory for all module distributions (overrides --install-purelib and --install-platlib)",209        ),210        ('install-headers=', None, "installation directory for C/C++ headers"),211        ('install-scripts=', None, "installation directory for Python scripts"),212        ('install-data=', None, "installation directory for data files"),213        # Byte-compilation options -- see install_lib.py for details, as214        # these are duplicated from there (but only install_lib does215        # anything with them).216        ('compile', 'c', "compile .py to .pyc [default]"),217        ('no-compile', None, "don't compile .py files"),218        (219            'optimize=',220            'O',221            "also compile with optimization: -O1 for \"python -O\", "222            "-O2 for \"python -OO\", and -O0 to disable [default: -O0]",223        ),224        # Miscellaneous control options225        ('force', 'f', "force installation (overwrite any existing files)"),226        ('skip-build', None, "skip rebuilding everything (for testing/debugging)"),227        # Where to install documentation (eventually!)228        # ('doc-format=', None, "format of documentation to generate"),229        # ('install-man=', None, "directory for Unix man pages"),230        # ('install-html=', None, "directory for HTML documentation"),231        # ('install-info=', None, "directory for GNU info files"),232        ('record=', None, "filename in which to record list of installed files"),233    ]234 235    boolean_options: ClassVar[list[str]] = ['compile', 'force', 'skip-build']236 237    if HAS_USER_SITE:238        user_options.append((239            'user',240            None,241            f"install in user site-package '{USER_SITE}'",242        ))243        boolean_options.append('user')244 245    negative_opt: ClassVar[dict[str, str]] = {'no-compile': 'compile'}246 247    def initialize_options(self) -> None:248        """Initializes options."""249        # High-level options: these select both an installation base250        # and scheme.251        self.prefix: str | None = None252        self.exec_prefix: str | None = None253        self.home: str | None = None254        self.user = False255 256        # These select only the installation base; it's up to the user to257        # specify the installation scheme (currently, that means supplying258        # the --install-{platlib,purelib,scripts,data} options).259        self.install_base = None260        self.install_platbase = None261        self.root: str | None = None262 263        # These options are the actual installation directories; if not264        # supplied by the user, they are filled in using the installation265        # scheme implied by prefix/exec-prefix/home and the contents of266        # that installation scheme.267        self.install_purelib = None  # for pure module distributions268        self.install_platlib = None  # non-pure (dists w/ extensions)269        self.install_headers = None  # for C/C++ headers270        self.install_lib: str | None = None  # set to either purelib or platlib271        self.install_scripts = None272        self.install_data = None273        self.install_userbase = USER_BASE274        self.install_usersite = USER_SITE275 276        self.compile = None277        self.optimize = None278 279        # Deprecated280        # These two are for putting non-packagized distributions into their281        # own directory and creating a .pth file if it makes sense.282        # 'extra_path' comes from the setup file; 'install_path_file' can283        # be turned off if it makes no sense to install a .pth file.  (But284        # better to install it uselessly than to guess wrong and not285        # install it when it's necessary and would be used!)  Currently,286        # 'install_path_file' is always true unless some outsider meddles287        # with it.288        self.extra_path = None289        self.install_path_file = True290 291        # 'force' forces installation, even if target files are not292        # out-of-date.  'skip_build' skips running the "build" command,293        # handy if you know it's not necessary.  'warn_dir' (which is *not*294        # a user option, it's just there so the bdist_* commands can turn295        # it off) determines whether we warn about installing to a296        # directory not in sys.path.297        self.force = False298        self.skip_build = False299        self.warn_dir = True300 301        # These are only here as a conduit from the 'build' command to the302        # 'install_*' commands that do the real work.  ('build_base' isn't303        # actually used anywhere, but it might be useful in future.)  They304        # are not user options, because if the user told the install305        # command where the build directory is, that wouldn't affect the306        # build command.307        self.build_base = None308        self.build_lib = None309 310        # Not defined yet because we don't know anything about311        # documentation yet.312        # self.install_man = None313        # self.install_html = None314        # self.install_info = None315 316        self.record = None317 318    # -- Option finalizing methods -------------------------------------319    # (This is rather more involved than for most commands,320    # because this is where the policy for installing third-321    # party Python modules on various platforms given a wide322    # array of user input is decided.  Yes, it's quite complex!)323 324    def finalize_options(self) -> None:  # noqa: C901325        """Finalizes options."""326        # This method (and its helpers, like 'finalize_unix()',327        # 'finalize_other()', and 'select_scheme()') is where the default328        # installation directories for modules, extension modules, and329        # anything else we care to install from a Python module330        # distribution.  Thus, this code makes a pretty important policy331        # statement about how third-party stuff is added to a Python332        # installation!  Note that the actual work of installation is done333        # by the relatively simple 'install_*' commands; they just take334        # their orders from the installation directory options determined335        # here.336 337        # Check for errors/inconsistencies in the options; first, stuff338        # that's wrong on any platform.339 340        if (self.prefix or self.exec_prefix or self.home) and (341            self.install_base or self.install_platbase342        ):343            raise DistutilsOptionError(344                "must supply either prefix/exec-prefix/home or install-base/install-platbase -- not both"345            )346 347        if self.home and (self.prefix or self.exec_prefix):348            raise DistutilsOptionError(349                "must supply either home or prefix/exec-prefix -- not both"350            )351 352        if self.user and (353            self.prefix354            or self.exec_prefix355            or self.home356            or self.install_base357            or self.install_platbase358        ):359            raise DistutilsOptionError(360                "can't combine user with prefix, "361                "exec_prefix/home, or install_(plat)base"362            )363 364        # Next, stuff that's wrong (or dubious) only on certain platforms.365        if os.name != "posix":366            if self.exec_prefix:367                self.warn("exec-prefix option ignored on this platform")368                self.exec_prefix = None369 370        # Now the interesting logic -- so interesting that we farm it out371        # to other methods.  The goal of these methods is to set the final372        # values for the install_{lib,scripts,data,...}  options, using as373        # input a heady brew of prefix, exec_prefix, home, install_base,374        # install_platbase, user-supplied versions of375        # install_{purelib,platlib,lib,scripts,data,...}, and the376        # install schemes.  Phew!377 378        self.dump_dirs("pre-finalize_{unix,other}")379 380        if os.name == 'posix':381            self.finalize_unix()382        else:383            self.finalize_other()384 385        self.dump_dirs("post-finalize_{unix,other}()")386 387        # Expand configuration variables, tilde, etc. in self.install_base388        # and self.install_platbase -- that way, we can use $base or389        # $platbase in the other installation directories and not worry390        # about needing recursive variable expansion (shudder).391 392        py_version = sys.version.split()[0]393        (prefix, exec_prefix) = get_config_vars('prefix', 'exec_prefix')394        try:395            abiflags = sys.abiflags396        except AttributeError:397            # sys.abiflags may not be defined on all platforms.398            abiflags = ''399        local_vars = {400            'dist_name': self.distribution.get_name(),401            'dist_version': self.distribution.get_version(),402            'dist_fullname': self.distribution.get_fullname(),403            'py_version': py_version,404            'py_version_short': f'{sys.version_info.major}.{sys.version_info.minor}',405            'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}',406            'sys_prefix': prefix,407            'prefix': prefix,408            'sys_exec_prefix': exec_prefix,409            'exec_prefix': exec_prefix,410            'abiflags': abiflags,411            'platlibdir': getattr(sys, 'platlibdir', 'lib'),412            'implementation_lower': _get_implementation().lower(),413            'implementation': _get_implementation(),414        }415 416        # vars for compatibility on older Pythons417        compat_vars = dict(418            # Python 3.9 and earlier419            py_version_nodot_plat=getattr(sys, 'winver', '').replace('.', ''),420        )421 422        if HAS_USER_SITE:423            local_vars['userbase'] = self.install_userbase424            local_vars['usersite'] = self.install_usersite425 426        self.config_vars = collections.ChainMap(427            local_vars,428            sysconfig.get_config_vars(),429            compat_vars,430            fw.vars(),431        )432 433        self.expand_basedirs()434 435        self.dump_dirs("post-expand_basedirs()")436 437        # Now define config vars for the base directories so we can expand438        # everything else.439        local_vars['base'] = self.install_base440        local_vars['platbase'] = self.install_platbase441 442        if DEBUG:443            from pprint import pprint444 445            print("config vars:")446            pprint(dict(self.config_vars))447 448        # Expand "~" and configuration variables in the installation449        # directories.450        self.expand_dirs()451 452        self.dump_dirs("post-expand_dirs()")453 454        # Create directories in the home dir:455        if self.user:456            self.create_home_path()457 458        # Pick the actual directory to install all modules to: either459        # install_purelib or install_platlib, depending on whether this460        # module distribution is pure or not.  Of course, if the user461        # already specified install_lib, use their selection.462        if self.install_lib is None:463            if self.distribution.has_ext_modules():  # has extensions: non-pure464                self.install_lib = self.install_platlib465            else:466                self.install_lib = self.install_purelib467 468        # Convert directories from Unix /-separated syntax to the local469        # convention.470        self.convert_paths(471            'lib',472            'purelib',473            'platlib',474            'scripts',475            'data',476            'headers',477            'userbase',478            'usersite',479        )480 481        # Deprecated482        # Well, we're not actually fully completely finalized yet: we still483        # have to deal with 'extra_path', which is the hack for allowing484        # non-packagized module distributions (hello, Numerical Python!) to485        # get their own directories.486        self.handle_extra_path()487        self.install_libbase = self.install_lib  # needed for .pth file488        self.install_lib = os.path.join(self.install_lib, self.extra_dirs)489 490        # If a new root directory was supplied, make all the installation491        # dirs relative to it.492        if self.root is not None:493            self.change_roots(494                'libbase', 'lib', 'purelib', 'platlib', 'scripts', 'data', 'headers'495            )496 497        self.dump_dirs("after prepending root")498 499        # Find out the build directories, ie. where to install from.500        self.set_undefined_options(501            'build', ('build_base', 'build_base'), ('build_lib', 'build_lib')502        )503 504        # Punt on doc directories for now -- after all, we're punting on505        # documentation completely!506 507    def dump_dirs(self, msg) -> None:508        """Dumps the list of user options."""509        if not DEBUG:510            return511        from ..fancy_getopt import longopt_xlate512 513        log.debug(msg + ":")514        for opt in self.user_options:515            opt_name = opt[0]516            if opt_name[-1] == "=":517                opt_name = opt_name[0:-1]518            if opt_name in self.negative_opt:519                opt_name = self.negative_opt[opt_name]520                opt_name = opt_name.translate(longopt_xlate)521                val = not getattr(self, opt_name)522            else:523                opt_name = opt_name.translate(longopt_xlate)524                val = getattr(self, opt_name)525            log.debug("  %s: %s", opt_name, val)526 527    def finalize_unix(self) -> None:528        """Finalizes options for posix platforms."""529        if self.install_base is not None or self.install_platbase is not None:530            incomplete_scheme = (531                (532                    self.install_lib is None533                    and self.install_purelib is None534                    and self.install_platlib is None535                )536                or self.install_headers is None537                or self.install_scripts is None538                or self.install_data is None539            )540            if incomplete_scheme:541                raise DistutilsOptionError(542                    "install-base or install-platbase supplied, but "543                    "installation scheme is incomplete"544                )545            return546 547        if self.user:548            if self.install_userbase is None:549                raise DistutilsPlatformError("User base directory is not specified")550            self.install_base = self.install_platbase = self.install_userbase551            self.select_scheme("posix_user")552        elif self.home is not None:553            self.install_base = self.install_platbase = self.home554            self.select_scheme("posix_home")555        else:556            if self.prefix is None:557                if self.exec_prefix is not None:558                    raise DistutilsOptionError(559                        "must not supply exec-prefix without prefix"560                    )561 562                # Allow Fedora to add components to the prefix563                _prefix_addition = getattr(sysconfig, '_prefix_addition', "")564 565                self.prefix = os.path.normpath(sys.prefix) + _prefix_addition566                self.exec_prefix = os.path.normpath(sys.exec_prefix) + _prefix_addition567 568            else:569                if self.exec_prefix is None:570                    self.exec_prefix = self.prefix571 572            self.install_base = self.prefix573            self.install_platbase = self.exec_prefix574            self.select_scheme("posix_prefix")575 576    def finalize_other(self) -> None:577        """Finalizes options for non-posix platforms"""578        if self.user:579            if self.install_userbase is None:580                raise DistutilsPlatformError("User base directory is not specified")581            self.install_base = self.install_platbase = self.install_userbase582            self.select_scheme(os.name + "_user")583        elif self.home is not None:584            self.install_base = self.install_platbase = self.home585            self.select_scheme("posix_home")586        else:587            if self.prefix is None:588                self.prefix = os.path.normpath(sys.prefix)589 590            self.install_base = self.install_platbase = self.prefix591            try:592                self.select_scheme(os.name)593            except KeyError:594                raise DistutilsPlatformError(595                    f"I don't know how to install stuff on '{os.name}'"596                )597 598    def select_scheme(self, name) -> None:599        _select_scheme(self, name)600 601    def _expand_attrs(self, attrs):602        for attr in attrs:603            val = getattr(self, attr)604            if val is not None:605                if os.name in ('posix', 'nt'):606                    val = os.path.expanduser(val)607                val = subst_vars(val, self.config_vars)608                setattr(self, attr, val)609 610    def expand_basedirs(self) -> None:611        """Calls `os.path.expanduser` on install_base, install_platbase and612        root."""613        self._expand_attrs(['install_base', 'install_platbase', 'root'])614 615    def expand_dirs(self) -> None:616        """Calls `os.path.expanduser` on install dirs."""617        self._expand_attrs([618            'install_purelib',619            'install_platlib',620            'install_lib',621            'install_headers',622            'install_scripts',623            'install_data',624        ])625 626    def convert_paths(self, *names) -> None:627        """Call `convert_path` over `names`."""628        for name in names:629            attr = "install_" + name630            setattr(self, attr, convert_path(getattr(self, attr)))631 632    def handle_extra_path(self) -> None:633        """Set `path_file` and `extra_dirs` using `extra_path`."""634        if self.extra_path is None:635            self.extra_path = self.distribution.extra_path636 637        if self.extra_path is not None:638            log.warning(639                "Distribution option extra_path is deprecated. "640                "See issue27919 for details."641            )642            if isinstance(self.extra_path, str):643                self.extra_path = self.extra_path.split(',')644 645            if len(self.extra_path) == 1:646                path_file = extra_dirs = self.extra_path[0]647            elif len(self.extra_path) == 2:648                path_file, extra_dirs = self.extra_path649            else:650                raise DistutilsOptionError(651                    "'extra_path' option must be a list, tuple, or "652                    "comma-separated string with 1 or 2 elements"653                )654 655            # convert to local form in case Unix notation used (as it656            # should be in setup scripts)657            extra_dirs = convert_path(extra_dirs)658        else:659            path_file = None660            extra_dirs = ''661 662        # XXX should we warn if path_file and not extra_dirs? (in which663        # case the path file would be harmless but pointless)664        self.path_file = path_file665        self.extra_dirs = extra_dirs666 667    def change_roots(self, *names) -> None:668        """Change the install directories pointed by name using root."""669        for name in names:670            attr = "install_" + name671            setattr(self, attr, change_root(self.root, getattr(self, attr)))672 673    def create_home_path(self) -> None:674        """Create directories under ~."""675        if not self.user:676            return677        home = convert_path(os.path.expanduser("~"))678        for path in self.config_vars.values():679            if str(path).startswith(home) and not os.path.isdir(path):680                self.debug_print(f"os.makedirs('{path}', 0o700)")681                os.makedirs(path, 0o700)682 683    # -- Command execution methods -------------------------------------684 685    def run(self):686        """Runs the command."""687        # Obviously have to build before we can install688        if not self.skip_build:689            self.run_command('build')690            # If we built for any other platform, we can't install.691            build_plat = self.distribution.get_command_obj('build').plat_name692            # check warn_dir - it is a clue that the 'install' is happening693            # internally, and not to sys.path, so we don't check the platform694            # matches what we are running.695            if self.warn_dir and build_plat != get_platform():696                raise DistutilsPlatformError("Can't install when cross-compiling")697 698        # Run all sub-commands (at least those that need to be run)699        for cmd_name in self.get_sub_commands():700            self.run_command(cmd_name)701 702        if self.path_file:703            self.create_path_file()704 705        # write list of installed files, if requested.706        if self.record:707            outputs = self.get_outputs()708            if self.root:  # strip any package prefix709                root_len = len(self.root)710                for counter in range(len(outputs)):711                    outputs[counter] = outputs[counter][root_len:]712            self.execute(713                write_file,714                (self.record, outputs),715                f"writing list of installed files to '{self.record}'",716            )717 718        sys_path = map(os.path.normpath, sys.path)719        sys_path = map(os.path.normcase, sys_path)720        install_lib = os.path.normcase(os.path.normpath(self.install_lib))721        if (722            self.warn_dir723            and not (self.path_file and self.install_path_file)724            and install_lib not in sys_path725        ):726            log.debug(727                (728                    "modules installed to '%s', which is not in "729                    "Python's module search path (sys.path) -- "730                    "you'll have to change the search path yourself"731                ),732                self.install_lib,733            )734 735    def create_path_file(self):736        """Creates the .pth file"""737        filename = os.path.join(self.install_libbase, self.path_file + ".pth")738        if self.install_path_file:739            self.execute(740                write_file, (filename, [self.extra_dirs]), f"creating {filename}"741            )742        else:743            self.warn(f"path file '{filename}' not created")744 745    # -- Reporting methods ---------------------------------------------746 747    def get_outputs(self):748        """Assembles the outputs of all the sub-commands."""749        outputs = []750        for cmd_name in self.get_sub_commands():751            cmd = self.get_finalized_command(cmd_name)752            # Add the contents of cmd.get_outputs(), ensuring753            # that outputs doesn't contain duplicate entries754            for filename in cmd.get_outputs():755                if filename not in outputs:756                    outputs.append(filename)757 758        if self.path_file and self.install_path_file:759            outputs.append(os.path.join(self.install_libbase, self.path_file + ".pth"))760 761        return outputs762 763    def get_inputs(self):764        """Returns the inputs of all the sub-commands"""765        # XXX gee, this looks familiar ;-(766        inputs = []767        for cmd_name in self.get_sub_commands():768            cmd = self.get_finalized_command(cmd_name)769            inputs.extend(cmd.get_inputs())770 771        return inputs772 773    # -- Predicates for sub-command list -------------------------------774 775    def has_lib(self):776        """Returns true if the current distribution has any Python777        modules to install."""778        return (779            self.distribution.has_pure_modules() or self.distribution.has_ext_modules()780        )781 782    def has_headers(self):783        """Returns true if the current distribution has any headers to784        install."""785        return self.distribution.has_headers()786 787    def has_scripts(self):788        """Returns true if the current distribution has any scripts to.789        install."""790        return self.distribution.has_scripts()791 792    def has_data(self):793        """Returns true if the current distribution has any data to.794        install."""795        return self.distribution.has_data_files()796 797    # 'sub_commands': a list of commands this command might have to run to798    # get its work done.  See cmd.py for more info.799    sub_commands = [800        ('install_lib', has_lib),801        ('install_headers', has_headers),802        ('install_scripts', has_scripts),803        ('install_data', has_data),804        ('install_egg_info', lambda self: True),805    ]806 
codekingpro/portable-devtools · Team Ai