diff -u -r --exclude __pycache__ distutils_orig/cmd.py distutils/cmd.py --- a/Lib/distutils/cmd.py 2014-03-15 23:04:41.000000000 -0700 +++ b/Lib/distutils/cmd.py 2014-08-24 15:12:17.000000000 -0700 @@ -5,6 +5,7 @@ """ import sys, os, re +from distutils.debug import DEBUG from distutils.errors import DistutilsOptionError from distutils import util, dir_util, file_util, archive_util, dep_util from distutils import log @@ -147,6 +148,60 @@ raise RuntimeError("abstract method -- subclass %s must override" % self.__class__) + def resolve_option_conflicts(self, side_a, side_b, msg=None): + """Given two containers of mutually exclusive option names, + attempt to resolve conflicts based on the priority of the source of the option. + + Options specified at the command-line unset those from config files, + rather than conflicting with them, etc. + + This should be called from finalize_options. + """ + + default_msg_t = "'{a}' and '{b}' are mutually exclusive options" + opt_dict = self.distribution.get_option_dict(self.get_command_name()) + priorities = self.distribution.option_source_priorities + for a in side_a: + a_value = getattr(self, a) + for b in side_b: + b_value = getattr(self, b) + if a_value and b_value: + if DEBUG: + self.announce( + "Resolving conflict between {a} and {b}.".format( + a=a, b=b, + ) + ) + if a in opt_dict and b in opt_dict: + source_a = opt_dict[a][0] + source_b = opt_dict[b][0] + prio_a = priorities[source_a] + prio_b = priorities[source_b] + else: + # an option didn't come from a known source, + # can't resolve priority + prio_a = prio_b = 0 + + if prio_a == prio_b: + raise DistutilsOptionError(msg or + default_msg_t.format(a=a, b=b) + ) + elif prio_a > prio_b: + if DEBUG: + self.announce( + "Unsetting {b} in favor of {a}.".format( + a=a, b=b, + ) + ) + setattr(self, b, None) + elif prio_b > prio_a: + if DEBUG: + self.announce( + "Unsetting {a} in favor of {b}.".format( + a=a, b=b, + ) + ) + setattr(self, a, None) def dump_options(self, header=None, indent=""): from distutils.fancy_getopt import longopt_xlate diff -u -r --exclude __pycache__ distutils_orig/command/install.py distutils/command/install.py --- a/Lib/distutils/command/install.py 2014-03-15 23:04:41.000000000 -0700 +++ b/Lib/distutils/command/install.py 2014-08-24 15:06:52.000000000 -0700 @@ -250,21 +250,26 @@ # Check for errors/inconsistencies in the options; first, stuff # that's wrong on any platform. - - if ((self.prefix or self.exec_prefix or self.home) and - (self.install_base or self.install_platbase)): - raise DistutilsOptionError( - "must supply either prefix/exec-prefix/home or " + - "install-base/install-platbase -- not both") - - if self.home and (self.prefix or self.exec_prefix): - raise DistutilsOptionError( - "must supply either home or prefix/exec-prefix -- not both") - - if self.user and (self.prefix or self.exec_prefix or self.home or - self.install_base or self.install_platbase): - raise DistutilsOptionError("can't combine user with prefix, " - "exec_prefix/home, or install_(plat)base") + + self.resolve_option_conflicts( + ('prefix', 'exec_prefix', 'home'), + ('install_base', 'install_platbase'), + msg="must supply either prefix/exec-prefix/home or " + + "install-base/install-platbase -- not both" + ) + + self.resolve_option_conflicts( + ('home',), + ('prefix', 'exec_prefix'), + msg="must supply either home or prefix/exec-prefix -- not both" + ) + + self.resolve_option_conflicts( + ('user',), + ('prefix', 'exec_prefix', 'home', 'install_base', 'install_platbase'), + msg="can't combine user with prefix, " + + "exec_prefix/home, or install_(plat)base" + ) # Next, stuff that's wrong (or dubious) only on certain platforms. if os.name != "posix": diff -u -r --exclude __pycache__ distutils_orig/dist.py distutils/dist.py --- a/Lib/distutils/dist.py 2014-03-15 23:04:41.000000000 -0700 +++ b/Lib/distutils/dist.py 2014-08-24 15:08:35.000000000 -0700 @@ -172,6 +172,12 @@ # instantiated. It is a dictionary of dictionaries of 2-tuples: # command_options = { command_name : { option : (source, value) } } self.command_options = {} + + # 'option_source_priorities' is where we store the order + # in which a source of options is loaded. + # This is used later for resolving conflicts between options + # from different sources. + self.option_source_priorities = {} # 'dist_files' is the list of (command, pyversion, file) that # have been created by any dist commands run so far. This is @@ -230,6 +236,7 @@ # through the general options dictionary. options = attrs.get('options') if options is not None: + self.option_source_priorities["setup script"] = len(self.option_source_priorities) del attrs['options'] for (command, cmd_options) in options.items(): opt_dict = self.get_option_dict(command) @@ -389,6 +396,7 @@ parser = ConfigParser() for filename in filenames: + self.option_source_priorities[filename] = len(self.option_source_priorities) if DEBUG: self.announce(" reading %s" % filename) parser.read(filename) @@ -588,6 +596,7 @@ # Put the options from the command-line into their official # holding pen, the 'command_options' dictionary. + self.option_source_priorities["command line"] = len(self.option_source_priorities) opt_dict = self.get_option_dict(command) for (name, value) in vars(opts).items(): opt_dict[name] = ("command line", value) @@ -845,6 +854,8 @@ klass = self.get_command_class(command) cmd_obj = self.command_obj[command] = klass(self) + # ensure command and distribution agree on command_name + cmd_obj.command_name = command self.have_run[command] = 0 # Set any options that were supplied in config files