summaryrefslogtreecommitdiffstats
path: root/Lib/distutils/dist.py
diff options
context:
space:
mode:
authorGreg Ward <gward@python.net>2000-04-04 01:40:52 (GMT)
committerGreg Ward <gward@python.net>2000-04-04 01:40:52 (GMT)
commitfe6462c1f3206dd69659d20b5babab515fc6d4c3 (patch)
tree500b1215d19bb611a3c754789434a8757bc1c8d6 /Lib/distutils/dist.py
parent6c981ad25e38a2f02a9347310bdc680755208450 (diff)
downloadcpython-fe6462c1f3206dd69659d20b5babab515fc6d4c3.zip
cpython-fe6462c1f3206dd69659d20b5babab515fc6d4c3.tar.gz
cpython-fe6462c1f3206dd69659d20b5babab515fc6d4c3.tar.bz2
Reorganization: moved the Distribution class from core.py to dist.py, and
the Command class from core.py to cmd.py. No other code needs changing though; distutils.core still provides the Command and Distribution classes, although indirectly now.
Diffstat (limited to 'Lib/distutils/dist.py')
-rw-r--r--Lib/distutils/dist.py567
1 files changed, 567 insertions, 0 deletions
diff --git a/Lib/distutils/dist.py b/Lib/distutils/dist.py
new file mode 100644
index 0000000..50e7556
--- /dev/null
+++ b/Lib/distutils/dist.py
@@ -0,0 +1,567 @@
+"""distutils.dist
+
+Provides the Distribution class, which represents the module distribution
+being built/installed/distributed."""
+
+# created 2000/04/03, Greg Ward
+# (extricated from core.py; actually dates back to the beginning)
+
+__revision__ = "$Id$"
+
+import sys, string, re
+from types import *
+from copy import copy
+from distutils.errors import *
+from distutils.fancy_getopt import fancy_getopt, print_help
+
+
+# Regex to define acceptable Distutils command names. 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; the default naming scheme is
+# to look for a Python module named after the command.
+command_re = re.compile (r'^[a-zA-Z]([a-zA-Z0-9_]*)$')
+
+
+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. This list should be kept to a bare minimum,
+ # since every global option is also valid as a command option -- and we
+ # don't want to pollute the commands with too many options that they
+ # have minimal control over.
+ global_options = [('verbose', 'v',
+ "run verbosely (default)"),
+ ('quiet', 'q',
+ "run quietly (turns verbosity off)"),
+ ('dry-run', 'n',
+ "don't actually do anything"),
+ ('force', 'f',
+ "skip dependency checking between files"),
+ ('help', 'h',
+ "show this help message"),
+ ]
+ negative_opt = {'quiet': 'verbose'}
+
+
+ # -- 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 = 1
+ self.dry_run = 0
+ self.force = 0
+ self.help = 0
+ self.help_commands = 0
+
+ # And the "distribution meta-data" options -- these can only
+ # come from setup.py (the caller), not the command line
+ # (or a hypothetical config file).
+ self.name = None
+ self.version = None
+ self.author = None
+ self.author_email = None
+ self.maintainer = None
+ self.maintainer_email = None
+ self.url = None
+ self.licence = None
+ self.description = None
+
+ # 'cmdclass' maps command names to class objects, so we
+ # can 1) quickly figure out which class to instantiate when
+ # we need to create a new command object, and 2) have a way
+ # for the client to override command classes
+ self.cmdclass = {}
+
+ # These options are really the business of various commands, rather
+ # than of the Distribution itself. We provide aliases for them in
+ # Distribution as a convenience to the developer.
+ # dictionary.
+ self.packages = None
+ self.package_dir = None
+ self.py_modules = None
+ self.libraries = None
+ self.ext_modules = None
+ self.ext_package = None
+ self.include_dirs = None
+ self.extra_path = None
+
+ # And now initialize bookkeeping stuff that can't be supplied by
+ # the caller at all. 'command_obj' maps command names to
+ # Command instances -- that's how we enforce that every command
+ # class is a singleton.
+ self.command_obj = {}
+
+ # 'have_run' maps command names to boolean values; it keeps track
+ # of whether we have actually run a particular command, to make it
+ # cheap to "run" a command whenever we think we might need to -- if
+ # it's already been done, no need for expensive filesystem
+ # operations, we just check the 'have_run' dictionary and carry on.
+ # It's only safe to query 'have_run' for a command class that has
+ # been instantiated -- a false value will be inserted when the
+ # command object is created, and replaced with a true value when
+ # the command is succesfully run. Thus it's probably best to use
+ # '.get()' rather than a straight lookup.
+ self.have_run = {}
+
+ # Now we'll use the attrs dictionary (ultimately, keyword args from
+ # the client) to possibly override any or all of these distribution
+ # options.
+ if attrs:
+
+ # Pull out the set of command options and work on them
+ # specifically. Note that this order guarantees that aliased
+ # command options will override any supplied redundantly
+ # through the general options dictionary.
+ options = attrs.get ('options')
+ if options:
+ del attrs['options']
+ for (command, cmd_options) in options.items():
+ cmd_obj = self.find_command_obj (command)
+ for (key, val) in cmd_options.items():
+ cmd_obj.set_option (key, val)
+ # loop over commands
+ # if any command options
+
+ # Now work on the rest of the attributes. Any attribute that's
+ # not already defined is invalid!
+ for (key,val) in attrs.items():
+ if hasattr (self, key):
+ setattr (self, key, val)
+ else:
+ raise DistutilsOptionError, \
+ "invalid distribution option '%s'" % key
+
+ # __init__ ()
+
+
+ def parse_command_line (self, args):
+ """Parse the setup script'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. Return true if
+ command-line successfully parsed and we should carry on with
+ executing commands; false if no errors but we shouldn't execute
+ commands (currently, this only happens if user asks for
+ help)."""
+
+ # late import because of mutual dependence between these classes
+ from distutils.cmd import Command
+
+
+ # 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 = []
+ options = self.global_options + \
+ [('help-commands', None,
+ "list all available commands")]
+ args = fancy_getopt (options, self.negative_opt,
+ self, sys.argv[1:])
+
+ # User just wants a list of commands -- we'll print it out and stop
+ # processing now (ie. if they ran "setup --help-commands foo bar",
+ # we ignore "foo bar").
+ if self.help_commands:
+ self.print_commands ()
+ print
+ print usage
+ return
+
+ 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)
+
+ # Make sure we have a command object to put the options into
+ # (this either pulls it out of a cache of command objects,
+ # or finds and instantiates the command class).
+ try:
+ cmd_obj = self.find_command_obj (command)
+ except DistutilsModuleError, msg:
+ raise DistutilsArgError, msg
+
+ # 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__
+
+ # Also make sure that the command object provides a list of its
+ # known options
+ if not (hasattr (cmd_obj, 'user_options') and
+ type (cmd_obj.user_options) is ListType):
+ raise DistutilsClassError, \
+ ("command class %s must provide " +
+ "'user_options' attribute (a list of tuples)") % \
+ cmd_obj.__class__
+
+ # Poof! like magic, all commands support the global
+ # options too, just by adding in 'global_options'.
+ negative_opt = self.negative_opt
+ if hasattr (cmd_obj, 'negative_opt'):
+ negative_opt = copy (negative_opt)
+ negative_opt.update (cmd_obj.negative_opt)
+
+ options = self.global_options + cmd_obj.user_options
+ args = fancy_getopt (options, negative_opt,
+ cmd_obj, args[1:])
+ if cmd_obj.help:
+ print_help (self.global_options,
+ header="Global options:")
+ print
+ print_help (cmd_obj.user_options,
+ header="Options for '%s' command:" % command)
+ print
+ print usage
+ return
+
+ self.command_obj[command] = cmd_obj
+ self.have_run[command] = 0
+
+ # while args
+
+ # If the user wants help -- ie. they gave the "--help" option --
+ # give it to 'em. We do this *after* processing the commands in
+ # case they want help on any particular command, eg.
+ # "setup.py --help foo". (This isn't the documented way to
+ # get help on a command, but I support it because that's how
+ # CVS does it -- might as well be consistent.)
+ if self.help:
+ print_help (self.global_options, header="Global options:")
+ print
+
+ for command in self.commands:
+ klass = self.find_command_class (command)
+ print_help (klass.user_options,
+ header="Options for '%s' command:" % command)
+ print
+
+ print usage
+ return
+
+ # Oops, no commands found -- an end-user error
+ if not self.commands:
+ raise DistutilsArgError, "no commands supplied"
+
+ # All is well: return true
+ return 1
+
+ # parse_command_line()
+
+
+ def print_command_list (self, commands, header, max_length):
+ """Print a subset of the list of all commands -- used by
+ 'print_commands()'."""
+
+ print header + ":"
+
+ for cmd in commands:
+ klass = self.cmdclass.get (cmd)
+ if not klass:
+ klass = self.find_command_class (cmd)
+ try:
+ description = klass.description
+ except AttributeError:
+ description = "(no description available)"
+
+ print " %-*s %s" % (max_length, cmd, description)
+
+ # print_command_list ()
+
+
+ def print_commands (self):
+ """Print out a help message listing all available commands with
+ a description of each. The list is divided into "standard
+ commands" (listed in distutils.command.__all__) and "extra
+ commands" (mentioned in self.cmdclass, but not a standard
+ command). The descriptions come from the command class
+ attribute 'description'."""
+
+ import distutils.command
+ std_commands = distutils.command.__all__
+ is_std = {}
+ for cmd in std_commands:
+ is_std[cmd] = 1
+
+ extra_commands = []
+ for cmd in self.cmdclass.keys():
+ if not is_std.get(cmd):
+ extra_commands.append (cmd)
+
+ max_length = 0
+ for cmd in (std_commands + extra_commands):
+ if len (cmd) > max_length:
+ max_length = len (cmd)
+
+ self.print_command_list (std_commands,
+ "Standard commands",
+ max_length)
+ if extra_commands:
+ print
+ self.print_command_list (extra_commands,
+ "Extra commands",
+ max_length)
+
+ # print_commands ()
+
+
+
+ # -- 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 = 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. If 'command' is an invalid
+ command name, then DistutilsModuleError will be raised."""
+
+ 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):
+
+ """Do whatever it takes to run a command (including nothing at all,
+ if the command has already been run). Specifically: if we have
+ already created and run the command named by 'command', return
+ silently without doing anything. If the command named by
+ 'command' doesn't even have a command object yet, create one.
+ Then invoke 'run()' on that command object (or an existing
+ one)."""
+
+ # Already been here, done that? then return silently.
+ if self.have_run.get (command):
+ return
+
+ self.announce ("running " + command)
+ cmd_obj = self.find_command_obj (command)
+ cmd_obj.ensure_ready ()
+ cmd_obj.run ()
+ self.have_run[command] = 1
+
+
+ def get_command_option (self, command, option):
+ """Create a command object for 'command' if necessary, ensure that
+ its option values are all set to their final values, 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.ensure_ready ()
+ 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, ensure that
+ its option values are all set to their final values, and return
+ a tuple containing the values of all the options listed in
+ 'options' for that command. Raise DistutilsOptionError if any
+ invalid option is supplied in 'options'."""
+
+ cmd_obj = self.find_command_obj (command)
+ cmd_obj.ensure_ready ()
+ 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)
+
+
+ # -- Distribution query methods ------------------------------------
+
+ def has_pure_modules (self):
+ return len (self.packages or self.py_modules or []) > 0
+
+ def has_ext_modules (self):
+ return self.ext_modules and len (self.ext_modules) > 0
+
+ def has_c_libraries (self):
+ return self.libraries and len (self.libraries) > 0
+
+ def has_modules (self):
+ return self.has_pure_modules() or self.has_ext_modules()
+
+ def is_pure (self):
+ return (self.has_pure_modules() and
+ not self.has_ext_modules() and
+ not self.has_c_libraries())
+
+ def get_name (self):
+ return self.name or "UNKNOWN"
+
+ def get_full_name (self):
+ return "%s-%s" % ((self.name or "UNKNOWN"), (self.version or "???"))
+
+# class Distribution
+
+
+if __name__ == "__main__":
+ dist = Distribution ()
+ print "ok"