Commits

Éric Araujo committed 9c73b1f Merge

Branch merge

Comments (0)

Files changed (17)

File contents unchanged.
File contents unchanged.

distutils2/command/__init__.py

 _COMMANDS = {
     'check': 'distutils2.command.check.check',
     'test': 'distutils2.command.test.test',
+    'configure': 'distutils2.command.configure.configure',
     'build': 'distutils2.command.build.build',
     'build_py': 'distutils2.command.build_py.build_py',
     'build_ext': 'distutils2.command.build_ext.build_ext',

distutils2/command/build.py

 
 Implements the Distutils 'build' command.
 """
-import sys
 import os
 
-from distutils2.util import get_platform
-from distutils2.command.cmd import Command
-from distutils2.errors import DistutilsOptionError
 from distutils2.compiler import show_compilers
+from distutils2.command.configure import configure
 
 
-class build(Command):
+class build(configure):
 
     description = "build everything needed to install"
 
-    user_options = [
-        ('build-base=', 'b',
-         "base directory for build library"),
-        ('build-purelib=', None,
-         "build directory for platform-neutral distributions"),
-        ('build-platlib=', None,
-         "build directory for platform-specific distributions"),
-        ('build-lib=', None,
-         "build directory for all distribution (defaults to either " +
-         "build-purelib or build-platlib"),
-        ('build-scripts=', None,
-         "build directory for scripts"),
-        ('build-temp=', 't',
-         "temporary build directory"),
-        ('plat-name=', 'p',
-         "platform name to build for, if supported "
-         "(default: %s)" % get_platform()),
-        ('compiler=', 'c',
-         "specify the compiler type"),
-        ('debug', 'g',
-         "compile extensions and libraries with debugging information"),
+    # All options are basically defined, initialized and finalized in
+    # configure, except for the ones that conflict with options from
+    # install (example: 'force', 'force_build' on configure).
+
+    user_options = configure.build_options + [
         ('force', 'f',
          "forcibly build everything (ignore file timestamps)"),
-        ('executable=', 'e',
-         "specify final destination interpreter path (build.py)"),
-        ('use-2to3', None,
-         "use 2to3 to make source python 3.x compatible"),
-        ('convert-2to3-doctests', None,
-         "use 2to3 to convert doctests in seperate text files"),
-        ('use-2to3-fixers', None,
-         "list additional fixers opted for during 2to3 conversion"),
         ]
 
-    boolean_options = ['debug', 'force']
+    boolean_options = configure.boolean_build_options + ['force']
+
+    negative_opt = configure.negative_build_opt.copy()
 
     help_options = [
         ('help-compiler', None,
         ]
 
     def initialize_options(self):
-        self.build_base = 'build'
-        # these are decided only after 'build_base' has its final value
-        # (unless overridden by the user or client)
-        self.build_purelib = None
-        self.build_platlib = None
-        self.build_lib = None
-        self.build_temp = None
-        self.build_scripts = None
-        self.compiler = None
-        self.plat_name = None
-        self.debug = None
+        configure.initialize_build_options(self)
         self.force = 0
-        self.executable = None
-        self.use_2to3 = False
-        self.convert_2to3_doctests = None
-        self.use_2to3_fixers = None
 
     def finalize_options(self):
-        if self.plat_name is None:
-            self.plat_name = get_platform()
-        else:
-            # plat-name only supported for windows (other platforms are
-            # supported via ./configure flags, if at all).  Avoid misleading
-            # other platforms.
-            if os.name != 'nt':
-                raise DistutilsOptionError(
-                            "--plat-name only supported on Windows (try "
-                            "using './configure --help' on your platform)")
-
-        plat_specifier = ".%s-%s" % (self.plat_name, sys.version[0:3])
-
-        # Make it so Python 2.x and Python 2.x with --with-pydebug don't
-        # share the same build directories. Doing so confuses the build
-        # process for C modules
-        if hasattr(sys, 'gettotalrefcount'):
-            plat_specifier += '-pydebug'
-
-        # 'build_purelib' and 'build_platlib' just default to 'lib' and
-        # 'lib.<plat>' under the base build directory.  We only use one of
-        # them for a given distribution, though --
-        if self.build_purelib is None:
-            self.build_purelib = os.path.join(self.build_base, 'lib')
-        if self.build_platlib is None:
-            self.build_platlib = os.path.join(self.build_base,
-                                              'lib' + plat_specifier)
-
-        # 'build_lib' is the actual directory that we will use for this
-        # particular module distribution -- if user didn't supply it, pick
-        # one of 'build_purelib' or 'build_platlib'.
-        if self.build_lib is None:
-            if self.distribution.ext_modules:
-                self.build_lib = self.build_platlib
-            else:
-                self.build_lib = self.build_purelib
-
-        # 'build_temp' -- temporary directory for compiler turds,
-        # "build/temp.<plat>"
-        if self.build_temp is None:
-            self.build_temp = os.path.join(self.build_base,
-                                           'temp' + plat_specifier)
-        if self.build_scripts is None:
-            self.build_scripts = os.path.join(self.build_base,
-                                              'scripts-' + sys.version[0:3])
-
-        if self.executable is None:
-            self.executable = os.path.normpath(sys.executable)
+        self.set_undefined_options('configure',
+            # Getting a list of acceptable options is easy.  Not.
+            *(option[0].rstrip('=').replace('-', '_')
+              for option in self.build_options
+              if option[0] not in self.negative_opt))
+        self.set_undefined_options('configure', ('force_build', 'force'))
+        # 'plat_name' is set in during finalization of configure's options,
+        # but when finalizing build options, an error will be raised if this
+        # attribute is not None on non-NT systems.  Thus, reset it.
+        if os.name != 'nt':
+            self.plat_name = None
+        configure.finalize_build_options(self)
 
     def run(self):
         # Run all relevant sub-commands.  This will be some subset of:

distutils2/command/build_ext.py

File contents unchanged.

distutils2/command/configure.py

+# encoding: utf-8
+"""Command to set build and install options and cache them.
+
+The configure class defined in this module serves two different
+purposes.  It is first a command that implements options checking and
+caching.  To avoid code duplication, it is also the base class for
+build and install_dist, which reuse methods from configure to check
+their own options.  This means that build and install_dist run configure
+as a command, to get options via
+"self.set_undefined_options('configure')".  Thus, both of these usages
+work as expected:
+
+    # Give options to configure and have build and install use them
+    # on the same command line (also save them)
+    python setup.py configure --build-base $TMP/build install
+
+    # Save options and use them automatically later
+    python setup.py configure --build-base $TMP/build
+    python setup.py build
+    python setup.py install
+"""
+
+import os
+import sys
+from distutils2 import logger
+from distutils2._backport import sysconfig
+from distutils2._backport.sysconfig import (get_config_var, get_config_vars,
+                                            get_path, get_paths)
+from distutils2.command.cmd import Command
+from distutils2.errors import DistutilsOptionError, DistutilsPlatformError
+from distutils2.util import convert_path, change_root, get_platform
+from distutils2.compiler import show_compilers
+
+__all__ = ['configure']
+__author__ = 'Éric Araujo'
+__credits__ = ('Jeremy Kloth (config command in 4Suite), distutils '
+               'contributors (code moved from build and install)')
+
+# compatibility with 2.4 and 2.5
+if sys.version < '2.6':
+    HAS_USER_SITE = False
+else:
+    HAS_USER_SITE = True
+
+
+class configure(Command):
+
+    description = 'set options for build and install and cache them'
+
+    build_options = [
+        # Directories to build things in
+        ('build-base=', 'b',
+         "base directory for build library"),
+        ('build-purelib=', None,
+         "build directory for platform-neutral distributions"),
+        ('build-platlib=', None,
+         "build directory for platform-specific distributions"),
+        ('build-lib=', None,
+         "build directory for all distribution (defaults to either " +
+         "build-purelib or build-platlib"),
+        ('build-scripts=', None,
+         "build directory for scripts"),
+        ('build-temp=', 't',
+         "temporary build directory"),
+
+        # Other options
+        ('plat-name=', 'p',
+         "platform name to build for, if supported "
+         "(default: %s)" % get_platform()),
+        ('compiler=', 'c',
+         "specify the compiler type"),
+        ('debug', 'g',
+         "compile extensions and libraries with debugging information"),
+        ('executable=', 'e',
+         "specify final destination interpreter path (build.py)"),
+        ('use-2to3', None,
+         "use 2to3 to make source python 3.x compatible"),
+        ('convert-2to3-doctests', None,
+         "use 2to3 to convert doctests in seperate text files"),
+        ('use-2to3-fixers', None,
+         "list additional fixers opted for during 2to3 conversion"),
+        ]
+
+    install_options = [
+        # Select installation scheme and set base director(y|ies)
+        ('prefix=', None,
+         "installation prefix"),
+        ('exec-prefix=', None,
+         "(Unix only) prefix for platform-specific files"),
+        ('home=', None,
+         "(Unix only) home directory to install under"),
+
+        # Or just set the base director(y|ies)
+        ('install-base=', None,
+         "base installation directory (instead of --prefix or --home)"),
+        ('install-platbase=', None,
+         "base installation directory for platform-specific files " +
+         "(instead of --exec-prefix or --home)"),
+        ('root=', None,
+         "install everything relative to this alternate root directory"),
+
+        # Or explicitly set the installation scheme
+        ('install-purelib=', None,
+         "installation directory for pure Python module distributions"),
+        ('install-platlib=', None,
+         "installation directory for non-pure module distributions"),
+        ('install-lib=', None,
+         "installation directory for all module distributions " +
+         "(overrides --install-purelib and --install-platlib)"),
+
+        ('install-headers=', None,
+         "installation directory for C/C++ headers"),
+        ('install-scripts=', None,
+         "installation directory for Python scripts"),
+        ('install-data=', None,
+         "installation directory for data files"),
+
+        # Byte-compilation options -- see install_lib.py for details, as
+        # these are duplicated from there (but only install_lib does
+        # anything with them).
+        ('compile', 'c', "compile .py to .pyc [default]"),
+        ('no-compile', None, "don't compile .py files"),
+        ('optimize=', 'O',
+         'also compile with optimization: -O1 for "python -O", '
+         '-O2 for "python -OO", and -O0 to disable [default: -O0]'),
+
+        # Miscellaneous control options
+        # force is left in install because of name conflict w/ build
+        ('skip-build', None,
+         "skip rebuilding everything (for testing/debugging)"),
+
+        # XXX use a name that makes clear this is the old format
+        ('record=', None,
+         "filename in which to record a list of installed files "
+         "(not PEP 376-compliant)"),
+
+        # .dist-info related arguments, read by install_dist_info
+        ('no-distinfo', None,
+         "do not create a .dist-info directory"),
+        ('installer=', None,
+         "the name of the installer"),
+        ('requested', None,
+         "generate a REQUESTED file (i.e."),
+        ('no-requested', None,
+         "do not generate a REQUESTED file"),
+        ('no-record', None,
+         "do not generate a RECORD file"),
+        ]
+
+    # Boolean options with the same name on build and install but
+    # different behavior are given unambiguous names on configure and
+    # un-aliased in build and install.  See 'force_build' and
+    # 'force_install' for an example.
+    boolean_build_options = ['debug']
+    boolean_install_options = ['compile', 'skip-build', 'no-distinfo',
+                               'requested', 'no-record']
+
+    # If there is ever a duplicate between options in those dicts, bad
+    # things should not happen in the merged dict a couple lignes later
+    # as long as the negative options are the same.
+    negative_build_opt = {}
+    negative_install_opt = {'no-compile': 'compile',
+                            'no-requested': 'requested'}
+
+    if HAS_USER_SITE:
+        install_options.append(
+            ('user', None,
+             "install in user site-packages directory [%s]" %
+             get_path('purelib', '%s_user' % os.name)))
+
+        boolean_install_options.append('user')
+
+    user_options = build_options + install_options + [
+        ('update', None,
+         "update cache file instead of overwriting it"),
+        ('force-build', None,
+         "forcibly build everything (ignore file timestamps)"),
+        ('force-install', None,
+         "force installation (overwrite any existing files)"),
+        ]
+
+    boolean_options = boolean_build_options + boolean_install_options + [
+        'update', 'force-build', 'force-install']
+
+    negative_opt = negative_build_opt.copy()
+    negative_opt.update(negative_install_opt)
+
+    help_options = [
+        ('help-compiler', None,
+         "list available compilers", show_compilers),
+        ]
+
+    # Sentinel value to prevent double-checking of option conflicts
+    # (options set in install from configure would cause an error in
+    # finalize_install_options)
+    _from_configure = False
+
+    # -- Proper configure methods -------------------------------------
+
+    def initialize_options(self):
+        self.initialize_build_options()
+        self.initialize_install_options()
+        # see method initialize_options of each class for the meaning
+        # of these fields
+        self.force_build = 0
+        self.force_install = 0
+        # merge options from the cache file instead of overwriting
+        self.update = False
+
+    def finalize_options(self):
+        self.finalize_build_options()
+        self.finalize_install_options()
+
+    def run(self):
+        # Write the configure.cache file
+
+        # XXX is there an easier way to get all options?
+        options = self.distribution.get_option_dict('configure')
+
+        # filter out options not coming from the command line
+        # (or from the cache file too in case of --update)
+        if self.update:
+            test = ('configure.cache', 'command line').__contains__
+        else:
+            test = 'command line'.__eq__
+
+        options = dict((option, value)
+                       for option, (source, value) in options.iteritems()
+                       if test(source))
+
+        if not options:
+            logger.debug('configure: nothing to write')
+            return
+
+        logger.debug('configure: writing to "configure.cache"')
+        if not self.dry_run:
+            #print os.path.abspath('configure.cache')
+            fp = open('configure.cache', 'w')
+            try:
+                print >> fp, '# File generated by the configure command;'
+                print >> fp, '# do NOT edit, it will be overwritten.'
+                print >> fp, '[configure]'
+                # TODO sort according to order in self.user_options
+                for line in sorted(options.iteritems()):
+                    print >> fp, '%s = %s' % line
+            finally:
+                fp.close()
+
+    # -- Initialize worker methods -------------------------------------
+    # XXX They could be private methods or private module-level functions,
+    # don't know if that would be better.
+
+    def initialize_build_options(self):
+        self.build_base = None
+        # these are decided only after 'build_base' has its final value
+        # (unless overridden by the user or client)
+        self.build_purelib = None
+        self.build_platlib = None
+        self.build_lib = None
+        self.build_temp = None
+        self.build_scripts = None
+        self.compiler = None
+        self.plat_name = None
+        self.debug = None
+        self.executable = None
+        self.use_2to3 = False
+        self.convert_2to3_doctests = None
+        self.use_2to3_fixers = None
+
+    def initialize_install_options(self):
+        # High-level options: these select both an installation base
+        # and scheme.
+        self.prefix = None
+        self.exec_prefix = None
+        self.home = None
+        if HAS_USER_SITE:
+            self.user = 0
+
+        # These select only the installation base; it's up to the user to
+        # specify the installation scheme (currently, that means supplying
+        # the --install-{platlib,purelib,scripts,data} options).
+        self.install_base = None
+        self.install_platbase = None
+        self.root = None
+
+        # These options are the actual installation directories; if not
+        # supplied by the user, they are filled in using the installation
+        # scheme implied by prefix/exec-prefix/home and the contents of
+        # that installation scheme.
+        self.install_purelib = None     # for pure module distributions
+        self.install_platlib = None     # non-pure (dists w/ extensions)
+        self.install_headers = None     # for C/C++ headers
+        self.install_lib = None         # set to either purelib or platlib
+        self.install_scripts = None
+        self.install_data = None
+        if HAS_USER_SITE:
+            self.install_userbase = None
+            self.install_usersite = None
+
+        self.compile = None
+        self.optimize = None
+
+        # These two are for putting non-packagized distributions into their
+        # own directory and creating a .pth file if it makes sense.
+        # 'extra_path' comes from the setup file; 'install_path_file' can
+        # be turned off if it makes no sense to install a .pth file.  (But
+        # better to install it uselessly than to guess wrong and not
+        # install it when it's necessary and would be used!)  Currently,
+        # 'install_path_file' is always true unless some outsider meddles
+        # with it.
+        self.extra_path = None
+        self.install_path_file = 1
+
+        # 'skip_build' skips running the "build" command,
+        # handy if you know it's not necessary.
+        self.skip_build = 0
+        self.record = None
+
+        # .dist-info related options
+        self.no_distinfo = None
+        self.installer = None
+        self.requested = None
+        self.no_record = None
+
+    # -- Finalize worker methods -------------------------------------
+
+    def finalize_build_options(self):
+        if self.plat_name is None:
+            self.plat_name = get_platform()
+        else:
+            # plat-name only supported for windows (other platforms are
+            # supported via ./configure flags, if at all).  Avoid misleading
+            # other platforms.
+            if os.name != 'nt':
+                raise DistutilsOptionError(
+                    "--plat-name only supported on Windows (try "
+                    "using './configure --help' on your platform)")
+
+        plat_specifier = ".%s-%s" % (self.plat_name, sys.version[0:3])
+
+        # Make it so Python 2.x and Python 2.x with --with-pydebug don't
+        # share the same build directories. Doing so confuses the build
+        # process for C modules
+        if hasattr(sys, 'gettotalrefcount'):
+            plat_specifier += '-pydebug'
+
+        # 'build_base' can be used to control the root directory of all
+        # other 'build_*' options
+        if self.build_base is None:
+            self.build_base = 'build'
+
+        # 'build_purelib' and 'build_platlib' just default to 'lib' and
+        # 'lib.<plat>' under the base build directory.  We only use one of
+        # them for a given distribution, though --
+        if self.build_purelib is None:
+            self.build_purelib = os.path.join(self.build_base, 'lib')
+        if self.build_platlib is None:
+            self.build_platlib = os.path.join(self.build_base,
+                                              'lib' + plat_specifier)
+
+        # 'build_lib' is the actual directory that we will use for this
+        # particular module distribution -- if user didn't supply it, pick
+        # one of 'build_purelib' or 'build_platlib'.
+        if self.build_lib is None:
+            if self.distribution.ext_modules:
+                self.build_lib = self.build_platlib
+            else:
+                self.build_lib = self.build_purelib
+
+        # 'build_temp' -- temporary directory for compiler turds,
+        # "build/temp.<plat>"
+        if self.build_temp is None:
+            self.build_temp = os.path.join(self.build_base,
+                                           'temp' + plat_specifier)
+        if self.build_scripts is None:
+            self.build_scripts = os.path.join(self.build_base,
+                                              'scripts-' + sys.version[0:3])
+
+        if self.executable is None:
+            self.executable = os.path.normpath(sys.executable)
+
+    # (This is rather more involved than for most commands,
+    # because this is where the policy for installing third-
+    # party Python modules on various platforms given a wide
+    # array of user input is decided.  Yes, it's quite complex!)
+
+    def finalize_install_options(self):
+        # This method (and its pliant slaves, like '_finalize_install_unix',
+        # and '_select_install_scheme') is where the default
+        # installation directories for modules, extension modules, and
+        # anything else we care to install from a Python module
+        # distribution.  Thus, this code makes a pretty important policy
+        # statement about how third-party stuff is added to a Python
+        # installation!  Note that the actual work of installation is done
+        # by the relatively simple 'install_*' commands; they just take
+        # their orders from the installation directory options determined
+        # here.
+
+        # 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) and
+            not self._from_configure):
+            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) and
+            not self._from_configure):
+            raise DistutilsOptionError(
+                "must supply either home or prefix/exec-prefix -- not both")
+
+        if HAS_USER_SITE and (self.user and (
+            self.prefix or self.exec_prefix or self.home or
+            self.install_base or self.install_platbase) and
+            not self._from_configure):
+
+            raise DistutilsOptionError(
+                "can't combine user with prefix/exec_prefix/home or "
+                "install_base/install_platbase")
+
+        # Next, stuff that's wrong (or dubious) only on certain platforms.
+        if os.name != "posix":
+            if self.exec_prefix:
+                self.warn("exec-prefix option ignored on this platform")
+                self.exec_prefix = None
+
+        # Set default values for user site-packages scheme
+        if HAS_USER_SITE:
+            if self.install_userbase is None:
+                self.install_userbase = get_config_var('userbase')
+            if self.install_usersite is None:
+                self.install_usersite = get_path('purelib',
+                                                 '%s_user' % os.name)
+
+        # Now the interesting logic -- so interesting that we farm it out
+        # to other methods.  The goal of these methods is to set the final
+        # values for the install_{lib,scripts,data,...}  options, using as
+        # input a heady brew of prefix, exec_prefix, home, install_base,
+        # install_platbase, user-supplied versions of
+        # install_{purelib,platlib,lib,scripts,data,...}, and the
+        # INSTALL_SCHEME dictionary above.  Phew!
+
+        self._dump_install_dirs("pre-finalize_{unix,other}")
+
+        if os.name == 'posix':
+            self._finalize_install_unix()
+        else:
+            self._finalize_install_other()
+
+        self._dump_install_dirs("post-finalize_{unix,other}()")
+
+        # Expand configuration variables, tilde, etc. in self.install_base
+        # and self.install_platbase -- that way, we can use $base or
+        # $platbase in the other installation directories and not worry
+        # about needing recursive variable expansion (shudder).
+
+        py_version = sys.version.split()[0]
+        prefix, exec_prefix, srcdir, projectbase = get_config_vars(
+            'prefix', 'exec_prefix', 'srcdir', 'projectbase')
+
+        metadata = self.distribution.metadata
+        self.config_vars = {
+            'dist_name': metadata['Name'],
+            'dist_version': metadata['Version'],
+            'dist_fullname': metadata.get_fullname(),
+            'py_version': py_version,
+            'py_version_short': py_version[:3],
+            'py_version_nodot': py_version[:3:2],
+            'sys_prefix': prefix,
+            'prefix': prefix,
+            'sys_exec_prefix': exec_prefix,
+            'exec_prefix': exec_prefix,
+            'srcdir': srcdir,
+            'projectbase': projectbase,
+            }
+
+        if HAS_USER_SITE:
+            self.config_vars['userbase'] = self.install_userbase
+            self.config_vars['usersite'] = self.install_usersite
+
+        self._expand_install_basedirs()
+
+        self._dump_install_dirs("post-expand_basedirs()")
+
+        # Now define config vars for the base directories so we can expand
+        # everything else.
+        self.config_vars['base'] = self.install_base
+        self.config_vars['platbase'] = self.install_platbase
+
+        # Expand "~" and configuration variables in the installation
+        # directories.
+        self._expand_install_dirs()
+
+        self._dump_install_dirs("post-expand_dirs()")
+
+        # Create directories in the home dir:
+        if HAS_USER_SITE and self.user:
+            self._create_home_path()
+
+        # Pick the actual directory to install all modules to: either
+        # install_purelib or install_platlib, depending on whether this
+        # module distribution is pure or not.  Of course, if the user
+        # already specified install_lib, use their selection.
+        if self.install_lib is None:
+            if self.distribution.ext_modules:  # has extensions: non-pure
+                self.install_lib = self.install_platlib
+            else:
+                self.install_lib = self.install_purelib
+
+        # Convert directories from Unix /-separated syntax to the local
+        # convention.
+        self._convert_install_paths('lib', 'purelib', 'platlib',
+                                    'scripts', 'data', 'headers')
+        if HAS_USER_SITE:
+            self._convert_install_paths('userbase', 'usersite')
+
+        # Well, we're not actually fully completely finalized yet: we still
+        # have to deal with 'extra_path', which is the hack for allowing
+        # non-packagized module distributions (hello, Numerical Python!) to
+        # get their own directories.
+        self._handle_extra_install_path()
+        self.install_libbase = self.install_lib  # needed for .pth file
+        self.install_lib = os.path.join(self.install_lib, self.extra_dirs)
+
+        # If a new root directory was supplied, make all the installation
+        # dirs relative to it.
+        if self.root is not None:
+            self._change_install_roots('libbase', 'lib', 'purelib', 'platlib',
+                              'scripts', 'data', 'headers')
+
+        self._dump_install_dirs("after prepending root")
+
+        if self.no_distinfo is None:
+            self.no_distinfo = False
+
+    # -- Helper methods for install -------------------------------------
+
+    def _finalize_install_unix(self):
+        """Finalize options for posix platforms."""
+        if self.install_base is not None or self.install_platbase is not None:
+            if ((self.install_lib is None and
+                 self.install_purelib is None and
+                 self.install_platlib is None) or
+                self.install_headers is None or
+                self.install_scripts is None or
+                self.install_data is None):
+                raise DistutilsOptionError(
+                    "install-base or install-platbase supplied, but "
+                    "installation scheme is incomplete")
+            return
+
+        if HAS_USER_SITE and self.user:
+            if self.install_userbase is None:
+                raise DistutilsPlatformError(
+                    "user base directory is not specified")
+            self.install_base = self.install_platbase = self.install_userbase
+            self._select_install_scheme("posix_user")
+        elif self.home is not None:
+            self.install_base = self.install_platbase = self.home
+            self._select_install_scheme("posix_home")
+        else:
+            if self.prefix is None:
+                if self.exec_prefix is not None:
+                    raise DistutilsOptionError(
+                        "must not supply exec-prefix without prefix")
+
+                self.prefix = os.path.normpath(sys.prefix)
+                self.exec_prefix = os.path.normpath(sys.exec_prefix)
+
+            else:
+                if self.exec_prefix is None:
+                    self.exec_prefix = self.prefix
+
+            self.install_base = self.prefix
+            self.install_platbase = self.exec_prefix
+            self._select_install_scheme("posix_prefix")
+
+    def _finalize_install_other(self):
+        """Finalize options for non-posix platforms"""
+        if HAS_USER_SITE and self.user:
+            if self.install_userbase is None:
+                raise DistutilsPlatformError(
+                    "user base directory is not specified")
+            self.install_base = self.install_platbase = self.install_userbase
+            self._select_install_scheme(os.name + "_user")
+        elif self.home is not None:
+            self.install_base = self.install_platbase = self.home
+            self._select_install_scheme("posix_home")
+        else:
+            if self.prefix is None:
+                self.prefix = os.path.normpath(sys.prefix)
+
+            self.install_base = self.install_platbase = self.prefix
+            try:
+                self._select_install_scheme(os.name)
+            except KeyError:
+                raise DistutilsPlatformError(
+                    "no support for installation on '%s'" % os.name)
+
+    def _dump_install_dirs(self, msg):
+        """Dump the list of user options."""
+        logger.debug(msg + ":")
+        for opt in self.user_options:
+            opt_name = opt[0]
+            if opt_name[-1] == "=":
+                opt_name = opt_name[0:-1]
+            if opt_name in self.negative_opt:
+                opt_name = self.negative_opt[opt_name]
+                opt_name = opt_name.replace('-', '_')
+                val = not getattr(self, opt_name)
+            else:
+                opt_name = opt_name.replace('-', '_')
+                val = getattr(self, opt_name)
+            logger.debug("  %s: %s" % (opt_name, val))
+
+    def _select_install_scheme(self, name):
+        """Set the install directories by applying the install schemes."""
+        # it's the caller's problem if they supply a bad name!
+        scheme = get_paths(name, expand=False)
+        for key, value in scheme.items():
+            if key == 'platinclude':
+                key = 'headers'
+                value = os.path.join(value, self.distribution.metadata['Name'])
+            attrname = 'install_' + key
+            if hasattr(self, attrname):
+                if getattr(self, attrname) is None:
+                    setattr(self, attrname, value)
+
+    def _expand_attrs(self, attrs):
+        for attr in attrs:
+            val = getattr(self, attr)
+            if val is not None:
+                if os.name == 'posix' or os.name == 'nt':
+                    val = os.path.expanduser(val)
+                # see if we want to push this work in sysconfig XXX
+                val = sysconfig._subst_vars(val, self.config_vars)
+                setattr(self, attr, val)
+
+    def _expand_install_basedirs(self):
+        """Call `os.path.expanduser` on install_{base,platbase} and root."""
+        self._expand_attrs(['install_base', 'install_platbase', 'root'])
+
+    def _expand_install_dirs(self):
+        """Call `os.path.expanduser` on install dirs."""
+        self._expand_attrs(['install_purelib', 'install_platlib',
+                            'install_lib', 'install_headers',
+                            'install_scripts', 'install_data'])
+
+    def _convert_install_paths(self, *names):
+        """Call `convert_path` over `names`."""
+        for name in names:
+            attr = "install_" + name
+            setattr(self, attr, convert_path(getattr(self, attr)))
+
+    def _handle_extra_install_path(self):
+        """Set `path_file` and `extra_dirs` using `extra_path`."""
+        if self.extra_path is None:
+            self.extra_path = self.distribution.extra_path
+
+        if self.extra_path is not None:
+            if isinstance(self.extra_path, str):
+                self.extra_path = self.extra_path.split(',')
+
+            if len(self.extra_path) == 1:
+                path_file = extra_dirs = self.extra_path[0]
+            elif len(self.extra_path) == 2:
+                path_file, extra_dirs = self.extra_path
+            else:
+                raise DistutilsOptionError(
+                    "'extra_path' option must be a list, tuple, or "
+                    "comma-separated string with 1 or 2 elements")
+
+            # convert to local form in case Unix notation used (as it
+            # should be in setup scripts)
+            extra_dirs = convert_path(extra_dirs)
+        else:
+            path_file = None
+            extra_dirs = ''
+
+        # XXX should we warn if path_file and not extra_dirs? (in which
+        # case the path file would be harmless but pointless)
+        self.path_file = path_file
+        self.extra_dirs = extra_dirs
+
+    def _change_install_roots(self, *names):
+        """Change the install direcories pointed by name using root."""
+        for name in names:
+            attr = "install_" + name
+            setattr(self, attr, change_root(self.root, getattr(self, attr)))
+
+    def _create_home_path(self):
+        """Create directories under ~."""
+        if HAS_USER_SITE and not self.user:
+            return
+        home = convert_path(os.path.expanduser("~"))
+        for name, path in self.config_vars.iteritems():
+            if path.startswith(home) and not os.path.isdir(path):
+                os.makedirs(path, 0700)

distutils2/command/install_dist.py

-"""distutils.command.install
+"""distutils.command.install_dist
 
 Implements the Distutils 'install_dist' command."""
 
-
+import os
 import sys
-import os
-
-from distutils2._backport import sysconfig
-from distutils2._backport.sysconfig import (get_config_vars, get_paths,
-                                            get_path, get_config_var)
 
 from distutils2 import logger
-from distutils2.command.cmd import Command
+from distutils2.command.configure import configure
 from distutils2.errors import DistutilsPlatformError
-from distutils2.util import write_file
-from distutils2.util import convert_path, change_root, get_platform
-from distutils2.errors import DistutilsOptionError
+from distutils2.util import write_file, get_platform
 
-# compatibility with 2.4 and 2.5
-if sys.version < '2.6':
-    HAS_USER_SITE = False
-else:
-    HAS_USER_SITE = True
 
-
-class install_dist(Command):
+class install_dist(configure):
 
     description = "install everything from build directory"
 
-    user_options = [
-        # Select installation scheme and set base director(y|ies)
-        ('prefix=', None,
-         "installation prefix"),
-        ('exec-prefix=', None,
-         "(Unix only) prefix for platform-specific files"),
-        ('home=', None,
-         "(Unix only) home directory to install under"),
+    # All options are basically defined, initialized and finalized in
+    # configure, except for the ones that conflict with options from
+    # build (example: 'force', 'force_install' on configure).
 
-        # Or just set the base director(y|ies)
-        ('install-base=', None,
-         "base installation directory (instead of --prefix or --home)"),
-        ('install-platbase=', None,
-         "base installation directory for platform-specific files " +
-         "(instead of --exec-prefix or --home)"),
-        ('root=', None,
-         "install everything relative to this alternate root directory"),
-
-        # Or explicitly set the installation scheme
-        ('install-purelib=', None,
-         "installation directory for pure Python module distributions"),
-        ('install-platlib=', None,
-         "installation directory for non-pure module distributions"),
-        ('install-lib=', None,
-         "installation directory for all module distributions " +
-         "(overrides --install-purelib and --install-platlib)"),
-
-        ('install-headers=', None,
-         "installation directory for C/C++ headers"),
-        ('install-scripts=', None,
-         "installation directory for Python scripts"),
-        ('install-data=', None,
-         "installation directory for data files"),
-
-        # Byte-compilation options -- see install_lib.py for details, as
-        # these are duplicated from there (but only install_lib does
-        # anything with them).
-        ('compile', 'c', "compile .py to .pyc [default]"),
-        ('no-compile', None, "don't compile .py files"),
-        ('optimize=', 'O',
-         'also compile with optimization: -O1 for "python -O", '
-         '-O2 for "python -OO", and -O0 to disable [default: -O0]'),
-
-        # Miscellaneous control options
+    user_options = configure.install_options + [
         ('force', 'f',
          "force installation (overwrite any existing files)"),
-        ('skip-build', None,
-         "skip rebuilding everything (for testing/debugging)"),
-
-        # Where to install documentation (eventually!)
-        #('doc-format=', None, "format of documentation to generate"),
-        #('install-man=', None, "directory for Unix man pages"),
-        #('install-html=', None, "directory for HTML documentation"),
-        #('install-info=', None, "directory for GNU info files"),
-
-        # XXX use a name that makes clear this is the old format
-        ('record=', None,
-         "filename in which to record a list of installed files "
-         "(not PEP 376-compliant)"),
-
-        # .dist-info related arguments, read by install_dist_info
-        ('no-distinfo', None,
-         "do not create a .dist-info directory"),
-        ('installer=', None,
-         "the name of the installer"),
-        ('requested', None,
-         "generate a REQUESTED file (i.e."),
-        ('no-requested', None,
-         "do not generate a REQUESTED file"),
-        ('no-record', None,
-         "do not generate a RECORD file"),
         ]
 
-    boolean_options = ['compile', 'force', 'skip-build', 'no-distinfo',
-                       'requested', 'no-record']
+    boolean_options = configure.boolean_install_options + ['force']
 
-    if HAS_USER_SITE:
-        user_options.append(
-            ('user', None,
-             "install in user site-packages directory [%s]" %
-             get_path('purelib', '%s_user' % os.name)))
-
-        boolean_options.append('user')
-
-    negative_opt = {'no-compile': 'compile', 'no-requested': 'requested'}
+    negative_opt = configure.negative_install_opt.copy()
 
     def initialize_options(self):
-        # High-level options: these select both an installation base
-        # and scheme.
-        self.prefix = None
-        self.exec_prefix = None
-        self.home = None
-        if HAS_USER_SITE:
-            self.user = 0
-
-        # These select only the installation base; it's up to the user to
-        # specify the installation scheme (currently, that means supplying
-        # the --install-{platlib,purelib,scripts,data} options).
-        self.install_base = None
-        self.install_platbase = None
-        self.root = None
-
-        # These options are the actual installation directories; if not
-        # supplied by the user, they are filled in using the installation
-        # scheme implied by prefix/exec-prefix/home and the contents of
-        # that installation scheme.
-        self.install_purelib = None     # for pure module distributions
-        self.install_platlib = None     # non-pure (dists w/ extensions)
-        self.install_headers = None     # for C/C++ headers
-        self.install_lib = None         # set to either purelib or platlib
-        self.install_scripts = None
-        self.install_data = None
-        if HAS_USER_SITE:
-            self.install_userbase = get_config_var('userbase')
-            self.install_usersite = get_path('purelib', '%s_user' % os.name)
-
-        self.compile = None
-        self.optimize = None
-
-        # These two are for putting non-packagized distributions into their
-        # own directory and creating a .pth file if it makes sense.
-        # 'extra_path' comes from the setup file; 'install_path_file' can
-        # be turned off if it makes no sense to install a .pth file.  (But
-        # better to install it uselessly than to guess wrong and not
-        # install it when it's necessary and would be used!)  Currently,
-        # 'install_path_file' is always true unless some outsider meddles
-        # with it.
-        self.extra_path = None
-        self.install_path_file = 1
-
-        # 'force' forces installation, even if target files are not
-        # out-of-date.  'skip_build' skips running the "build" command,
-        # handy if you know it's not necessary.  'warn_dir' (which is *not*
-        # a user option, it's just there so the bdist_* commands can turn
-        # it off) determines whether we warn about installing to a
-        # directory not in sys.path.
-        self.force = 0
-        self.skip_build = 0
-        self.warn_dir = 1
-
+        configure.initialize_install_options(self)
         # These are only here as a conduit from the 'build' command to the
         # 'install_*' commands that do the real work.  ('build_base' isn't
         # actually used anywhere, but it might be useful in future.)  They
         # build command.
         self.build_base = None
         self.build_lib = None
-
-        # Not defined yet because we don't know anything about
-        # documentation yet.
-        #self.install_man = None
-        #self.install_html = None
-        #self.install_info = None
-
-        self.record = None
-
-        # .dist-info related options
-        self.no_distinfo = None
-        self.installer = None
-        self.requested = None
-        self.no_record = None
-
-    # -- Option finalizing methods -------------------------------------
-    # (This is rather more involved than for most commands,
-    # because this is where the policy for installing third-
-    # party Python modules on various platforms given a wide
-    # array of user input is decided.  Yes, it's quite complex!)
+        # 'force' forces installation, even if target files are not
+        # out-of-date. 'warn_dir' determines whether we warn about
+        # installing to a directory not in sys.path; it is *not* a user
+        # option, it's just there so the bdist_* commands can turn it off
+        self.force = 0
+        self.warn_dir = 1
 
     def finalize_options(self):
-        # This method (and its pliant slaves, like 'finalize_unix()',
-        # 'finalize_other()', and 'select_scheme()') is where the default
-        # installation directories for modules, extension modules, and
-        # anything else we care to install from a Python module
-        # distribution.  Thus, this code makes a pretty important policy
-        # statement about how third-party stuff is added to a Python
-        # installation!  Note that the actual work of installation is done
-        # by the relatively simple 'install_*' commands; they just take
-        # their orders from the installation directory options determined
-        # here.
-
-        # 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 HAS_USER_SITE and 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_base/install_platbase")
-
-        # Next, stuff that's wrong (or dubious) only on certain platforms.
-        if os.name != "posix":
-            if self.exec_prefix:
-                self.warn("exec-prefix option ignored on this platform")
-                self.exec_prefix = None
-
-        # Now the interesting logic -- so interesting that we farm it out
-        # to other methods.  The goal of these methods is to set the final
-        # values for the install_{lib,scripts,data,...}  options, using as
-        # input a heady brew of prefix, exec_prefix, home, install_base,
-        # install_platbase, user-supplied versions of
-        # install_{purelib,platlib,lib,scripts,data,...}, and the
-        # INSTALL_SCHEME dictionary above.  Phew!
-
-        self.dump_dirs("pre-finalize_{unix,other}")
-
-        if os.name == 'posix':
-            self.finalize_unix()
-        else:
-            self.finalize_other()
-
-        self.dump_dirs("post-finalize_{unix,other}()")
-
-        # Expand configuration variables, tilde, etc. in self.install_base
-        # and self.install_platbase -- that way, we can use $base or
-        # $platbase in the other installation directories and not worry
-        # about needing recursive variable expansion (shudder).
-
-        py_version = sys.version.split()[0]
-        prefix, exec_prefix, srcdir, projectbase = get_config_vars(
-            'prefix', 'exec_prefix', 'srcdir', 'projectbase')
-
-        metadata = self.distribution.metadata
-        self.config_vars = {
-            'dist_name': metadata['Name'],
-            'dist_version': metadata['Version'],
-            'dist_fullname': metadata.get_fullname(),
-            'py_version': py_version,
-            'py_version_short': py_version[:3],
-            'py_version_nodot': py_version[:3:2],
-            'sys_prefix': prefix,
-            'prefix': prefix,
-            'sys_exec_prefix': exec_prefix,
-            'exec_prefix': exec_prefix,
-            'srcdir': srcdir,
-            'projectbase': projectbase,
-            }
-
-        if HAS_USER_SITE:
-            self.config_vars['userbase'] = self.install_userbase
-            self.config_vars['usersite'] = self.install_usersite
-
-        self.expand_basedirs()
-
-        self.dump_dirs("post-expand_basedirs()")
-
-        # Now define config vars for the base directories so we can expand
-        # everything else.
-        self.config_vars['base'] = self.install_base
-        self.config_vars['platbase'] = self.install_platbase
-
-        # Expand "~" and configuration variables in the installation
-        # directories.
-        self.expand_dirs()
-
-        self.dump_dirs("post-expand_dirs()")
-
-        # Create directories in the home dir:
-        if HAS_USER_SITE and self.user:
-            self.create_home_path()
-
-        # Pick the actual directory to install all modules to: either
-        # install_purelib or install_platlib, depending on whether this
-        # module distribution is pure or not.  Of course, if the user
-        # already specified install_lib, use their selection.
-        if self.install_lib is None:
-            if self.distribution.ext_modules:  # has extensions: non-pure
-                self.install_lib = self.install_platlib
-            else:
-                self.install_lib = self.install_purelib
-
-        # Convert directories from Unix /-separated syntax to the local
-        # convention.
-        self.convert_paths('lib', 'purelib', 'platlib',
-                           'scripts', 'data', 'headers')
-        if HAS_USER_SITE:
-            self.convert_paths('userbase', 'usersite')
-
-        # Well, we're not actually fully completely finalized yet: we still
-        # have to deal with 'extra_path', which is the hack for allowing
-        # non-packagized module distributions (hello, Numerical Python!) to
-        # get their own directories.
-        self.handle_extra_path()
-        self.install_libbase = self.install_lib  # needed for .pth file
-        self.install_lib = os.path.join(self.install_lib, self.extra_dirs)
-
-        # If a new root directory was supplied, make all the installation
-        # dirs relative to it.
-        if self.root is not None:
-            self.change_roots('libbase', 'lib', 'purelib', 'platlib',
-                              'scripts', 'data', 'headers')
-
-        self.dump_dirs("after prepending root")
-
-        # Find out the build directories, ie. where to install from.
+        # Find out the build directories, i.e. where to install from, from
+        # the 'configure' or 'build' command.  Also take other options from
+        # 'configure'.
+        self.set_undefined_options('configure',
+            *(option[0].rstrip('=').replace('-', '_')
+              for option in self.install_options
+              if option[0] not in self.negative_opt))
+        self.set_undefined_options('configure', ('force_install', 'force'))
         self.set_undefined_options('build', 'build_base', 'build_lib')
-
-        # Punt on doc directories for now -- after all, we're punting on
-        # documentation completely!
-
-        if self.no_distinfo is None:
-            self.no_distinfo = False
-
-    def finalize_unix(self):
-        """Finalize options for posix platforms."""
-        if self.install_base is not None or self.install_platbase is not None:
-            if ((self.install_lib is None and
-                 self.install_purelib is None and
-                 self.install_platlib is None) or
-                self.install_headers is None or
-                self.install_scripts is None or
-                self.install_data is None):
-                raise DistutilsOptionError(
-                    "install-base or install-platbase supplied, but "
-                    "installation scheme is incomplete")
-            return
-
-        if HAS_USER_SITE and self.user:
-            if self.install_userbase is None:
-                raise DistutilsPlatformError(
-                    "user base directory is not specified")
-            self.install_base = self.install_platbase = self.install_userbase
-            self.select_scheme("posix_user")
-        elif self.home is not None:
-            self.install_base = self.install_platbase = self.home
-            self.select_scheme("posix_home")
-        else:
-            if self.prefix is None:
-                if self.exec_prefix is not None:
-                    raise DistutilsOptionError(
-                        "must not supply exec-prefix without prefix")
-
-                self.prefix = os.path.normpath(sys.prefix)
-                self.exec_prefix = os.path.normpath(sys.exec_prefix)
-
-            else:
-                if self.exec_prefix is None:
-                    self.exec_prefix = self.prefix
-
-            self.install_base = self.prefix
-            self.install_platbase = self.exec_prefix
-            self.select_scheme("posix_prefix")
-
-    def finalize_other(self):
-        """Finalize options for non-posix platforms"""
-        if HAS_USER_SITE and self.user:
-            if self.install_userbase is None:
-                raise DistutilsPlatformError(
-                    "user base directory is not specified")
-            self.install_base = self.install_platbase = self.install_userbase
-            self.select_scheme(os.name + "_user")
-        elif self.home is not None:
-            self.install_base = self.install_platbase = self.home
-            self.select_scheme("posix_home")
-        else:
-            if self.prefix is None:
-                self.prefix = os.path.normpath(sys.prefix)
-
-            self.install_base = self.install_platbase = self.prefix
-            try:
-                self.select_scheme(os.name)
-            except KeyError:
-                raise DistutilsPlatformError(
-                    "no support for installation on '%s'" % os.name)
-
-    def dump_dirs(self, msg):
-        """Dump the list of user options."""
-        logger.debug(msg + ":")
-        for opt in self.user_options:
-            opt_name = opt[0]
-            if opt_name[-1] == "=":
-                opt_name = opt_name[0:-1]
-            if opt_name in self.negative_opt:
-                opt_name = self.negative_opt[opt_name]
-                opt_name = opt_name.replace('-', '_')
-                val = not getattr(self, opt_name)
-            else:
-                opt_name = opt_name.replace('-', '_')
-                val = getattr(self, opt_name)
-            logger.debug("  %s: %s" % (opt_name, val))
-
-    def select_scheme(self, name):
-        """Set the install directories by applying the install schemes."""
-        # it's the caller's problem if they supply a bad name!
-        scheme = get_paths(name, expand=False)
-        for key, value in scheme.items():
-            if key == 'platinclude':
-                key = 'headers'
-                value = os.path.join(value, self.distribution.metadata['Name'])
-            attrname = 'install_' + key
-            if hasattr(self, attrname):
-                if getattr(self, attrname) is None:
-                    setattr(self, attrname, value)
-
-    def _expand_attrs(self, attrs):
-        for attr in attrs:
-            val = getattr(self, attr)
-            if val is not None:
-                if os.name == 'posix' or os.name == 'nt':
-                    val = os.path.expanduser(val)
-                # see if we want to push this work in sysconfig XXX
-                val = sysconfig._subst_vars(val, self.config_vars)
-                setattr(self, attr, val)
-
-    def expand_basedirs(self):
-        """Call `os.path.expanduser` on install_{base,platbase} and root."""
-        self._expand_attrs(['install_base', 'install_platbase', 'root'])
-
-    def expand_dirs(self):
-        """Call `os.path.expanduser` on install dirs."""
-        self._expand_attrs(['install_purelib', 'install_platlib',
-                            'install_lib', 'install_headers',
-                            'install_scripts', 'install_data'])
-
-    def convert_paths(self, *names):
-        """Call `convert_path` over `names`."""
-        for name in names:
-            attr = "install_" + name
-            setattr(self, attr, convert_path(getattr(self, attr)))
-
-    def handle_extra_path(self):
-        """Set `path_file` and `extra_dirs` using `extra_path`."""
-        if self.extra_path is None:
-            self.extra_path = self.distribution.extra_path
-
-        if self.extra_path is not None:
-            if isinstance(self.extra_path, str):
-                self.extra_path = self.extra_path.split(',')
-
-            if len(self.extra_path) == 1:
-                path_file = extra_dirs = self.extra_path[0]
-            elif len(self.extra_path) == 2:
-                path_file, extra_dirs = self.extra_path
-            else:
-                raise DistutilsOptionError(
-                    "'extra_path' option must be a list, tuple, or "
-                    "comma-separated string with 1 or 2 elements")
-
-            # convert to local form in case Unix notation used (as it
-            # should be in setup scripts)
-            extra_dirs = convert_path(extra_dirs)
-        else:
-            path_file = None
-            extra_dirs = ''
-
-        # XXX should we warn if path_file and not extra_dirs? (in which
-        # case the path file would be harmless but pointless)
-        self.path_file = path_file
-        self.extra_dirs = extra_dirs
-
-    def change_roots(self, *names):
-        """Change the install direcories pointed by name using root."""
-        for name in names:
-            attr = "install_" + name
-            setattr(self, attr, change_root(self.root, getattr(self, attr)))
-
-    def create_home_path(self):
-        """Create directories under ~."""
-        if HAS_USER_SITE and not self.user:
-            return
-        home = convert_path(os.path.expanduser("~"))
-        for name, path in self.config_vars.iteritems():
-            if path.startswith(home) and not os.path.isdir(path):
-                os.makedirs(path, 0700)
+        self._from_configure = True
+        configure.finalize_install_options(self)
 
     # -- Command execution methods -------------------------------------
 

distutils2/dist.py

File contents unchanged.

distutils2/run.py

     return USAGE % {'script': script}
 
 
-def commands_main(**attrs):
+def main(**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; find and parse config files; parse the command
     return dist
 
 
-def main():
-    """Main entry point for Distutils2"""
-    parser = OptionParser()
-    parser.disable_interspersed_args()
-    parser.add_option("-v", "--version",
-                  action="store_true", dest="version", default=False,
-                  help="Prints out the version of Distutils2 and exits.")
-
-    options, args = parser.parse_args()
-    if options.version:
-        print('Distutils2 %s' % __version__)
-        sys.exit(0)
-
-    if len(args) == 0:
-        parser.print_help()
-
-    commands_main()
-    sys.exit(0)
 
 if __name__ == '__main__':
     main()

distutils2/tests/test_command_configure.py

+"""Tests for distutils2.command.configure."""
+
+import os
+
+import distutils2.command
+from distutils2.command.configure import configure
+from distutils2.dist import Distribution
+from distutils2.errors import DistutilsOptionError
+from distutils2.tests.support import unittest, TempdirManager, LoggingCatcher
+
+
+ALL_COMMANDS = distutils2.command.get_command_names()
+ALL_COMMANDS.pop(ALL_COMMANDS.index('configure'))
+
+
+class BaseTestCase(TempdirManager, LoggingCatcher, unittest.TestCase):
+
+    def setUp(self):
+        super(BaseTestCase, self).setUp()
+        self.addCleanup(os.chdir, os.getcwd())
+        self.tmpdir = self.mkdtemp()
+        os.chdir(self.tmpdir)
+
+    def run_cmd(self, args):
+        """Helper for tests: run (the equivalent of) setup.py *args
+
+        *args* can be a list of arguments or a string that will be split on
+        whitespace.
+
+        Return a Distribution instance.
+        """
+        if isinstance(args, basestring):
+            args = args.split()
+        d = Distribution(dict(name='bacon', version='0.1a0',
+                              script_name='python code', script_args=args))
+        d.parse_command_line()
+        d.run_commands()
+        return d
+
+    def get_cache_content(self):
+        try:
+            fp = open('configure.cache')
+            content = fp.read().splitlines()
+        finally:
+            fp.close()
+        return content
+
+
+class ConfigureTestCase(BaseTestCase):
+
+    def test_minimal(self):
+        # command runs ok
+        self.run_cmd('configure')
+        # no cache file is created if there is no option given
+        self.assertEquals(os.listdir(self.tmpdir), [])
+
+    def test_one_option(self):
+        # shortcut
+        eq = self.assertEquals
+        self.run_cmd('configure --build-base .')
+
+        # cache file is created
+        eq(os.listdir(self.tmpdir), ['configure.cache'])
+        content = self.get_cache_content()
+
+        # cache file starts with comments
+        eq(content[0][0], '#')
+        eq(content[1][0], '#')
+        # check contents
+        eq(content[2:], ['[configure]', 'build_base = .'])
+
+    def test_more_options(self):
+        eq = self.assertEquals
+        self.run_cmd('configure --build-temp ~ '
+                     '--prefix /opt --force-build --force-install')
+
+        # cache file is created
+        eq(os.listdir(self.tmpdir), ['configure.cache'])
+        content = self.get_cache_content()
+
+        # cache file starts with comments
+        eq(content[0][0], '#')
+        eq(content[1][0], '#')
+        # check contents
+        eq(content[2:], ['[configure]', 'build_temp = ~', 'force_build = 1',
+                         'force_install = 1', 'prefix = /opt'])
+
+        self.assertNotIn('force', configure.user_options)
+
+    def test_update(self):
+        pass  # TODO
+
+    def test_conflicts(self):
+        # TODO move code from test_build/install in a support module
+        pass
+
+
+class ConfigureBuildTestCase(BaseTestCase):
+
+    def test_together(self):
+        dist = self.run_cmd(
+            'configure --build-base TEST1 --build-lib TEST2 build')
+        build = dist.get_command_obj('build')
+        self.assertTrue(dist.have_run['build'])
+        self.assertEqual(build.build_base, 'TEST1')
+
+    def test_separate(self):
+        dist = self.run_cmd(
+            'configure --build-base TEST3 --build-scripts TEST4')
+        dist.dry_run = True
+        dist.run_command('build')
+        build = dist.get_command_obj('build')
+        build.ensure_finalized()
+        self.assertEqual(build.build_base, 'TEST3')
+        self.assertEqual(build.build_scripts, 'TEST4')
+
+
+class ConfigureInstallTestCase(BaseTestCase):
+    # test command lines like "setup.py configure --something install_dist"
+    # + test running configure first and then install_dist
+
+    def test_together(self):
+        dist = self.run_cmd('--dry-run configure --prefix /TEST3 install_dist')
+        install_dist = dist.get_command_obj('install_dist')
+        self.assertEqual(install_dist.prefix, '/TEST3')
+
+    def test_separate(self):
+        dist = self.run_cmd('configure --prefix /TEST4')
+        dist.dry_run = True
+        dist.run_command('install_dist')
+        install_dist = dist.get_command_obj('install_dist')
+        self.assertEqual(install_dist.prefix, '/TEST4')
+
+
+class ConfigureBuildInstallTestCase(BaseTestCase):
+    # + test running configure first and then build and install_dist
+    # (use options that are not taken from build by install_dist)
+
+    def test_together(self):
+        dist = self.run_cmd('configure --build-base TEST1 --prefix TEST2 '
+                            'build install_dist')
+        build = dist.get_command_obj('build')
+        install_dist = dist.get_command_obj('install_dist')
+        self.assertEqual(build.build_base, 'TEST1')
+        self.assertEqual(install_dist.prefix, 'TEST2')
+
+    def test_separate(self):
+        dist = self.run_cmd('configure --executable=python-goats '
+                            '--home /opt/test')
+        dist.dry_run = True
+        dist.run_command('build')
+        dist.run_command('install_dist')
+        build = dist.get_command_obj('build')
+        install_dist = dist.get_command_obj('install_dist')
+        self.assertEqual(build.executable, 'python-goats')
+        self.assertEqual(install_dist.home, '/opt/test')
+
+
+class CacheFileUsageTestCase(BaseTestCase):
+
+    def _write_and_run(self, command, **kwargs):
+        project_dir, dist = self.create_dist()
+        os.chdir(project_dir)
+        key, value = kwargs.iteritems().next()
+        self.write_file('configure.cache',
+                        '[%s]\n%s = %s' % (command, key, value))
+        dist.parse_config_files()
+        cmd = dist.get_command_obj(command)
+        cmd.ensure_finalized()
+        return getattr(cmd, key, ''), value
+
+    def test_allowed_commands(self):
+        self.assertEqual(*self._write_and_run('configure',
+                                              executable='pygoats'))
+        self.assertEqual(*self._write_and_run('configure',
+                                              home='/opt/goatlib'))
+        self.assertRaises(DistutilsOptionError, self._write_and_run,
+                          'configure', say='ni')
+
+    def test_disallowed_commands(self):
+        for command in ALL_COMMANDS:
+            self.assertNotEqual(*self._write_and_run(command, say='ni'))
+
+
+def test_suite():
+    suite = [unittest.makeSuite(ConfigureTestCase),
+             unittest.makeSuite(ConfigureBuildTestCase),
+             unittest.makeSuite(ConfigureInstallTestCase),
+             unittest.makeSuite(ConfigureBuildInstallTestCase),
+             ]
+    return unittest.TestSuite(suite)
+
+if __name__ == '__main__':
+    unittest.main()

distutils2/tests/test_command_install_dist.py

         cmd = install_dist(dist)
 
         # two elements
-        cmd.handle_extra_path()
+        cmd._handle_extra_install_path()
         self.assertEqual(cmd.extra_path, ['path', 'dirs'])
         self.assertEqual(cmd.extra_dirs, 'dirs')
         self.assertEqual(cmd.path_file, 'path')
 
         # one element
         cmd.extra_path = ['path']
-        cmd.handle_extra_path()
+        cmd._handle_extra_install_path()
         self.assertEqual(cmd.extra_path, ['path'])
         self.assertEqual(cmd.extra_dirs, 'path')
         self.assertEqual(cmd.path_file, 'path')
 
         # none
         dist.extra_path = cmd.extra_path = None
-        cmd.handle_extra_path()
+        cmd._handle_extra_install_path()
         self.assertEqual(cmd.extra_path, None)
         self.assertEqual(cmd.extra_dirs, '')
         self.assertEqual(cmd.path_file, None)
 
         # three elements (no way !)
         cmd.extra_path = 'path,dirs,again'
-        self.assertRaises(DistutilsOptionError, cmd.handle_extra_path)
+        self.assertRaises(DistutilsOptionError, cmd._handle_extra_install_path)
 
     def test_finalize_options(self):
         dist = Distribution({'name': 'xx'})

distutils2/tests/test_config.py

File contents unchanged.

distutils2/tests/test_install.py

File contents unchanged.

docs/source/distutils/index.rst

File contents unchanged.

docs/source/index.rst

File contents unchanged.

docs/source/install/index.rst

 modules from standard source distributions.
 
 
+.. FIXME mention distutils briefly and make distutils2 the new standard
+
 .. _inst-new-standard:
 
 The new standard: Distutils
 you'll run lots of individual Distutils commands on their own.
 
 
+.. _inst-using-configure:
+
+Using the configure command
+---------------------------
+
+For most simple distributions, running ``python setup.py install_dist`` is
+enough to get a distribution installed in the right place.  If the build step
+requires a specific option for example, the maintainer of the distibution can
+include it in the local :file:`setup.cfg` configuration file, right along the
+:file:`setup.py` script.  If you want to always install to your personal
+site-packages directory (Python 2.6 and higher), you can set this option
+onceagain and for all in your user configuration file (see
+:ref:`inst-config-files` to find where it's located).
+
+.. XXX update that part according to Tarek's explanation
+
+The :command:`configure` command fits a niche between these uses: It allows
+you to save up options for :command:`build` and :command:`install_dist` for
+one distribution, on one computer.  It does not impact every distribution like
+the user configuration file, and it does not impact all users of the setup
+script like the local :file:`setup.cfg` configuration file.  Let's take one
+example project consisting only of a ``bacon`` module with a :file:`setup.py`
+script.  During development, you want to test if install works, and you want
+to check exactly where all the files go.  Easy enough, just pass the right
+option so that the command installs everything under a temporary directory::
+
+    python setup.py install_dist --root ~/test
+
+If you want to further tailor the :command:`build` and :command:`install_dist`
+commands without creating a :file:`setup.cfg` file, because this file is
+included in distributions but you don't want to include options specific to
+your computer, you have to remember a number of options to give to the
+commands each time you type them, which can become tedious quickly.  Save
+typing with the :command:`configure` command::
+
+    python setup.py configure --build-base ~/test/build --root ~/test
+
+When you run ``python setup.py build``, the :option:`--build-base` option set
+with the :command:`configure` command will be used; when you run ``python
+setup.py install_dist``, the :option:`--root` will be used.
+
+To get a list of all supported options, run ``python setup.py configure
+--help``.  Options pertaining to :command:`build` are listed first, then
+options for :command:`install_dist`.  You can get options separately with e.g.
+``python setup.py build --help``.  Since both commands have a
+:option:`--force` option, they have been renamed in :command:`configure` to
+:option:`--force-build` and :option:`--force-install`.
+
+Under the hood, options are stored into a file named :file:`configure.cache`,
+located alongside the :file:`setup.py` script.  This file will be included in
+bdist, to allow third-party code to query build options.
+
+.. note:: :command:`configure` is still under development, since some of its
+most important tasks are not done yet.
+
+The command takes inspiration from the :command:`config` command implemented
+in 4Suite.
+
+
 .. _inst-how-build-works:
 
 How building works
         try:
             from distutils2._backport import hashlib
         except ImportError:
+            # XXX Not a beautiful kludge.
             import subprocess
             subprocess.call([sys.executable, 'setup.py', 'build_ext'])
     sys.exit(test_main())