First checkin of real Distutils code.
This commit is contained in:
parent
481ac8811e
commit
2689e3ddce
|
@ -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
|
|
@ -0,0 +1,63 @@
|
|||
"""distutils.errors
|
||||
|
||||
Provides exceptions used by the Distutils modules. Note that Distutils
|
||||
modules may raise standard exceptions; in particular, SystemExit is
|
||||
usually raised for errors that are obviously the end-user's fault
|
||||
(eg. bad command-line arguments).
|
||||
|
||||
This module safe to use in "from ... import *" mode; it only exports
|
||||
symbols whose names start with "Distutils" and end with "Error"."""
|
||||
|
||||
# created 1999/03/03, Greg Ward
|
||||
|
||||
__rcsid__ = "$Id$"
|
||||
|
||||
from types import *
|
||||
|
||||
if type (RuntimeError) is ClassType:
|
||||
|
||||
# DistutilsError is the root of all Distutils evil.
|
||||
class DistutilsError (Exception):
|
||||
pass
|
||||
|
||||
# DistutilsModuleError is raised if we are unable to load an expected
|
||||
# module, or find an expected class within some module
|
||||
class DistutilsModuleError (DistutilsError):
|
||||
pass
|
||||
|
||||
# DistutilsClassError is raised if we encounter a distribution or command
|
||||
# class that's not holding up its end of the bargain.
|
||||
class DistutilsClassError (DistutilsError):
|
||||
pass
|
||||
|
||||
# DistutilsGetoptError (help me -- I have JavaProgrammersDisease!) is
|
||||
# raised if the option table provided to fancy_getopt is bogus.
|
||||
class DistutilsGetoptError (DistutilsError):
|
||||
pass
|
||||
|
||||
# DistutilsArgError is raised by fancy_getopt in response to getopt.error;
|
||||
# distutils.core then turns around and raises SystemExit from that. (Thus
|
||||
# client code should never see DistutilsArgError.)
|
||||
class DistutilsArgError (DistutilsError):
|
||||
pass
|
||||
|
||||
# DistutilsFileError is raised for any problems in the filesystem:
|
||||
# expected file not found, etc.
|
||||
class DistutilsFileError (DistutilsError):
|
||||
pass
|
||||
|
||||
# DistutilsOptionError is raised anytime an attempt is made to access
|
||||
# (get or set) an option that does not exist for a particular command
|
||||
# (or for the distribution itself).
|
||||
class DistutilsOptionError (DistutilsError):
|
||||
pass
|
||||
|
||||
# String-based exceptions
|
||||
else:
|
||||
DistutilsError = 'DistutilsError'
|
||||
DistutilsModuleError = 'DistutilsModuleError'
|
||||
DistutilsClassError = 'DistutilsClassError'
|
||||
DistutilsGetoptError = 'DistutilsGetoptError'
|
||||
DistutilsArgError = 'DistutilsArgError'
|
||||
DistutilsFileError = 'DistutilsFileError'
|
||||
DistutilsOptionError = 'DistutilsOptionError'
|
|
@ -0,0 +1,115 @@
|
|||
"""distutils.fancy_getopt
|
||||
|
||||
Wrapper around the standard getopt module that provides the following
|
||||
additional features:
|
||||
* short and long options are tied together
|
||||
* options have help strings, so fancy_getopt could potentially
|
||||
create a complete usage summary
|
||||
* options set attributes of a passed-in object
|
||||
"""
|
||||
|
||||
# created 1999/03/03, Greg Ward
|
||||
|
||||
__rcsid__ = "$Id$"
|
||||
|
||||
import string, re
|
||||
from types import *
|
||||
import getopt
|
||||
from distutils.errors import *
|
||||
|
||||
# Much like command_re in distutils.core, this is close to but not quite
|
||||
# the same as a Python NAME -- except, in the spirit of most GNU
|
||||
# utilities, we use '-' in place of '_'. (The spirit of LISP lives on!)
|
||||
# The similarities to NAME are again not a coincidence...
|
||||
longopt_re = re.compile (r'^[a-zA-Z]([a-zA-Z0-9-]*)$')
|
||||
|
||||
# This is used to translate long options to legitimate Python identifiers
|
||||
# (for use as attributes of some object).
|
||||
longopt_xlate = string.maketrans ('-', '_')
|
||||
|
||||
|
||||
def fancy_getopt (options, object, args):
|
||||
|
||||
# The 'options' table is a list of 3-tuples:
|
||||
# (long_option, short_option, help_string)
|
||||
# if an option takes an argument, its long_option should have '='
|
||||
# appended; short_option should just be a single character, no ':' in
|
||||
# any case. If a long_option doesn't have a corresponding
|
||||
# short_option, short_option should be None. All option tuples must
|
||||
# have long options.
|
||||
|
||||
# Build the short_opts string and long_opts list, remembering how
|
||||
# the two are tied together
|
||||
|
||||
short_opts = [] # we'll join 'em when done
|
||||
long_opts = []
|
||||
short2long = {}
|
||||
attr_name = {}
|
||||
takes_arg = {}
|
||||
|
||||
for (long, short, help) in options:
|
||||
# Type-check the option names
|
||||
if type (long) is not StringType or len (long) < 2:
|
||||
raise DistutilsGetoptError, \
|
||||
"long option must be a string of length >= 2"
|
||||
|
||||
if (not ((short is None) or
|
||||
(type (short) is StringType and len (short) == 1))):
|
||||
raise DistutilsGetoptError, \
|
||||
"short option must be None or string of length 1"
|
||||
|
||||
long_opts.append (long)
|
||||
|
||||
if long[-1] == '=': # option takes an argument?
|
||||
if short: short = short + ':'
|
||||
long = long[0:-1]
|
||||
takes_arg[long] = 1
|
||||
else:
|
||||
takes_arg[long] = 0
|
||||
|
||||
# Now enforce some bondage on the long option name, so we can later
|
||||
# translate it to an attribute name in 'object'. Have to do this a
|
||||
# bit late to make sure we've removed any trailing '='.
|
||||
if not longopt_re.match (long):
|
||||
raise DistutilsGetoptError, \
|
||||
("invalid long option name '%s' " +
|
||||
"(must be letters, numbers, hyphens only") % long
|
||||
|
||||
attr_name[long] = string.translate (long, longopt_xlate)
|
||||
if short:
|
||||
short_opts.append (short)
|
||||
short2long[short[0]] = long
|
||||
|
||||
# end loop over 'options'
|
||||
|
||||
short_opts = string.join (short_opts)
|
||||
try:
|
||||
(opts, args) = getopt.getopt (args, short_opts, long_opts)
|
||||
except getopt.error, msg:
|
||||
raise DistutilsArgError, msg
|
||||
|
||||
for (opt, val) in opts:
|
||||
if len (opt) == 2 and opt[0] == '-': # it's a short option
|
||||
opt = short2long[opt[1]]
|
||||
|
||||
elif len (opt) > 2 and opt[0:2] == '--':
|
||||
opt = opt[2:]
|
||||
|
||||
else:
|
||||
raise RuntimeError, "getopt lies! (bad option string '%s')" % \
|
||||
opt
|
||||
|
||||
attr = attr_name[opt]
|
||||
if takes_arg[opt]:
|
||||
setattr (object, attr, val)
|
||||
else:
|
||||
if val == '':
|
||||
setattr (object, attr, 1)
|
||||
else:
|
||||
raise RuntimeError, "getopt lies! (bad value '%s')" % value
|
||||
|
||||
# end loop over options found in 'args'
|
||||
|
||||
return args
|
||||
|
||||
# end fancy_getopt()
|
|
@ -0,0 +1,111 @@
|
|||
# XXX this is ridiculous! if commands need to pass options around,
|
||||
# they can just pass them via the 'run' method... what we REALLY need
|
||||
# is a way for commands to get at each other, via the Distribution!
|
||||
|
||||
class Options:
|
||||
"""Used by Distribution and Command to encapsulate distribution
|
||||
and command options -- parsing them from command-line arguments,
|
||||
passing them between the distribution and command objects, etc."""
|
||||
|
||||
# -- Creation/initialization methods -------------------------------
|
||||
|
||||
def __init__ (self, owner):
|
||||
|
||||
# 'owner' is the object (presumably either a Distribution
|
||||
# or Command instance) to which this set of options applies.
|
||||
self.owner = owner
|
||||
|
||||
# The option table: maps option names to dictionaries, which
|
||||
# look something like:
|
||||
# { 'longopt': long command-line option string (optional)
|
||||
# 'shortopt': short option (1 char) (optional)
|
||||
# 'type': 'string', 'boolean', or 'list'
|
||||
# 'description': text description (eg. for help strings)
|
||||
# 'default': default value for the option
|
||||
# 'send': list of (cmd,option) tuples: send option down the line
|
||||
# 'receive': (cmd,option) tuple: pull option from upstream
|
||||
# }
|
||||
self.table = {}
|
||||
|
||||
|
||||
def set_basic_options (self, *options):
|
||||
"""Add very basic options: no separate longopt, no fancy typing, no
|
||||
send targets or receive destination. The arguments should just
|
||||
be {1..4}-tuples of
|
||||
(name [, shortopt [, description [, default]]])
|
||||
If name ends with '=', the option takes a string argument;
|
||||
otherwise it's boolean."""
|
||||
|
||||
for opt in options:
|
||||
if not (type (opt) is TupleType and 1 <= len (opt) <= 4):
|
||||
raise ValueError, \
|
||||
("invalid basic option record '%s': " + \
|
||||
"must be tuple of length 1 .. 4") % opt
|
||||
|
||||
elements = ('name', 'shortopt', 'description', 'default')
|
||||
name = opt[0]
|
||||
self.table[name] = {}
|
||||
for i in range (1,4):
|
||||
if len (opt) >= i:
|
||||
self.table[name][elements[i]] = opt[i]
|
||||
else:
|
||||
break
|
||||
|
||||
# set_basic_options ()
|
||||
|
||||
|
||||
def add_option (self, name, **args):
|
||||
|
||||
# XXX should probably sanity-check the keys of args
|
||||
self.table[name] = args
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
# These are in the order that they will execute in to ensure proper
|
||||
# prioritizing of option sources -- the default value is the most
|
||||
# basic; it can be overridden by "client options" (the keyword args
|
||||
# passed from setup.py to the 'setup' function); they in turn lose to
|
||||
# options passed in "from above" (ie. from the Distribution, or from
|
||||
# higher-level Commands); these in turn may be overridden by
|
||||
# command-line arguments (which come from the end-user, the runner of
|
||||
# setup.py). Only when all this is done can we pass options down to
|
||||
# other Commands.
|
||||
|
||||
# Hmmm, it also matters in which order Commands are processed: should a
|
||||
# command-line option to 'make_blib' take precedence over the
|
||||
# corresponding value passed down from its boss, 'build'?
|
||||
|
||||
def set_defaults (self):
|
||||
pass
|
||||
|
||||
def set_client_options (self, options):
|
||||
# 'self' should be a Distribution instance for this one --
|
||||
# this is to process the kw args passed to 'setup'
|
||||
pass
|
||||
|
||||
def receive_option (self, option, value):
|
||||
# do we need to know the identity of the sender? don't
|
||||
# think we should -- too much B&D
|
||||
|
||||
# oh, 'self' should be anything *but* a Distribution (ie.
|
||||
# a Command instance) -- only Commands take orders from above!
|
||||
# (ironically enough)
|
||||
pass
|
||||
|
||||
def parse_command_line (self, args):
|
||||
# here, 'self' can usefully be either a Distribution (for parsing
|
||||
# "global" command-line options) or a Command (for "command-specific"
|
||||
# options)
|
||||
pass
|
||||
|
||||
|
||||
def send_option (self, option, dest):
|
||||
# perhaps this should not take a dest, but send the option
|
||||
# to all possible receivers?
|
||||
pass
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
# class Options
|
|
@ -0,0 +1,245 @@
|
|||
"""distutils.util
|
||||
|
||||
General-purpose utility functions used throughout the Distutils
|
||||
(especially in command classes). Mostly filesystem manipulation, but
|
||||
not limited to that. The functions in this module generally raise
|
||||
DistutilsFileError when they have problems with the filesystem, because
|
||||
os.error in pre-1.5.2 Python only gives the error message and not the
|
||||
file causing it."""
|
||||
|
||||
# created 1999/03/08, Greg Ward
|
||||
|
||||
__rcsid__ = "$Id$"
|
||||
|
||||
import os
|
||||
from distutils.errors import *
|
||||
|
||||
|
||||
# I don't use os.makedirs because a) it's new to Python 1.5.2, and
|
||||
# b) it blows up if the directory already exists (I want to silently
|
||||
# succeed in that case).
|
||||
def mkpath (name, mode=0777, verbose=0):
|
||||
"""Create a directory and any missing ancestor directories. If the
|
||||
directory already exists, return silently. Raise
|
||||
DistutilsFileError if unable to create some directory along the
|
||||
way (eg. some sub-path exists, but is a file rather than a
|
||||
directory). If 'verbose' is true, print a one-line summary of
|
||||
each mkdir to stdout."""
|
||||
|
||||
# XXX what's the better way to handle verbosity? print as we create
|
||||
# each directory in the path (the current behaviour), or only announce
|
||||
# the creation of the whole path, and force verbose=0 on all sub-calls?
|
||||
|
||||
if os.path.isdir (name):
|
||||
return
|
||||
|
||||
(head, tail) = os.path.split (name)
|
||||
tails = [tail] # stack of lone dirs to create
|
||||
|
||||
while head and tail and not os.path.isdir (head):
|
||||
#print "splitting '%s': " % head,
|
||||
(head, tail) = os.path.split (head)
|
||||
#print "to ('%s','%s')" % (head, tail)
|
||||
tails.insert (0, tail) # push next higher dir onto stack
|
||||
|
||||
#print "stack of tails:", tails
|
||||
|
||||
# now 'head' contains the highest directory that already exists
|
||||
for d in tails:
|
||||
#print "head = %s, d = %s: " % (head, d),
|
||||
head = os.path.join (head, d)
|
||||
if verbose:
|
||||
print "creating", head
|
||||
try:
|
||||
os.mkdir (head)
|
||||
except os.error, (errno, errstr):
|
||||
raise DistutilsFileError, "%s: %s" % (head, errstr)
|
||||
|
||||
# mkpath ()
|
||||
|
||||
|
||||
def newer (file1, file2):
|
||||
"""Return true if file1 exists and is more recently modified than
|
||||
file2, or if file1 exists and file2 doesn't. Return false if both
|
||||
exist and file2 is the same age or younger than file1. Raises
|
||||
DistutilsFileError if file1 does not exist."""
|
||||
|
||||
if not os.path.exists (file1):
|
||||
raise DistutilsFileError, "file '%s' does not exist" % file1
|
||||
if not os.path.exists (file2):
|
||||
return 1
|
||||
|
||||
from stat import *
|
||||
mtime1 = os.stat(file1)[ST_MTIME]
|
||||
mtime2 = os.stat(file2)[ST_MTIME]
|
||||
|
||||
return mtime1 > mtime2
|
||||
|
||||
# newer ()
|
||||
|
||||
|
||||
def make_file (src, dst, func, args,
|
||||
verbose=0, update_message=None, noupdate_message=None):
|
||||
"""Makes 'dst' from 'src' (both filenames) by calling 'func' with
|
||||
'args', but only if it needs to: i.e. if 'dst' does not exist or
|
||||
'src' is newer than 'dst'."""
|
||||
|
||||
if newer (src, dst):
|
||||
if verbose and update_message:
|
||||
print update_message
|
||||
apply (func, args)
|
||||
else:
|
||||
if verbose and noupdate_message:
|
||||
print noupdate_message
|
||||
|
||||
# make_file ()
|
||||
|
||||
|
||||
def _copy_file_contents (src, dst, buffer_size=16*1024):
|
||||
"""Copy the file 'src' to 'dst'; both must be filenames. Any error
|
||||
opening either file, reading from 'src', or writing to 'dst',
|
||||
raises DistutilsFileError. Data is read/written in chunks of
|
||||
'buffer_size' bytes (default 16k). No attempt is made to handle
|
||||
anything apart from regular files."""
|
||||
|
||||
# Stolen from shutil module in the standard library, but with
|
||||
# custom error-handling added.
|
||||
|
||||
fsrc = None
|
||||
fdst = None
|
||||
try:
|
||||
try:
|
||||
fsrc = open(src, 'rb')
|
||||
except os.error, (errno, errstr):
|
||||
raise DistutilsFileError, "could not open %s: %s" % (src, errstr)
|
||||
|
||||
try:
|
||||
fdst = open(dst, 'wb')
|
||||
except os.error, (errno, errstr):
|
||||
raise DistutilsFileError, "could not create %s: %s" % (dst, errstr)
|
||||
|
||||
while 1:
|
||||
try:
|
||||
buf = fsrc.read (buffer_size)
|
||||
except os.error, (errno, errstr):
|
||||
raise DistutilsFileError, \
|
||||
"could not read from %s: %s" % (src, errstr)
|
||||
|
||||
if not buf:
|
||||
break
|
||||
|
||||
try:
|
||||
fdst.write(buf)
|
||||
except os.error, (errno, errstr):
|
||||
raise DistutilsFileError, \
|
||||
"could not write to %s: %s" % (dst, errstr)
|
||||
|
||||
finally:
|
||||
if fdst:
|
||||
fdst.close()
|
||||
if fsrc:
|
||||
fsrc.close()
|
||||
|
||||
# _copy_file_contents()
|
||||
|
||||
|
||||
def copy_file (src, dst,
|
||||
preserve_mode=1,
|
||||
preserve_times=1,
|
||||
update=0,
|
||||
verbose=0):
|
||||
|
||||
"""Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src'
|
||||
is copied there with the same name; otherwise, it must be a
|
||||
filename. (If the file exists, it will be ruthlessly clobbered.)
|
||||
If 'preserve_mode' is true (the default), the file's mode (type
|
||||
and permission bits, or whatever is analogous on the current
|
||||
platform) is copied. If 'preserve_times' is true (the default),
|
||||
the last-modified and last-access times are copied as well. If
|
||||
'update' is true, 'src' will only be copied if 'dst' does not
|
||||
exist, or if 'dst' does exist but is older than 'src'. If
|
||||
'verbose' is true, then a one-line summary of the copy will be
|
||||
printed to stdout."""
|
||||
|
||||
# XXX doesn't copy Mac-specific metadata
|
||||
|
||||
from shutil import copyfile
|
||||
from stat import *
|
||||
|
||||
if not os.path.isfile (src):
|
||||
raise DistutilsFileError, \
|
||||
"can't copy %s:not a regular file" % src
|
||||
|
||||
if os.path.isdir (dst):
|
||||
dir = dst
|
||||
dst = os.path.join (dst, os.path.basename (src))
|
||||
else:
|
||||
dir = os.path.dirname (dst)
|
||||
|
||||
if update and not newer (src, dst):
|
||||
return
|
||||
|
||||
if verbose:
|
||||
print "copying %s -> %s" % (src, dir)
|
||||
|
||||
copyfile (src, dst)
|
||||
if preserve_mode or preserve_times:
|
||||
st = os.stat (src)
|
||||
if preserve_mode:
|
||||
os.chmod (dst, S_IMODE (st[ST_MODE]))
|
||||
if preserve_times:
|
||||
os.utime (dst, (st[ST_ATIME], st[ST_MTIME]))
|
||||
|
||||
# copy_file ()
|
||||
|
||||
|
||||
def copy_tree (src, dst,
|
||||
preserve_mode=1,
|
||||
preserve_times=1,
|
||||
preserve_symlinks=0,
|
||||
update=0,
|
||||
verbose=0):
|
||||
|
||||
"""Copy an entire directory tree 'src' to a new location 'dst'. Both
|
||||
'src' and 'dst' must be directory names. If 'src' is not a
|
||||
directory, raise DistutilsFileError. If 'dst' does not exist, it
|
||||
is created with 'mkpath'. The endresult of the copy is that
|
||||
every file in 'src' is copied to 'dst', and directories under
|
||||
'src' are recursively copied to 'dst'.
|
||||
|
||||
'preserve_mode' and 'preserve_times' are the same as for
|
||||
'copy_file'; note that they only apply to regular files, not to
|
||||
directories. If 'preserve_symlinks' is true, symlinks will be
|
||||
copied as symlinks (on platforms that support them!); otherwise
|
||||
(the default), the destination of the symlink will be copied.
|
||||
'update' and 'verbose' are the same as for 'copy_file'."""
|
||||
|
||||
if not os.path.isdir (src):
|
||||
raise DistutilsFileError, \
|
||||
"cannot copy tree %s: not a directory" % src
|
||||
try:
|
||||
names = os.listdir (src)
|
||||
except os.error, (errno, errstr):
|
||||
raise DistutilsFileError, \
|
||||
"error listing files in %s: %s" % (src, errstr)
|
||||
|
||||
|
||||
mkpath (dst, verbose=verbose)
|
||||
|
||||
for n in names:
|
||||
src_name = os.path.join (src, n)
|
||||
dst_name = os.path.join (dst, n)
|
||||
|
||||
if preserve_symlinks and os.path.islink (src_name):
|
||||
link_dest = os.readlink (src_name)
|
||||
os.symlink (link_dest, dst_name)
|
||||
elif os.path.isdir (src_name):
|
||||
copy_tree (src_name, dst_name,
|
||||
preserve_mode, preserve_times, preserve_symlinks,
|
||||
update, verbose)
|
||||
else:
|
||||
copy_file (src_name, dst_name,
|
||||
preserve_mode, preserve_times,
|
||||
update, verbose)
|
||||
|
||||
# copy_tree ()
|
Loading…
Reference in New Issue