Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
cmd.py434 linesDownload Raw Back to _distutils
1"""distutils.cmd2 3Provides the Command class, the base class for the command classes4in the distutils.command package.5"""6 7import logging8import os9import re10import sys11 12from . import _modified, archive_util, dir_util, file_util, util13from ._log import log14from .errors import DistutilsOptionError15 16 17class Command:18    """Abstract base class for defining command classes, the "worker bees"19    of the Distutils.  A useful analogy for command classes is to think of20    them as subroutines with local variables called "options".  The options21    are "declared" in 'initialize_options()' and "defined" (given their22    final values, aka "finalized") in 'finalize_options()', both of which23    must be defined by every command class.  The distinction between the24    two is necessary because option values might come from the outside25    world (command line, config file, ...), and any options dependent on26    other options must be computed *after* these outside influences have27    been processed -- hence 'finalize_options()'.  The "body" of the28    subroutine, where it does all its work based on the values of its29    options, is the 'run()' method, which must also be implemented by every30    command class.31    """32 33    # 'sub_commands' formalizes the notion of a "family" of commands,34    # eg. "install" as the parent with sub-commands "install_lib",35    # "install_headers", etc.  The parent of a family of commands36    # defines 'sub_commands' as a class attribute; it's a list of37    #    (command_name : string, predicate : unbound_method | string | None)38    # tuples, where 'predicate' is a method of the parent command that39    # determines whether the corresponding command is applicable in the40    # current situation.  (Eg. we "install_headers" is only applicable if41    # we have any C header files to install.)  If 'predicate' is None,42    # that command is always applicable.43    #44    # 'sub_commands' is usually defined at the *end* of a class, because45    # predicates can be unbound methods, so they must already have been46    # defined.  The canonical example is the "install" command.47    sub_commands = []48 49    # -- Creation/initialization methods -------------------------------50 51    def __init__(self, dist):52        """Create and initialize a new Command object.  Most importantly,53        invokes the 'initialize_options()' method, which is the real54        initializer and depends on the actual command being55        instantiated.56        """57        # late import because of mutual dependence between these classes58        from distutils.dist import Distribution59 60        if not isinstance(dist, Distribution):61            raise TypeError("dist must be a Distribution instance")62        if self.__class__ is Command:63            raise RuntimeError("Command is an abstract class")64 65        self.distribution = dist66        self.initialize_options()67 68        # Per-command versions of the global flags, so that the user can69        # customize Distutils' behaviour command-by-command and let some70        # commands fall back on the Distribution's behaviour.  None means71        # "not defined, check self.distribution's copy", while 0 or 1 mean72        # false and true (duh).  Note that this means figuring out the real73        # value of each flag is a touch complicated -- hence "self._dry_run"74        # will be handled by __getattr__, below.75        # XXX This needs to be fixed.76        self._dry_run = None77 78        # verbose is largely ignored, but needs to be set for79        # backwards compatibility (I think)?80        self.verbose = dist.verbose81 82        # Some commands define a 'self.force' option to ignore file83        # timestamps, but methods defined *here* assume that84        # 'self.force' exists for all commands.  So define it here85        # just to be safe.86        self.force = None87 88        # The 'help' flag is just used for command-line parsing, so89        # none of that complicated bureaucracy is needed.90        self.help = 091 92        # 'finalized' records whether or not 'finalize_options()' has been93        # called.  'finalize_options()' itself should not pay attention to94        # this flag: it is the business of 'ensure_finalized()', which95        # always calls 'finalize_options()', to respect/update it.96        self.finalized = 097 98    # XXX A more explicit way to customize dry_run would be better.99    def __getattr__(self, attr):100        if attr == 'dry_run':101            myval = getattr(self, "_" + attr)102            if myval is None:103                return getattr(self.distribution, attr)104            else:105                return myval106        else:107            raise AttributeError(attr)108 109    def ensure_finalized(self):110        if not self.finalized:111            self.finalize_options()112        self.finalized = 1113 114    # Subclasses must define:115    #   initialize_options()116    #     provide default values for all options; may be customized by117    #     setup script, by options from config file(s), or by command-line118    #     options119    #   finalize_options()120    #     decide on the final values for all options; this is called121    #     after all possible intervention from the outside world122    #     (command-line, option file, etc.) has been processed123    #   run()124    #     run the command: do whatever it is we're here to do,125    #     controlled by the command's various option values126 127    def initialize_options(self):128        """Set default values for all the options that this command129        supports.  Note that these defaults may be overridden by other130        commands, by the setup script, by config files, or by the131        command-line.  Thus, this is not the place to code dependencies132        between options; generally, 'initialize_options()' implementations133        are just a bunch of "self.foo = None" assignments.134 135        This method must be implemented by all command classes.136        """137        raise RuntimeError(138            "abstract method -- subclass %s must override" % self.__class__139        )140 141    def finalize_options(self):142        """Set final values for all the options that this command supports.143        This is always called as late as possible, ie.  after any option144        assignments from the command-line or from other commands have been145        done.  Thus, this is the place to code option dependencies: if146        'foo' depends on 'bar', then it is safe to set 'foo' from 'bar' as147        long as 'foo' still has the same value it was assigned in148        'initialize_options()'.149 150        This method must be implemented by all command classes.151        """152        raise RuntimeError(153            "abstract method -- subclass %s must override" % self.__class__154        )155 156    def dump_options(self, header=None, indent=""):157        from distutils.fancy_getopt import longopt_xlate158 159        if header is None:160            header = "command options for '%s':" % self.get_command_name()161        self.announce(indent + header, level=logging.INFO)162        indent = indent + "  "163        for option, _, _ in self.user_options:164            option = option.translate(longopt_xlate)165            if option[-1] == "=":166                option = option[:-1]167            value = getattr(self, option)168            self.announce(indent + f"{option} = {value}", level=logging.INFO)169 170    def run(self):171        """A command's raison d'etre: carry out the action it exists to172        perform, controlled by the options initialized in173        'initialize_options()', customized by other commands, the setup174        script, the command-line, and config files, and finalized in175        'finalize_options()'.  All terminal output and filesystem176        interaction should be done by 'run()'.177 178        This method must be implemented by all command classes.179        """180        raise RuntimeError(181            "abstract method -- subclass %s must override" % self.__class__182        )183 184    def announce(self, msg, level=logging.DEBUG):185        log.log(level, msg)186 187    def debug_print(self, msg):188        """Print 'msg' to stdout if the global DEBUG (taken from the189        DISTUTILS_DEBUG environment variable) flag is true.190        """191        from distutils.debug import DEBUG192 193        if DEBUG:194            print(msg)195            sys.stdout.flush()196 197    # -- Option validation methods -------------------------------------198    # (these are very handy in writing the 'finalize_options()' method)199    #200    # NB. the general philosophy here is to ensure that a particular option201    # value meets certain type and value constraints.  If not, we try to202    # force it into conformance (eg. if we expect a list but have a string,203    # split the string on comma and/or whitespace).  If we can't force the204    # option into conformance, raise DistutilsOptionError.  Thus, command205    # classes need do nothing more than (eg.)206    #   self.ensure_string_list('foo')207    # and they can be guaranteed that thereafter, self.foo will be208    # a list of strings.209 210    def _ensure_stringlike(self, option, what, default=None):211        val = getattr(self, option)212        if val is None:213            setattr(self, option, default)214            return default215        elif not isinstance(val, str):216            raise DistutilsOptionError(f"'{option}' must be a {what} (got `{val}`)")217        return val218 219    def ensure_string(self, option, default=None):220        """Ensure that 'option' is a string; if not defined, set it to221        'default'.222        """223        self._ensure_stringlike(option, "string", default)224 225    def ensure_string_list(self, option):226        r"""Ensure that 'option' is a list of strings.  If 'option' is227        currently a string, we split it either on /,\s*/ or /\s+/, so228        "foo bar baz", "foo,bar,baz", and "foo,   bar baz" all become229        ["foo", "bar", "baz"].230        """231        val = getattr(self, option)232        if val is None:233            return234        elif isinstance(val, str):235            setattr(self, option, re.split(r',\s*|\s+', val))236        else:237            if isinstance(val, list):238                ok = all(isinstance(v, str) for v in val)239            else:240                ok = False241            if not ok:242                raise DistutilsOptionError(243                    f"'{option}' must be a list of strings (got {val!r})"244                )245 246    def _ensure_tested_string(self, option, tester, what, error_fmt, default=None):247        val = self._ensure_stringlike(option, what, default)248        if val is not None and not tester(val):249            raise DistutilsOptionError(250                ("error in '%s' option: " + error_fmt) % (option, val)251            )252 253    def ensure_filename(self, option):254        """Ensure that 'option' is the name of an existing file."""255        self._ensure_tested_string(256            option, os.path.isfile, "filename", "'%s' does not exist or is not a file"257        )258 259    def ensure_dirname(self, option):260        self._ensure_tested_string(261            option,262            os.path.isdir,263            "directory name",264            "'%s' does not exist or is not a directory",265        )266 267    # -- Convenience methods for commands ------------------------------268 269    def get_command_name(self):270        if hasattr(self, 'command_name'):271            return self.command_name272        else:273            return self.__class__.__name__274 275    def set_undefined_options(self, src_cmd, *option_pairs):276        """Set the values of any "undefined" options from corresponding277        option values in some other command object.  "Undefined" here means278        "is None", which is the convention used to indicate that an option279        has not been changed between 'initialize_options()' and280        'finalize_options()'.  Usually called from 'finalize_options()' for281        options that depend on some other command rather than another282        option of the same command.  'src_cmd' is the other command from283        which option values will be taken (a command object will be created284        for it if necessary); the remaining arguments are285        '(src_option,dst_option)' tuples which mean "take the value of286        'src_option' in the 'src_cmd' command object, and copy it to287        'dst_option' in the current command object".288        """289        # Option_pairs: list of (src_option, dst_option) tuples290        src_cmd_obj = self.distribution.get_command_obj(src_cmd)291        src_cmd_obj.ensure_finalized()292        for src_option, dst_option in option_pairs:293            if getattr(self, dst_option) is None:294                setattr(self, dst_option, getattr(src_cmd_obj, src_option))295 296    def get_finalized_command(self, command, create=1):297        """Wrapper around Distribution's 'get_command_obj()' method: find298        (create if necessary and 'create' is true) the command object for299        'command', call its 'ensure_finalized()' method, and return the300        finalized command object.301        """302        cmd_obj = self.distribution.get_command_obj(command, create)303        cmd_obj.ensure_finalized()304        return cmd_obj305 306    # XXX rename to 'get_reinitialized_command()'? (should do the307    # same in dist.py, if so)308    def reinitialize_command(self, command, reinit_subcommands=0):309        return self.distribution.reinitialize_command(command, reinit_subcommands)310 311    def run_command(self, command):312        """Run some other command: uses the 'run_command()' method of313        Distribution, which creates and finalizes the command object if314        necessary and then invokes its 'run()' method.315        """316        self.distribution.run_command(command)317 318    def get_sub_commands(self):319        """Determine the sub-commands that are relevant in the current320        distribution (ie., that need to be run).  This is based on the321        'sub_commands' class attribute: each tuple in that list may include322        a method that we call to determine if the subcommand needs to be323        run for the current distribution.  Return a list of command names.324        """325        commands = []326        for cmd_name, method in self.sub_commands:327            if method is None or method(self):328                commands.append(cmd_name)329        return commands330 331    # -- External world manipulation -----------------------------------332 333    def warn(self, msg):334        log.warning("warning: %s: %s\n", self.get_command_name(), msg)335 336    def execute(self, func, args, msg=None, level=1):337        util.execute(func, args, msg, dry_run=self.dry_run)338 339    def mkpath(self, name, mode=0o777):340        dir_util.mkpath(name, mode, dry_run=self.dry_run)341 342    def copy_file(343        self, infile, outfile, preserve_mode=1, preserve_times=1, link=None, level=1344    ):345        """Copy a file respecting verbose, dry-run and force flags.  (The346        former two default to whatever is in the Distribution object, and347        the latter defaults to false for commands that don't define it.)"""348        return file_util.copy_file(349            infile,350            outfile,351            preserve_mode,352            preserve_times,353            not self.force,354            link,355            dry_run=self.dry_run,356        )357 358    def copy_tree(359        self,360        infile,361        outfile,362        preserve_mode=1,363        preserve_times=1,364        preserve_symlinks=0,365        level=1,366    ):367        """Copy an entire directory tree respecting verbose, dry-run,368        and force flags.369        """370        return dir_util.copy_tree(371            infile,372            outfile,373            preserve_mode,374            preserve_times,375            preserve_symlinks,376            not self.force,377            dry_run=self.dry_run,378        )379 380    def move_file(self, src, dst, level=1):381        """Move a file respecting dry-run flag."""382        return file_util.move_file(src, dst, dry_run=self.dry_run)383 384    def spawn(self, cmd, search_path=1, level=1):385        """Spawn an external command respecting dry-run flag."""386        from distutils.spawn import spawn387 388        spawn(cmd, search_path, dry_run=self.dry_run)389 390    def make_archive(391        self, base_name, format, root_dir=None, base_dir=None, owner=None, group=None392    ):393        return archive_util.make_archive(394            base_name,395            format,396            root_dir,397            base_dir,398            dry_run=self.dry_run,399            owner=owner,400            group=group,401        )402 403    def make_file(404        self, infiles, outfile, func, args, exec_msg=None, skip_msg=None, level=1405    ):406        """Special case of 'execute()' for operations that process one or407        more input files and generate one output file.  Works just like408        'execute()', except the operation is skipped and a different409        message printed if 'outfile' already exists and is newer than all410        files listed in 'infiles'.  If the command defined 'self.force',411        and it is true, then the command is unconditionally run -- does no412        timestamp checks.413        """414        if skip_msg is None:415            skip_msg = "skipping %s (inputs unchanged)" % outfile416 417        # Allow 'infiles' to be a single string418        if isinstance(infiles, str):419            infiles = (infiles,)420        elif not isinstance(infiles, (list, tuple)):421            raise TypeError("'infiles' must be a string, or a list or tuple of strings")422 423        if exec_msg is None:424            exec_msg = "generating {} from {}".format(outfile, ', '.join(infiles))425 426        # If 'outfile' must be regenerated (either because it doesn't427        # exist, is out-of-date, or the 'force' flag is true) then428        # perform the action that presumably regenerates it429        if self.force or _modified.newer_group(infiles, outfile):430            self.execute(func, args, exec_msg, level)431        # Otherwise, print the "skip" message432        else:433            log.debug(skip_msg)434 
codekingpro/portable-devtools · Team Ai