First checkin of real Distutils code.

This commit is contained in:
Greg Ward 1999-03-22 14:52:19 +00:00
parent 481ac8811e
commit 2689e3ddce
5 changed files with 1131 additions and 0 deletions

597
Lib/distutils/core.py Normal file
View File

@ -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

63
Lib/distutils/errors.py Normal file
View File

@ -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'

View File

@ -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()

111
Lib/distutils/options.py Normal file
View File

@ -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

245
Lib/distutils/util.py Normal file
View File

@ -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 ()