diff options
author | Greg Ward <gward@python.net> | 1999-03-22 14:52:19 (GMT) |
---|---|---|
committer | Greg Ward <gward@python.net> | 1999-03-22 14:52:19 (GMT) |
commit | 2689e3ddce70e8acc5bc231a80221980d5bdfec3 (patch) | |
tree | 5c60133b1b4c8732503d4ecbe625091321be7217 /Lib/distutils/core.py | |
parent | 481ac8811e41f509e3a4ca6ef0151ce69671b43e (diff) | |
download | cpython-2689e3ddce70e8acc5bc231a80221980d5bdfec3.zip cpython-2689e3ddce70e8acc5bc231a80221980d5bdfec3.tar.gz cpython-2689e3ddce70e8acc5bc231a80221980d5bdfec3.tar.bz2 |
First checkin of real Distutils code.
Diffstat (limited to 'Lib/distutils/core.py')
-rw-r--r-- | Lib/distutils/core.py | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/Lib/distutils/core.py b/Lib/distutils/core.py new file mode 100644 index 0000000..3a7443c --- /dev/null +++ b/Lib/distutils/core.py @@ -0,0 +1,597 @@ +"""distutils.core + +The only module that needs to be imported to use the Distutils; provides +the 'setup' function (which must be called); the 'Distribution' class +(which may be subclassed if additional functionality is desired), and +the 'Command' class (which is used both internally by Distutils, and +may be subclassed by clients for still more flexibility).""" + +# created 1999/03/01, Greg Ward + +__rcsid__ = "$Id$" + +import sys +import string, re +from distutils.errors import * +from distutils.fancy_getopt import fancy_getopt + +# This is not *quite* the same as a Python NAME; I don't allow leading +# underscores. The fact that they're very similar is no coincidence... +command_re = re.compile (r'^[a-zA-Z]([a-zA-Z0-9_]*)$') + +# Defining this as a global is probably inadequate -- what about +# listing the available options (or even commands, which can vary +# quite late as well) +usage = '%s [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]' % sys.argv[0] + + + +def setup (**attrs): + """The gateway to the Distutils: do everything your setup script + needs to do, in a highly flexible and user-driven way. Briefly: + create a Distribution instance; parse the command-line, creating + and customizing instances of the command class for each command + found on the command-line; run each of those commands. + + The Distribution instance might be an instance of a class + supplied via the 'distclass' keyword argument to 'setup'; if no + such class is supplied, then the 'Distribution' class (also in + this module) is instantiated. All other arguments to 'setup' + (except for 'cmdclass') are used to set attributes of the + Distribution instance. + + The 'cmdclass' argument, if supplied, is a dictionary mapping + command names to command classes. Each command encountered on the + command line will be turned into a command class, which is in turn + instantiated; any class found in 'cmdclass' is used in place of the + default, which is (for command 'foo_bar') class 'FooBar' in module + 'distutils.command.foo_bar'. The command object must provide an + 'options' attribute which is a list of option specifiers for + 'distutils.fancy_getopt'. Any command-line options between the + current and the next command are used to set attributes in the + current command object. + + When the entire command-line has been successfully parsed, calls the + 'run' method on each command object in turn. This method will be + driven entirely by the Distribution object (which each command + object has a reference to, thanks to its constructor), and the + command-specific options that became attributes of each command + object.""" + + # Determine the distribution class -- either caller-supplied or + # our Distribution (see below). + klass = attrs.get ('distclass') + if klass: + del attrs['distclass'] + else: + klass = Distribution + + # Create the Distribution instance, using the remaining arguments + # (ie. everything except distclass) to initialize it + dist = klass (attrs) + + # Get it to parse the command line; any command-line errors are + # the end-users fault, so turn them into SystemExit to suppress + # tracebacks. + try: + dist.parse_command_line (sys.argv[1:]) + except DistutilsArgError, msg: + raise SystemExit, msg + + # And finally, run all the commands found on the command line. + dist.run_commands () + +# setup () + + +class Distribution: + """The core of the Distutils. Most of the work hiding behind + 'setup' is really done within a Distribution instance, which + farms the work out to the Distutils commands specified on the + command line. + + Clients will almost never instantiate Distribution directly, + unless the 'setup' function is totally inadequate to their needs. + However, it is conceivable that a client might wish to subclass + Distribution for some specialized purpose, and then pass the + subclass to 'setup' as the 'distclass' keyword argument. If so, + it is necessary to respect the expectations that 'setup' has of + Distribution: it must have a constructor and methods + 'parse_command_line()' and 'run_commands()' with signatures like + those described below.""" + + + # 'global_options' describes the command-line options that may + # be supplied to the client (setup.py) prior to any actual + # commands. Eg. "./setup.py -nv" or "./setup.py --verbose" + # both take advantage of these global options. + global_options = [('verbose', 'v', "run verbosely"), + ('dry-run', 'n', "don't actually do anything"), + ] + + + # -- Creation/initialization methods ------------------------------- + + def __init__ (self, attrs=None): + """Construct a new Distribution instance: initialize all the + attributes of a Distribution, and then uses 'attrs' (a + dictionary mapping attribute names to values) to assign + some of those attributes their "real" values. (Any attributes + not mentioned in 'attrs' will be assigned to some null + value: 0, None, an empty list or dictionary, etc.) Most + importantly, initialize the 'command_obj' attribute + to the empty dictionary; this will be filled in with real + command objects by 'parse_command_line()'.""" + + # Default values for our command-line options + self.verbose = 0 + self.dry_run = 0 + + # And for all other attributes (stuff that might be passed in + # from setup.py, rather than from the end-user) + self.name = None + self.version = None + self.author = None + self.licence = None + self.description = None + + self.cmdclass = {} + + # The rest of these are really the business of various commands, + # rather than of the Distribution itself. However, they have + # to be here as a conduit to the relevant command class. + self.py_modules = None + self.ext_modules = None + self.package = None + + # Now we'll use the attrs dictionary to possibly override + # any or all of these distribution options + if attrs: + for k in attrs.keys(): + setattr (self, k, attrs[k]) + + # And now initialize bookkeeping stuff that can't be supplied by + # the caller at all + self.command_obj = {} + + # __init__ () + + + def parse_command_line (self, args): + """Parse the client's command line: set any Distribution + attributes tied to command-line options, create all command + objects, and set their options from the command-line. 'args' + must be a list of command-line arguments, most likely + 'sys.argv[1:]' (see the 'setup()' function). This list is + first processed for "global options" -- options that set + attributes of the Distribution instance. Then, it is + alternately scanned for Distutils command and options for + that command. Each new command terminates the options for + the previous command. The allowed options for a command are + determined by the 'options' attribute of the command object + -- thus, we instantiate (and cache) every command object + here, in order to access its 'options' attribute. Any error + in that 'options' attribute raises DistutilsGetoptError; any + error on the command-line raises DistutilsArgError. If no + Distutils commands were found on the command line, raises + DistutilsArgError.""" + + # We have to parse the command line a bit at a time -- global + # options, then the first command, then its options, and so on -- + # because each command will be handled by a different class, and + # the options that are valid for a particular class aren't + # known until we instantiate the command class, which doesn't + # happen until we know what the command is. + + self.commands = [] + args = fancy_getopt (self.global_options, self, sys.argv[1:]) + + while args: + # Pull the current command from the head of the command line + command = args[0] + if not command_re.match (command): + raise SystemExit, "invalid command name '%s'" % command + self.commands.append (command) + + # Have to instantiate the command class now, so we have a + # way to get its valid options and somewhere to put the + # results of parsing its share of the command-line + cmd_obj = self.create_command_obj (command) + + # Require that the command class be derived from Command -- + # that way, we can be sure that we at least have the 'run' + # and 'get_option' methods. + if not isinstance (cmd_obj, Command): + raise DistutilsClassError, \ + "command class %s must subclass Command" % \ + cmd_obj.__class__ + + # XXX this assumes that cmd_obj provides an 'options' + # attribute, but we're not enforcing that anywhere! + args = fancy_getopt (cmd_obj.options, cmd_obj, args[1:]) + self.command_obj[command] = cmd_obj + + # while args + + # Oops, no commands found -- an end-user error + if not self.commands: + sys.stderr.write (usage + "\n") + raise DistutilsArgError, "no commands supplied" + + # parse_command_line() + + + # -- Command class/object methods ---------------------------------- + + # This is a method just so it can be overridden if desired; it doesn't + # actually use or change any attributes of the Distribution instance. + def find_command_class (self, command): + """Given a command, derives the names of the module and class + expected to implement the command: eg. 'foo_bar' becomes + 'distutils.command.foo_bar' (the module) and 'FooBar' (the + class within that module). Loads the module, extracts the + class from it, and returns the class object. + + Raises DistutilsModuleError with a semi-user-targeted error + message if the expected module could not be loaded, or the + expected class was not found in it.""" + + module_name = 'distutils.command.' + command + klass_name = string.join \ + (map (string.capitalize, string.split (command, '_')), '') + + try: + __import__ (module_name) + module = sys.modules[module_name] + except ImportError: + raise DistutilsModuleError, \ + "invalid command '%s' (no module named %s)" % \ + (command, module_name) + + try: + klass = vars(module)[klass_name] + except KeyError: + raise DistutilsModuleError, \ + "invalid command '%s' (no class '%s' in module '%s')" \ + % (command, klass_name, module_name) + + return klass + + # find_command_class () + + + def create_command_obj (self, command): + """Figure out the class that should implement a command, + instantiate it, cache and return the new "command object". + The "command class" is determined either by looking it up in + the 'cmdclass' attribute (this is the mechanism whereby + clients may override default Distutils commands or add their + own), or by calling the 'find_command_class()' method (if the + command name is not in 'cmdclass'.""" + + # Determine the command class -- either it's in the command_class + # dictionary, or we have to divine the module and class name + klass = self.cmdclass.get(command) + if not klass: + klass = self.find_command_class (command) + self.cmdclass[command] = klass + + # Found the class OK -- instantiate it + cmd_obj = klass (self) + return cmd_obj + + + def find_command_obj (self, command, create=1): + """Look up and return a command object in the cache maintained by + 'create_command_obj()'. If none found, the action taken + depends on 'create': if true (the default), create a new + command object by calling 'create_command_obj()' and return + it; otherwise, return None.""" + + cmd_obj = self.command_obj.get (command) + if not cmd_obj and create: + cmd_obj = self.create_command_obj (command) + self.command_obj[command] = cmd_obj + + return cmd_obj + + + # -- Methods that operate on the Distribution ---------------------- + + def announce (self, msg, level=1): + """Print 'msg' if 'level' is greater than or equal to the verbosity + level recorded in the 'verbose' attribute (which, currently, + can be only 0 or 1).""" + + if self.verbose >= level: + print msg + + + def run_commands (self): + """Run each command that was seen on the client command line. + Uses the list of commands found and cache of command objects + created by 'create_command_obj()'.""" + + for cmd in self.commands: + self.run_command (cmd) + + + def get_option (self, option): + """Return the value of a distribution option. Raise + DistutilsOptionError if 'option' is not known.""" + + try: + return getattr (self, opt) + except AttributeError: + raise DistutilsOptionError, \ + "unknown distribution option %s" % option + + + def get_options (self, *options): + """Return (as a tuple) the values of several distribution + options. Raise DistutilsOptionError if any element of + 'options' is not known.""" + + values = [] + try: + for opt in options: + values.append (getattr (self, opt)) + except AttributeError, name: + raise DistutilsOptionError, \ + "unknown distribution option %s" % name + + return tuple (values) + + + # -- Methods that operate on its Commands -------------------------- + + def run_command (self, command): + """Create a command object for 'command' if necessary, and + run the command by invoking its 'run()' method.""" + + self.announce ("running " + command) + cmd_obj = self.find_command_obj (command) + cmd_obj.run () + + + def get_command_option (self, command, option): + """Create a command object for 'command' if necessary, finalize + its option values by invoking its 'set_final_options()' + method, and return the value of its 'option' option. Raise + DistutilsOptionError if 'option' is not known for + that 'command'.""" + + cmd_obj = self.find_command_obj (command) + cmd_obj.set_final_options () + return cmd_obj.get_option (option) + try: + return getattr (cmd_obj, option) + except AttributeError: + raise DistutilsOptionError, \ + "command %s: no such option %s" % (command, option) + + + def get_command_options (self, command, *options): + """Create a command object for 'command' if necessary, finalize + its option values by invoking its 'set_final_options()' + method, and return the values of all the options listed in + 'options' for that command. Raise DistutilsOptionError if + 'option' is not known for that 'command'.""" + + cmd_obj = self.find_command_obj (command) + cmd_obj.set_final_options () + values = [] + try: + for opt in options: + values.append (getattr (cmd_obj, option)) + except AttributeError, name: + raise DistutilsOptionError, \ + "command %s: no such option %s" % (command, name) + + return tuple (values) + +# end class Distribution + + +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 'set_initial_options()' + and "initialized" (given their real values) in + 'set_final_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 'set_final_values()'. 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 'set_default_options()' method, which is the + real initializer and depends on the actual command being + instantiated.""" + + 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.set_default_options () + + # end __init__ () + + # Subclasses must define: + # set_default_options() + # provide default values for all options; may be overridden + # by Distutils client, by command-line options, or by options + # from option file + # set_final_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 set_default_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, + 'set_default_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 set_final_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 'set_default_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 + 'set_initial_options()', customized by the user and other + commands, and finalized in 'set_final_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.distribution.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.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.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.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 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.set_final_options () + 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 set_peer_option (self, command, option, value): + """Attempt to simulate a command-line override of some option + value in another command. Creates a command object for + 'command' if necessary, sets 'option' to 'value', and invokes + 'set_final_options()' on that command object. This will only + have the desired effect if the command object for 'command' + has not previously been created. Generally this is used to + ensure that the options in 'command' dependent on 'option' + are computed, hopefully (but not necessarily) deriving from + 'value'. It might be more accurate to call this method + 'influence_dependent_peer_options()'.""" + + cmd_obj = self.distribution.find_command_obj (command) + cmd_obj.set_option (option, value) + cmd_obj.set_final_options () + + + 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) + +# end class Command |