diff options
Diffstat (limited to 'Lib/distutils/cmd.py')
-rw-r--r-- | Lib/distutils/cmd.py | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/Lib/distutils/cmd.py b/Lib/distutils/cmd.py new file mode 100644 index 0000000..ad78703 --- /dev/null +++ b/Lib/distutils/cmd.py @@ -0,0 +1,390 @@ +"""distutils.cmd + +Provides the Command class, the base class for the command classes +in the distutils.command package.""" + +# created 2000/04/03, Greg Ward +# (extricated from core.py; actually dates back to the beginning) + +__revision__ = "$Id$" + +import sys, string +from types import * +from distutils.errors import * +from distutils import util + + +class Command: + """Abstract base class for defining command classes, the "worker bees" + of the Distutils. A useful analogy for command classes is to + think of them as subroutines with local variables called + "options". The options are "declared" in 'initialize_options()' + and "defined" (given their final values, aka "finalized") in + 'finalize_options()', both of which must be defined by every + command class. The distinction between the two is necessary + because option values might come from the outside world (command + line, option file, ...), and any options dependent on other + options must be computed *after* these outside influences have + been processed -- hence 'finalize_options()'. The "body" of the + subroutine, where it does all its work based on the values of its + options, is the 'run()' method, which must also be implemented by + every command class.""" + + # -- Creation/initialization methods ------------------------------- + + def __init__ (self, dist): + """Create and initialize a new Command object. Most importantly, + invokes the 'initialize_options()' method, which is the + real initializer and depends on the actual command being + instantiated.""" + + # late import because of mutual dependence between these classes + from distutils.dist import Distribution + + if not isinstance (dist, Distribution): + raise TypeError, "dist must be a Distribution instance" + if self.__class__ is Command: + raise RuntimeError, "Command is an abstract class" + + self.distribution = dist + self.initialize_options () + + # Per-command versions of the global flags, so that the user can + # customize Distutils' behaviour command-by-command and let some + # commands fallback on the Distribution's behaviour. None means + # "not defined, check self.distribution's copy", while 0 or 1 mean + # false and true (duh). Note that this means figuring out the real + # value of each flag is a touch complicatd -- hence "self.verbose" + # (etc.) will be handled by __getattr__, below. + self._verbose = None + self._dry_run = None + self._force = None + + # The 'help' flag is just used for command-line parsing, so + # none of that complicated bureaucracy is needed. + self.help = 0 + + # 'ready' records whether or not 'finalize_options()' has been + # called. 'finalize_options()' itself should not pay attention to + # this flag: it is the business of 'ensure_ready()', which always + # calls 'finalize_options()', to respect/update it. + self.ready = 0 + + # __init__ () + + + def __getattr__ (self, attr): + if attr in ('verbose', 'dry_run', 'force'): + myval = getattr (self, "_" + attr) + if myval is None: + return getattr (self.distribution, attr) + else: + return myval + else: + raise AttributeError, attr + + + def ensure_ready (self): + if not self.ready: + self.finalize_options () + self.ready = 1 + + + # Subclasses must define: + # initialize_options() + # provide default values for all options; may be overridden + # by Distutils client, by command-line options, or by options + # from option file + # finalize_options() + # decide on the final values for all options; this is called + # after all possible intervention from the outside world + # (command-line, option file, etc.) has been processed + # run() + # run the command: do whatever it is we're here to do, + # controlled by the command's various option values + + def initialize_options (self): + """Set default values for all the options that this command + supports. Note that these defaults may be overridden + by the command-line supplied by the user; thus, this is + not the place to code dependencies between options; generally, + 'initialize_options()' implementations are just a bunch + of "self.foo = None" assignments. + + This method must be implemented by all command classes.""" + + raise RuntimeError, \ + "abstract method -- subclass %s must override" % self.__class__ + + def finalize_options (self): + """Set final values for all the options that this command + supports. This is always called as late as possible, ie. + after any option assignments from the command-line or from + other commands have been done. Thus, this is the place to to + code option dependencies: if 'foo' depends on 'bar', then it + is safe to set 'foo' from 'bar' as long as 'foo' still has + the same value it was assigned in 'initialize_options()'. + + This method must be implemented by all command classes.""" + + raise RuntimeError, \ + "abstract method -- subclass %s must override" % self.__class__ + + def run (self): + """A command's raison d'etre: carry out the action it exists + to perform, controlled by the options initialized in + 'initialize_options()', customized by the user and other + commands, and finalized in 'finalize_options()'. All + terminal output and filesystem interaction should be done by + 'run()'. + + This method must be implemented by all command classes.""" + + raise RuntimeError, \ + "abstract method -- subclass %s must override" % self.__class__ + + def announce (self, msg, level=1): + """If the Distribution instance to which this command belongs + has a verbosity level of greater than or equal to 'level' + print 'msg' to stdout.""" + + if self.verbose >= level: + print msg + + + # -- Option query/set methods -------------------------------------- + + def get_option (self, option): + """Return the value of a single option for this command. Raise + DistutilsOptionError if 'option' is not known.""" + try: + return getattr (self, option) + except AttributeError: + raise DistutilsOptionError, \ + "command %s: no such option %s" % \ + (self.get_command_name(), option) + + + def get_options (self, *options): + """Return (as a tuple) the values of several options for this + command. Raise DistutilsOptionError if any of the options in + 'options' are not known.""" + + values = [] + try: + for opt in options: + values.append (getattr (self, opt)) + except AttributeError, name: + raise DistutilsOptionError, \ + "command %s: no such option %s" % \ + (self.get_command_name(), name) + + return tuple (values) + + + def set_option (self, option, value): + """Set the value of a single option for this command. Raise + DistutilsOptionError if 'option' is not known.""" + + if not hasattr (self, option): + raise DistutilsOptionError, \ + "command '%s': no such option '%s'" % \ + (self.get_command_name(), option) + if value is not None: + setattr (self, option, value) + + def set_options (self, **optval): + """Set the values of several options for this command. Raise + DistutilsOptionError if any of the options specified as + keyword arguments are not known.""" + + for k in optval.keys(): + if optval[k] is not None: + self.set_option (k, optval[k]) + + + # -- Convenience methods for commands ------------------------------ + + def get_command_name (self): + if hasattr (self, 'command_name'): + return self.command_name + else: + return self.__class__.__name__ + + + def set_undefined_options (self, src_cmd, *option_pairs): + """Set the values of any "undefined" options from corresponding + option values in some other command object. "Undefined" here + means "is None", which is the convention used to indicate + that an option has not been changed between + 'set_initial_values()' and 'set_final_values()'. Usually + called from 'set_final_values()' for options that depend on + some other command rather than another option of the same + command. 'src_cmd' is the other command from which option + values will be taken (a command object will be created for it + if necessary); the remaining arguments are + '(src_option,dst_option)' tuples which mean "take the value + of 'src_option' in the 'src_cmd' command object, and copy it + to 'dst_option' in the current command object".""" + + # Option_pairs: list of (src_option, dst_option) tuples + + src_cmd_obj = self.distribution.find_command_obj (src_cmd) + src_cmd_obj.ensure_ready () + try: + for (src_option, dst_option) in option_pairs: + if getattr (self, dst_option) is None: + self.set_option (dst_option, + src_cmd_obj.get_option (src_option)) + except AttributeError, name: + # duh, which command? + raise DistutilsOptionError, "unknown option %s" % name + + + def find_peer (self, command, create=1): + """Wrapper around Distribution's 'find_command_obj()' method: + find (create if necessary and 'create' is true) the command + object for 'command'..""" + + cmd_obj = self.distribution.find_command_obj (command, create) + cmd_obj.ensure_ready () + return cmd_obj + + + def get_peer_option (self, command, option): + """Find or create the command object for 'command', and return + its 'option' option.""" + + cmd_obj = self.find_peer (command) + return cmd_obj.get_option (option) + + + def run_peer (self, command): + """Run some other command: uses the 'run_command()' method of + Distribution, which creates the command object if necessary + and then invokes its 'run()' method.""" + + self.distribution.run_command (command) + + + # -- External world manipulation ----------------------------------- + + def warn (self, msg): + sys.stderr.write ("warning: %s: %s\n" % + (self.get_command_name(), msg)) + + + def execute (self, func, args, msg=None, level=1): + """Perform some action that affects the outside world (eg. + by writing to the filesystem). Such actions are special because + they should be disabled by the "dry run" flag, and should + announce themselves if the current verbosity level is high + enough. This method takes care of all that bureaucracy for you; + all you have to do is supply the funtion to call and an argument + tuple for it (to embody the "external action" being performed), + a message to print if the verbosity level is high enough, and an + optional verbosity threshold.""" + + # Generate a message if we weren't passed one + if msg is None: + msg = "%s %s" % (func.__name__, `args`) + if msg[-2:] == ',)': # correct for singleton tuple + msg = msg[0:-2] + ')' + + # Print it if verbosity level is high enough + self.announce (msg, level) + + # And do it, as long as we're not in dry-run mode + if not self.dry_run: + apply (func, args) + + # execute() + + + def mkpath (self, name, mode=0777): + util.mkpath (name, mode, + self.verbose, self.dry_run) + + + def copy_file (self, infile, outfile, + preserve_mode=1, preserve_times=1, link=None, level=1): + """Copy a file respecting verbose, dry-run and force flags.""" + + return util.copy_file (infile, outfile, + preserve_mode, preserve_times, + not self.force, + link, + self.verbose >= level, + self.dry_run) + + + def copy_tree (self, infile, outfile, + preserve_mode=1, preserve_times=1, preserve_symlinks=0, + level=1): + """Copy an entire directory tree respecting verbose, dry-run, + and force flags.""" + + return util.copy_tree (infile, outfile, + preserve_mode,preserve_times,preserve_symlinks, + not self.force, + self.verbose >= level, + self.dry_run) + + + def move_file (self, src, dst, level=1): + """Move a file respecting verbose and dry-run flags.""" + return util.move_file (src, dst, + self.verbose >= level, + self.dry_run) + + + def spawn (self, cmd, search_path=1, level=1): + from distutils.spawn import spawn + spawn (cmd, search_path, + self.verbose >= level, + self.dry_run) + + + def make_archive (self, base_name, format, + root_dir=None, base_dir=None): + util.make_archive (base_name, format, root_dir, base_dir, + self.verbose, self.dry_run) + + + def make_file (self, infiles, outfile, func, args, + exec_msg=None, skip_msg=None, level=1): + + """Special case of 'execute()' for operations that process one or + more input files and generate one output file. Works just like + 'execute()', except the operation is skipped and a different + message printed if 'outfile' already exists and is newer than + all files listed in 'infiles'.""" + + + if exec_msg is None: + exec_msg = "generating %s from %s" % \ + (outfile, string.join (infiles, ', ')) + if skip_msg is None: + skip_msg = "skipping %s (inputs unchanged)" % outfile + + + # Allow 'infiles' to be a single string + if type (infiles) is StringType: + infiles = (infiles,) + elif type (infiles) not in (ListType, TupleType): + raise TypeError, \ + "'infiles' must be a string, or a list or tuple of strings" + + # If 'outfile' must be regenerated (either because it doesn't + # exist, is out-of-date, or the 'force' flag is true) then + # perform the action that presumably regenerates it + if self.force or util.newer_group (infiles, outfile): + self.execute (func, args, exec_msg, level) + + # Otherwise, print the "skip" message + else: + self.announce (skip_msg, level) + + # make_file () + +# class Command |