diff options
-rw-r--r-- | Lib/distutils/core.py | 134 |
1 files changed, 108 insertions, 26 deletions
diff --git a/Lib/distutils/core.py b/Lib/distutils/core.py index 2d5d066..8d2572a 100644 --- a/Lib/distutils/core.py +++ b/Lib/distutils/core.py @@ -72,9 +72,12 @@ def setup (**attrs): # (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. + # If we had a config file, this is where we would parse it: override + # the client-supplied command options, but be overridden by the + # command line. + + # 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: @@ -111,6 +114,18 @@ class Distribution: ('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 ------------------------------- @@ -129,11 +144,13 @@ class Distribution: 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) + # 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.url = None self.licence = None self.description = None @@ -143,18 +160,14 @@ class Distribution: # for the client to override command classes 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]) + # 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. + # XXX not needed anymore! (I think...) + #self.py_modules = None + #self.ext_modules = None + #self.package = None # And now initialize bookkeeping stuff that can't be supplied by # the caller at all. 'command_obj' maps command names to @@ -174,6 +187,49 @@ class Distribution: # '.get()' rather than a straight lookup. 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__ () @@ -213,10 +269,10 @@ class Distribution: 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) + # 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). + cmd_obj = self.find_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' @@ -226,8 +282,15 @@ class Distribution: "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! + # Also make sure that the command object provides a list of its + # 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:]) self.command_obj[command] = cmd_obj self.have_run[command] = 0 @@ -376,6 +439,11 @@ class Distribution: Then invoke 'run()' on that command object (or an existing 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. if self.have_run.get (command): return @@ -530,7 +598,7 @@ class Command: except AttributeError: raise DistutilsOptionError, \ "command %s: no such option %s" % \ - (self.command_name(), option) + (self.get_command_name(), option) def get_options (self, *options): @@ -545,7 +613,7 @@ class Command: except AttributeError, name: raise DistutilsOptionError, \ "command %s: no such option %s" % \ - (self.command_name(), name) + (self.get_command_name(), name) return tuple (values) @@ -557,7 +625,7 @@ class Command: if not hasattr (self, option): raise DistutilsOptionError, \ "command %s: no such option %s" % \ - (self.command_name(), option) + (self.get_command_name(), option) if value is not None: setattr (self, option, value) @@ -573,6 +641,20 @@ class Command: # -- 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): """Set the values of any "undefined" options from corresponding option values in some other command object. "Undefined" here |