Commit 42926ddc authored by Greg Ward's avatar Greg Ward

Careful rethink of command options, distribution options, distribution

  attributes, etc.  Biggest change was to the Distribution constructor
  -- it now looks for an 'options' attribute, which contains values
  (options) that are explicitly farmed out to the commands.  Also,
  certain options supplied to Distribution (ie. in the 'setup()' call in
  setup.py) are now "command option aliases", meaning they are dropped
  right into a certain command rather than being distribution options.
  This is handled by a new Distribution class attribute,
  'alias_options'.
Various comment changes to reflect the new way-of-thinking.
Added 'get_command_name()' method to Command -- was assuming its
  existence all along as 'command_name()', so changed the code that
  needs it to call 'get_command_name()'.
parent 3d50b908
...@@ -72,9 +72,12 @@ def setup (**attrs): ...@@ -72,9 +72,12 @@ def setup (**attrs):
# (ie. everything except distclass) to initialize it # (ie. everything except distclass) to initialize it
dist = klass (attrs) dist = klass (attrs)
# Get it to parse the command line; any command-line errors are # If we had a config file, this is where we would parse it: override
# the end-users fault, so turn them into SystemExit to suppress # the client-supplied command options, but be overridden by the
# tracebacks. # command line.
# Parse the command line; any command-line errors are the end-users
# fault, so turn them into SystemExit to suppress tracebacks.
try: try:
dist.parse_command_line (sys.argv[1:]) dist.parse_command_line (sys.argv[1:])
except DistutilsArgError, msg: except DistutilsArgError, msg:
...@@ -111,6 +114,18 @@ class Distribution: ...@@ -111,6 +114,18 @@ class Distribution:
('dry-run', 'n', "don't actually do anything"), ('dry-run', 'n', "don't actually do anything"),
] ]
# 'alias_options' map distribution options to command options -- the
# idea is that the most common, essential options can be directly
# specified as Distribution attributes, and the rest can go in the
# 'options' dictionary. These aliases are for those common, essential
# options.
alias_options = { 'py_modules': ('build_py', 'modules'),
'ext_modules': ('build_ext', 'modules'),
'package': [('build_py', 'package',),
('build_ext', 'package')],
}
# -- Creation/initialization methods ------------------------------- # -- Creation/initialization methods -------------------------------
...@@ -129,11 +144,13 @@ class Distribution: ...@@ -129,11 +144,13 @@ class Distribution:
self.verbose = 0 self.verbose = 0
self.dry_run = 0 self.dry_run = 0
# And for all other attributes (stuff that might be passed in # And the "distribution meta-data" options -- these can only
# from setup.py, rather than from the end-user) # come from setup.py (the caller), not the command line
# (or a hypothetical config file)..
self.name = None self.name = None
self.version = None self.version = None
self.author = None self.author = None
self.url = None
self.licence = None self.licence = None
self.description = None self.description = None
...@@ -143,18 +160,14 @@ class Distribution: ...@@ -143,18 +160,14 @@ class Distribution:
# for the client to override command classes # for the client to override command classes
self.cmdclass = {} self.cmdclass = {}
# The rest of these are really the business of various commands, # These options are really the business of various commands, rather
# rather than of the Distribution itself. However, they have # than of the Distribution itself. We provide aliases for them in
# to be here as a conduit to the relevant command class. # Distribution as a convenience to the developer.
self.py_modules = None # dictionary.
self.ext_modules = None # XXX not needed anymore! (I think...)
self.package = None #self.py_modules = None
#self.ext_modules = None
# Now we'll use the attrs dictionary to possibly override #self.package = None
# 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 # And now initialize bookkeeping stuff that can't be supplied by
# the caller at all. 'command_obj' maps command names to # the caller at all. 'command_obj' maps command names to
...@@ -174,6 +187,49 @@ class Distribution: ...@@ -174,6 +187,49 @@ class Distribution:
# '.get()' rather than a straight lookup. # '.get()' rather than a straight lookup.
self.have_run = {} self.have_run = {}
# Now we'll use the attrs dictionary (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. Note that some of
# these may be aliases for command options, so we might go
# through some of the above again.
for (key,val) in attrs.items():
alias = self.alias_options.get (key)
if alias:
if type (alias) is ListType:
for (command, cmd_option) in alias:
cmd_obj = self.find_command_obj (command)
cmd_obj.set_option (cmd_option, val)
elif type (alias) is TupleType:
(command, cmd_option) = alias
cmd_obj = self.find_command_obj (command)
cmd_obj.set_option (cmd_option, val)
else:
raise RuntimeError, \
("oops! bad alias option for '%s': " +
"must be tuple or list of tuples") % key
elif hasattr (self, key):
setattr (self, key, val)
else:
raise DistutilsOptionError, \
"invalid distribution option '%s'" % key
# __init__ () # __init__ ()
...@@ -213,10 +269,10 @@ class Distribution: ...@@ -213,10 +269,10 @@ class Distribution:
raise SystemExit, "invalid command name '%s'" % command raise SystemExit, "invalid command name '%s'" % command
self.commands.append (command) self.commands.append (command)
# Have to instantiate the command class now, so we have a # Make sure we have a command object to put the options into
# way to get its valid options and somewhere to put the # (this either pulls it out of a cache of command objects,
# results of parsing its share of the command-line # or finds and instantiates the command class).
cmd_obj = self.create_command_obj (command) cmd_obj = self.find_command_obj (command)
# Require that the command class be derived from Command -- # Require that the command class be derived from Command --
# that way, we can be sure that we at least have the 'run' # that way, we can be sure that we at least have the 'run'
...@@ -226,8 +282,15 @@ class Distribution: ...@@ -226,8 +282,15 @@ class Distribution:
"command class %s must subclass Command" % \ "command class %s must subclass Command" % \
cmd_obj.__class__ cmd_obj.__class__
# XXX this assumes that cmd_obj provides an 'options' # Also make sure that the command object provides a list of its
# attribute, but we're not enforcing that anywhere! # known options
if not (hasattr (cmd_obj, 'options') and
type (cmd_obj.options) is ListType):
raise DistutilsClasserror, \
("command class %s must provide an 'options' attribute "+
"(a list of tuples)") % \
cmd_obj.__class__
args = fancy_getopt (cmd_obj.options, cmd_obj, args[1:]) args = fancy_getopt (cmd_obj.options, cmd_obj, args[1:])
self.command_obj[command] = cmd_obj self.command_obj[command] = cmd_obj
self.have_run[command] = 0 self.have_run[command] = 0
...@@ -376,6 +439,11 @@ class Distribution: ...@@ -376,6 +439,11 @@ class Distribution:
Then invoke 'run()' on that command object (or an existing Then invoke 'run()' on that command object (or an existing
one).""" one)."""
# XXX currently, this is the only place where we invoke a
# command object's 'run()' method -- so it might make sense to
# put the 'set_final_options()' call here, too, instead of
# requiring every command's 'run()' to call it first.
# Already been here, done that? then return silently. # Already been here, done that? then return silently.
if self.have_run.get (command): if self.have_run.get (command):
return return
...@@ -530,7 +598,7 @@ class Command: ...@@ -530,7 +598,7 @@ class Command:
except AttributeError: except AttributeError:
raise DistutilsOptionError, \ raise DistutilsOptionError, \
"command %s: no such option %s" % \ "command %s: no such option %s" % \
(self.command_name(), option) (self.get_command_name(), option)
def get_options (self, *options): def get_options (self, *options):
...@@ -545,7 +613,7 @@ class Command: ...@@ -545,7 +613,7 @@ class Command:
except AttributeError, name: except AttributeError, name:
raise DistutilsOptionError, \ raise DistutilsOptionError, \
"command %s: no such option %s" % \ "command %s: no such option %s" % \
(self.command_name(), name) (self.get_command_name(), name)
return tuple (values) return tuple (values)
...@@ -557,7 +625,7 @@ class Command: ...@@ -557,7 +625,7 @@ class Command:
if not hasattr (self, option): if not hasattr (self, option):
raise DistutilsOptionError, \ raise DistutilsOptionError, \
"command %s: no such option %s" % \ "command %s: no such option %s" % \
(self.command_name(), option) (self.get_command_name(), option)
if value is not None: if value is not None:
setattr (self, option, value) setattr (self, option, value)
...@@ -573,6 +641,20 @@ class Command: ...@@ -573,6 +641,20 @@ class Command:
# -- Convenience methods for commands ------------------------------ # -- Convenience methods for commands ------------------------------
def get_command_name (self):
if hasattr (self, 'command_name'):
return self.command_name
else:
class_name = self.__class__.__name__
# The re.split here returs empty strings delimited by the
# words we're actually interested in -- e.g. "FooBarBaz"
# splits to ['', 'Foo', '', 'Bar', '', 'Baz', '']. Hence
# the 'filter' to strip out the empties.
words = filter (None, re.split (r'([A-Z][a-z]+)', class_name))
return string.join (map (string.lower, words), "_")
def set_undefined_options (self, src_cmd, *option_pairs): def set_undefined_options (self, src_cmd, *option_pairs):
"""Set the values of any "undefined" options from corresponding """Set the values of any "undefined" options from corresponding
option values in some other command object. "Undefined" here option values in some other command object. "Undefined" here
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment